Merge branch 'add-generic-pse-support'

Oleksij Rempel says:

====================
add generic PSE support

Add generic support for the Ethernet Power Sourcing Equipment.
====================

Link: https://lore.kernel.org/r/20221003065202.3889095-1-o.rempel@pengutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2022-10-03 17:34:06 -07:00
commit 331834898f
21 changed files with 1080 additions and 12 deletions

View File

@ -144,6 +144,12 @@ properties:
Mark the corresponding energy efficient ethernet mode as
broken and request the ethernet to stop advertising it.
pses:
$ref: /schemas/types.yaml#/definitions/phandle-array
maxItems: 1
description:
Specifies a reference to a node representing a Power Sourcing Equipment.
phy-is-integrated:
$ref: /schemas/types.yaml#/definitions/flag
description:

View File

@ -0,0 +1,40 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/pse-pd/podl-pse-regulator.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Regulator based Power Sourcing Equipment
maintainers:
- Oleksij Rempel <o.rempel@pengutronix.de>
description: Regulator based PoDL PSE controller. The device must be referenced
by the PHY node to control power injection to the Ethernet cable.
allOf:
- $ref: "pse-controller.yaml#"
properties:
compatible:
const: podl-pse-regulator
'#pse-cells':
const: 0
pse-supply:
description: Power supply for the PSE controller
additionalProperties: false
required:
- compatible
- pse-supply
examples:
- |
ethernet-pse {
compatible = "podl-pse-regulator";
pse-supply = <&reg_t1l1>;
#pse-cells = <0>;
};

View File

@ -0,0 +1,33 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/pse-pd/pse-controller.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Power Sourcing Equipment (PSE).
description: Binding for the Power Sourcing Equipment (PSE) as defined in the
IEEE 802.3 specification. It is designed for hardware which is delivering
power over twisted pair/ethernet cable. The ethernet-pse nodes should be
used to describe PSE controller and referenced by the ethernet-phy node.
maintainers:
- Oleksij Rempel <o.rempel@pengutronix.de>
properties:
$nodename:
pattern: "^ethernet-pse(@.*)?$"
"#pse-cells":
description:
Used to uniquely identify a PSE instance within an IC. Will be
0 on PSE nodes with only a single output and at least 1 on nodes
controlling several outputs.
enum: [0, 1]
required:
- "#pse-cells"
additionalProperties: true
...

View File

@ -220,6 +220,8 @@ Userspace to kernel:
``ETHTOOL_MSG_PHC_VCLOCKS_GET`` get PHC virtual clocks info
``ETHTOOL_MSG_MODULE_SET`` set transceiver module parameters
``ETHTOOL_MSG_MODULE_GET`` get transceiver module parameters
``ETHTOOL_MSG_PSE_SET`` set PSE parameters
``ETHTOOL_MSG_PSE_GET`` get PSE parameters
===================================== =================================
Kernel to userspace:
@ -260,6 +262,7 @@ Kernel to userspace:
``ETHTOOL_MSG_STATS_GET_REPLY`` standard statistics
``ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY`` PHC virtual clocks info
``ETHTOOL_MSG_MODULE_GET_REPLY`` transceiver module parameters
``ETHTOOL_MSG_PSE_GET_REPLY`` PSE parameters
======================================== =================================
``GET`` requests are sent by userspace applications to retrieve device
@ -1627,6 +1630,62 @@ For SFF-8636 modules, low power mode is forced by the host according to table
For CMIS modules, low power mode is forced by the host according to table 6-12
in revision 5.0 of the specification.
PSE_GET
=======
Gets PSE attributes.
Request contents:
===================================== ====== ==========================
``ETHTOOL_A_PSE_HEADER`` nested request header
===================================== ====== ==========================
Kernel response contents:
====================================== ====== =============================
``ETHTOOL_A_PSE_HEADER`` nested reply header
``ETHTOOL_A_PODL_PSE_ADMIN_STATE`` u32 Operational state of the PoDL
PSE functions
``ETHTOOL_A_PODL_PSE_PW_D_STATUS`` u32 power detection status of the
PoDL PSE.
====================================== ====== =============================
When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_STATE`` attribute identifies
the operational state of the PoDL PSE functions. The operational state of the
PSE function can be changed using the ``ETHTOOL_A_PODL_PSE_ADMIN_CONTROL``
action. This option is corresponding to ``IEEE 802.3-2018`` 30.15.1.1.2
aPoDLPSEAdminState. Possible values are:
.. kernel-doc:: include/uapi/linux/ethtool.h
:identifiers: ethtool_podl_pse_admin_state
When set, the optional ``ETHTOOL_A_PODL_PSE_PW_D_STATUS`` attribute identifies
the power detection status of the PoDL PSE. The status depend on internal PSE
state machine and automatic PD classification support. This option is
corresponding to ``IEEE 802.3-2018`` 30.15.1.1.3 aPoDLPSEPowerDetectionStatus.
Possible values are:
.. kernel-doc:: include/uapi/linux/ethtool.h
:identifiers: ethtool_podl_pse_pw_d_status
PSE_SET
=======
Sets PSE parameters.
Request contents:
====================================== ====== =============================
``ETHTOOL_A_PSE_HEADER`` nested request header
``ETHTOOL_A_PODL_PSE_ADMIN_CONTROL`` u32 Control PoDL PSE Admin state
====================================== ====== =============================
When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_CONTROL`` attribute is used
to control PoDL PSE Admin functions. This option is implementing
``IEEE 802.3-2018`` 30.15.1.2.1 acPoDLPSEAdminControl. See
``ETHTOOL_A_PODL_PSE_ADMIN_STATE`` for supported values.
Request translation
===================

View File

@ -500,6 +500,8 @@ config NET_SB1000
source "drivers/net/phy/Kconfig"
source "drivers/net/pse-pd/Kconfig"
source "drivers/net/can/Kconfig"
source "drivers/net/mctp/Kconfig"

View File

@ -23,6 +23,7 @@ obj-$(CONFIG_NET) += loopback.o
obj-$(CONFIG_NETDEV_LEGACY_INIT) += Space.o
obj-$(CONFIG_NETCONSOLE) += netconsole.o
obj-y += phy/
obj-y += pse-pd/
obj-y += mdio/
obj-y += pcs/
obj-$(CONFIG_RIONET) += rionet.o

View File

@ -10,10 +10,31 @@
#include <linux/fwnode_mdio.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/pse-pd/pse.h>
MODULE_AUTHOR("Calvin Johnson <calvin.johnson@oss.nxp.com>");
MODULE_LICENSE("GPL");
static struct pse_control *
fwnode_find_pse_control(struct fwnode_handle *fwnode)
{
struct pse_control *psec;
struct device_node *np;
if (!IS_ENABLED(CONFIG_PSE_CONTROLLER))
return NULL;
np = to_of_node(fwnode);
if (!np)
return NULL;
psec = of_pse_control_get(np);
if (PTR_ERR(psec) == -ENOENT)
return NULL;
return psec;
}
static struct mii_timestamper *
fwnode_find_mii_timestamper(struct fwnode_handle *fwnode)
{
@ -91,14 +112,21 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
struct fwnode_handle *child, u32 addr)
{
struct mii_timestamper *mii_ts = NULL;
struct pse_control *psec = NULL;
struct phy_device *phy;
bool is_c45 = false;
u32 phy_id;
int rc;
psec = fwnode_find_pse_control(child);
if (IS_ERR(psec))
return PTR_ERR(psec);
mii_ts = fwnode_find_mii_timestamper(child);
if (IS_ERR(mii_ts))
return PTR_ERR(mii_ts);
if (IS_ERR(mii_ts)) {
rc = PTR_ERR(mii_ts);
goto clean_pse;
}
rc = fwnode_property_match_string(child, "compatible",
"ethernet-phy-ieee802.3-c45");
@ -110,8 +138,8 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
else
phy = phy_device_create(bus, addr, phy_id, 0, NULL);
if (IS_ERR(phy)) {
unregister_mii_timestamper(mii_ts);
return PTR_ERR(phy);
rc = PTR_ERR(phy);
goto clean_mii_ts;
}
if (is_acpi_node(child)) {
@ -125,25 +153,33 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
/* All data is now stored in the phy struct, so register it */
rc = phy_device_register(phy);
if (rc) {
phy_device_free(phy);
fwnode_handle_put(phy->mdio.dev.fwnode);
return rc;
goto clean_phy;
}
} else if (is_of_node(child)) {
rc = fwnode_mdiobus_phy_device_register(bus, phy, child, addr);
if (rc) {
unregister_mii_timestamper(mii_ts);
phy_device_free(phy);
return rc;
}
if (rc)
goto clean_phy;
}
phy->psec = psec;
/* phy->mii_ts may already be defined by the PHY driver. A
* mii_timestamper probed via the device tree will still have
* precedence.
*/
if (mii_ts)
phy->mii_ts = mii_ts;
return 0;
clean_phy:
phy_device_free(phy);
clean_mii_ts:
unregister_mii_timestamper(mii_ts);
clean_pse:
pse_control_put(psec);
return rc;
}
EXPORT_SYMBOL(fwnode_mdiobus_register_phy);

View File

@ -26,6 +26,7 @@
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/phy_led_triggers.h>
#include <linux/pse-pd/pse.h>
#include <linux/property.h>
#include <linux/sfp.h>
#include <linux/skbuff.h>
@ -991,6 +992,7 @@ EXPORT_SYMBOL(phy_device_register);
void phy_device_remove(struct phy_device *phydev)
{
unregister_mii_timestamper(phydev->mii_ts);
pse_control_put(phydev->psec);
device_del(&phydev->mdio.dev);

View File

@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Ethernet Power Sourcing Equipment drivers
#
menuconfig PSE_CONTROLLER
bool "Ethernet Power Sourcing Equipment Support"
help
Generic Power Sourcing Equipment Controller support.
If unsure, say no.
if PSE_CONTROLLER
config PSE_REGULATOR
tristate "Regulator based PSE controller"
help
This module provides support for simple regulator based Ethernet Power
Sourcing Equipment without automatic classification support. For
example for basic implementation of PoDL (802.3bu) specification.
endif

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
# Makefile for Linux PSE drivers
obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o

View File

@ -0,0 +1,314 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Framework for Ethernet Power Sourcing Equipment
//
// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
//
#include <linux/device.h>
#include <linux/of.h>
#include <linux/pse-pd/pse.h>
static DEFINE_MUTEX(pse_list_mutex);
static LIST_HEAD(pse_controller_list);
/**
* struct pse_control - a PSE control
* @pcdev: a pointer to the PSE controller device
* this PSE control belongs to
* @list: list entry for the pcdev's PSE controller list
* @id: ID of the PSE line in the PSE controller device
* @refcnt: Number of gets of this pse_control
*/
struct pse_control {
struct pse_controller_dev *pcdev;
struct list_head list;
unsigned int id;
struct kref refcnt;
};
/**
* of_pse_zero_xlate - dummy function for controllers with one only control
* @pcdev: a pointer to the PSE controller device
* @pse_spec: PSE line specifier as found in the device tree
*
* This static translation function is used by default if of_xlate in
* :c:type:`pse_controller_dev` is not set. It is useful for all PSE
* controllers with #pse-cells = <0>.
*/
static int of_pse_zero_xlate(struct pse_controller_dev *pcdev,
const struct of_phandle_args *pse_spec)
{
return 0;
}
/**
* of_pse_simple_xlate - translate pse_spec to the PSE line number
* @pcdev: a pointer to the PSE controller device
* @pse_spec: PSE line specifier as found in the device tree
*
* This static translation function is used by default if of_xlate in
* :c:type:`pse_controller_dev` is not set. It is useful for all PSE
* controllers with 1:1 mapping, where PSE lines can be indexed by number
* without gaps.
*/
static int of_pse_simple_xlate(struct pse_controller_dev *pcdev,
const struct of_phandle_args *pse_spec)
{
if (pse_spec->args[0] >= pcdev->nr_lines)
return -EINVAL;
return pse_spec->args[0];
}
/**
* pse_controller_register - register a PSE controller device
* @pcdev: a pointer to the initialized PSE controller device
*/
int pse_controller_register(struct pse_controller_dev *pcdev)
{
if (!pcdev->of_xlate) {
if (pcdev->of_pse_n_cells == 0)
pcdev->of_xlate = of_pse_zero_xlate;
else if (pcdev->of_pse_n_cells == 1)
pcdev->of_xlate = of_pse_simple_xlate;
}
mutex_init(&pcdev->lock);
INIT_LIST_HEAD(&pcdev->pse_control_head);
mutex_lock(&pse_list_mutex);
list_add(&pcdev->list, &pse_controller_list);
mutex_unlock(&pse_list_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(pse_controller_register);
/**
* pse_controller_unregister - unregister a PSE controller device
* @pcdev: a pointer to the PSE controller device
*/
void pse_controller_unregister(struct pse_controller_dev *pcdev)
{
mutex_lock(&pse_list_mutex);
list_del(&pcdev->list);
mutex_unlock(&pse_list_mutex);
}
EXPORT_SYMBOL_GPL(pse_controller_unregister);
static void devm_pse_controller_release(struct device *dev, void *res)
{
pse_controller_unregister(*(struct pse_controller_dev **)res);
}
/**
* devm_pse_controller_register - resource managed pse_controller_register()
* @dev: device that is registering this PSE controller
* @pcdev: a pointer to the initialized PSE controller device
*
* Managed pse_controller_register(). For PSE controllers registered by
* this function, pse_controller_unregister() is automatically called on
* driver detach. See pse_controller_register() for more information.
*/
int devm_pse_controller_register(struct device *dev,
struct pse_controller_dev *pcdev)
{
struct pse_controller_dev **pcdevp;
int ret;
pcdevp = devres_alloc(devm_pse_controller_release, sizeof(*pcdevp),
GFP_KERNEL);
if (!pcdevp)
return -ENOMEM;
ret = pse_controller_register(pcdev);
if (ret) {
devres_free(pcdevp);
return ret;
}
*pcdevp = pcdev;
devres_add(dev, pcdevp);
return 0;
}
EXPORT_SYMBOL_GPL(devm_pse_controller_register);
/* PSE control section */
static void __pse_control_release(struct kref *kref)
{
struct pse_control *psec = container_of(kref, struct pse_control,
refcnt);
lockdep_assert_held(&pse_list_mutex);
module_put(psec->pcdev->owner);
list_del(&psec->list);
kfree(psec);
}
static void __pse_control_put_internal(struct pse_control *psec)
{
lockdep_assert_held(&pse_list_mutex);
kref_put(&psec->refcnt, __pse_control_release);
}
/**
* pse_control_put - free the PSE control
* @psec: PSE control pointer
*/
void pse_control_put(struct pse_control *psec)
{
if (IS_ERR_OR_NULL(psec))
return;
mutex_lock(&pse_list_mutex);
__pse_control_put_internal(psec);
mutex_unlock(&pse_list_mutex);
}
EXPORT_SYMBOL_GPL(pse_control_put);
static struct pse_control *
pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index)
{
struct pse_control *psec;
lockdep_assert_held(&pse_list_mutex);
list_for_each_entry(psec, &pcdev->pse_control_head, list) {
if (psec->id == index) {
kref_get(&psec->refcnt);
return psec;
}
}
psec = kzalloc(sizeof(*psec), GFP_KERNEL);
if (!psec)
return ERR_PTR(-ENOMEM);
if (!try_module_get(pcdev->owner)) {
kfree(psec);
return ERR_PTR(-ENODEV);
}
psec->pcdev = pcdev;
list_add(&psec->list, &pcdev->pse_control_head);
psec->id = index;
kref_init(&psec->refcnt);
return psec;
}
struct pse_control *
of_pse_control_get(struct device_node *node)
{
struct pse_controller_dev *r, *pcdev;
struct of_phandle_args args;
struct pse_control *psec;
int psec_id;
int ret;
if (!node)
return ERR_PTR(-EINVAL);
ret = of_parse_phandle_with_args(node, "pses", "#pse-cells", 0, &args);
if (ret)
return ERR_PTR(ret);
mutex_lock(&pse_list_mutex);
pcdev = NULL;
list_for_each_entry(r, &pse_controller_list, list) {
if (args.np == r->dev->of_node) {
pcdev = r;
break;
}
}
if (!pcdev) {
psec = ERR_PTR(-EPROBE_DEFER);
goto out;
}
if (WARN_ON(args.args_count != pcdev->of_pse_n_cells)) {
psec = ERR_PTR(-EINVAL);
goto out;
}
psec_id = pcdev->of_xlate(pcdev, &args);
if (psec_id < 0) {
psec = ERR_PTR(psec_id);
goto out;
}
/* pse_list_mutex also protects the pcdev's pse_control list */
psec = pse_control_get_internal(pcdev, psec_id);
out:
mutex_unlock(&pse_list_mutex);
of_node_put(args.np);
return psec;
}
EXPORT_SYMBOL_GPL(of_pse_control_get);
/**
* pse_ethtool_get_status - get status of PSE control
* @psec: PSE control pointer
* @extack: extack for reporting useful error messages
* @status: struct to store PSE status
*/
int pse_ethtool_get_status(struct pse_control *psec,
struct netlink_ext_ack *extack,
struct pse_control_status *status)
{
const struct pse_controller_ops *ops;
int err;
ops = psec->pcdev->ops;
if (!ops->ethtool_get_status) {
NL_SET_ERR_MSG(extack,
"PSE driver does not support status report");
return -EOPNOTSUPP;
}
mutex_lock(&psec->pcdev->lock);
err = ops->ethtool_get_status(psec->pcdev, psec->id, extack, status);
mutex_unlock(&psec->pcdev->lock);
return err;
}
EXPORT_SYMBOL_GPL(pse_ethtool_get_status);
/**
* pse_ethtool_set_config - set PSE control configuration
* @psec: PSE control pointer
* @extack: extack for reporting useful error messages
* @config: Configuration of the test to run
*/
int pse_ethtool_set_config(struct pse_control *psec,
struct netlink_ext_ack *extack,
const struct pse_control_config *config)
{
const struct pse_controller_ops *ops;
int err;
ops = psec->pcdev->ops;
if (!ops->ethtool_set_config) {
NL_SET_ERR_MSG(extack,
"PSE driver does not configuration");
return -EOPNOTSUPP;
}
mutex_lock(&psec->pcdev->lock);
err = ops->ethtool_set_config(psec->pcdev, psec->id, extack, config);
mutex_unlock(&psec->pcdev->lock);
return err;
}
EXPORT_SYMBOL_GPL(pse_ethtool_set_config);

View File

@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Driver for the regulator based Ethernet Power Sourcing Equipment, without
// auto classification support.
//
// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
//
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pse-pd/pse.h>
#include <linux/regulator/consumer.h>
struct pse_reg_priv {
struct pse_controller_dev pcdev;
struct regulator *ps; /*power source */
enum ethtool_podl_pse_admin_state admin_state;
};
static struct pse_reg_priv *to_pse_reg(struct pse_controller_dev *pcdev)
{
return container_of(pcdev, struct pse_reg_priv, pcdev);
}
static int
pse_reg_ethtool_set_config(struct pse_controller_dev *pcdev, unsigned long id,
struct netlink_ext_ack *extack,
const struct pse_control_config *config)
{
struct pse_reg_priv *priv = to_pse_reg(pcdev);
int ret;
if (priv->admin_state == config->admin_cotrol)
return 0;
switch (config->admin_cotrol) {
case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED:
ret = regulator_enable(priv->ps);
break;
case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED:
ret = regulator_disable(priv->ps);
break;
default:
dev_err(pcdev->dev, "Unknown admin state %i\n",
config->admin_cotrol);
ret = -ENOTSUPP;
}
if (ret)
return ret;
priv->admin_state = config->admin_cotrol;
return 0;
}
static int
pse_reg_ethtool_get_status(struct pse_controller_dev *pcdev, unsigned long id,
struct netlink_ext_ack *extack,
struct pse_control_status *status)
{
struct pse_reg_priv *priv = to_pse_reg(pcdev);
int ret;
ret = regulator_is_enabled(priv->ps);
if (ret < 0)
return ret;
if (!ret)
status->podl_pw_status = ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
else
status->podl_pw_status =
ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING;
status->podl_admin_state = priv->admin_state;
return 0;
}
static const struct pse_controller_ops pse_reg_ops = {
.ethtool_get_status = pse_reg_ethtool_get_status,
.ethtool_set_config = pse_reg_ethtool_set_config,
};
static int
pse_reg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pse_reg_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
if (!pdev->dev.of_node)
return -ENOENT;
priv->ps = devm_regulator_get_exclusive(dev, "pse");
if (IS_ERR(priv->ps))
return dev_err_probe(dev, PTR_ERR(priv->ps),
"failed to get PSE regulator.\n");
platform_set_drvdata(pdev, priv);
ret = regulator_is_enabled(priv->ps);
if (ret < 0)
return ret;
if (ret)
priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
else
priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
priv->pcdev.owner = THIS_MODULE;
priv->pcdev.ops = &pse_reg_ops;
priv->pcdev.dev = dev;
ret = devm_pse_controller_register(dev, &priv->pcdev);
if (ret) {
dev_err(dev, "failed to register PSE controller (%pe)\n",
ERR_PTR(ret));
return ret;
}
return 0;
}
static const __maybe_unused struct of_device_id pse_reg_of_match[] = {
{ .compatible = "podl-pse-regulator", },
{ },
};
MODULE_DEVICE_TABLE(of, pse_reg_of_match);
static struct platform_driver pse_reg_driver = {
.probe = pse_reg_probe,
.driver = {
.name = "PSE regulator",
.of_match_table = of_match_ptr(pse_reg_of_match),
},
};
module_platform_driver(pse_reg_driver);
MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>");
MODULE_DESCRIPTION("regulator based Ethernet Power Sourcing Equipment");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:pse-regulator");

View File

@ -597,6 +597,7 @@ struct macsec_ops;
* @master_slave_get: Current master/slave advertisement
* @master_slave_state: Current master/slave configuration
* @mii_ts: Pointer to time stamper callbacks
* @psec: Pointer to Power Sourcing Equipment control struct
* @lock: Mutex for serialization access to PHY
* @state_queue: Work queue for state machine
* @shared: Pointer to private data shared by phys in one package
@ -715,6 +716,7 @@ struct phy_device {
struct phylink *phylink;
struct net_device *attached_dev;
struct mii_timestamper *mii_ts;
struct pse_control *psec;
u8 mdix;
u8 mdix_ctrl;

129
include/linux/pse-pd/pse.h Normal file
View File

@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
*/
#ifndef _LINUX_PSE_CONTROLLER_H
#define _LINUX_PSE_CONTROLLER_H
#include <linux/ethtool.h>
#include <linux/list.h>
#include <uapi/linux/ethtool.h>
struct phy_device;
struct pse_controller_dev;
/**
* struct pse_control_config - PSE control/channel configuration.
*
* @admin_cotrol: set PoDL PSE admin control as described in
* IEEE 802.3-2018 30.15.1.2.1 acPoDLPSEAdminControl
*/
struct pse_control_config {
enum ethtool_podl_pse_admin_state admin_cotrol;
};
/**
* struct pse_control_status - PSE control/channel status.
*
* @podl_admin_state: operational state of the PoDL PSE
* functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
* @podl_pw_status: power detection status of the PoDL PSE.
* IEEE 802.3-2018 30.15.1.1.3 aPoDLPSEPowerDetectionStatus:
*/
struct pse_control_status {
enum ethtool_podl_pse_admin_state podl_admin_state;
enum ethtool_podl_pse_pw_d_status podl_pw_status;
};
/**
* struct pse_controller_ops - PSE controller driver callbacks
*
* @ethtool_get_status: get PSE control status for ethtool interface
* @ethtool_set_config: set PSE control configuration over ethtool interface
*/
struct pse_controller_ops {
int (*ethtool_get_status)(struct pse_controller_dev *pcdev,
unsigned long id, struct netlink_ext_ack *extack,
struct pse_control_status *status);
int (*ethtool_set_config)(struct pse_controller_dev *pcdev,
unsigned long id, struct netlink_ext_ack *extack,
const struct pse_control_config *config);
};
struct module;
struct device_node;
struct of_phandle_args;
struct pse_control;
/**
* struct pse_controller_dev - PSE controller entity that might
* provide multiple PSE controls
* @ops: a pointer to device specific struct pse_controller_ops
* @owner: kernel module of the PSE controller driver
* @list: internal list of PSE controller devices
* @pse_control_head: head of internal list of requested PSE controls
* @dev: corresponding driver model device struct
* @of_pse_n_cells: number of cells in PSE line specifiers
* @of_xlate: translation function to translate from specifier as found in the
* device tree to id as given to the PSE control ops
* @nr_lines: number of PSE controls in this controller device
* @lock: Mutex for serialization access to the PSE controller
*/
struct pse_controller_dev {
const struct pse_controller_ops *ops;
struct module *owner;
struct list_head list;
struct list_head pse_control_head;
struct device *dev;
int of_pse_n_cells;
int (*of_xlate)(struct pse_controller_dev *pcdev,
const struct of_phandle_args *pse_spec);
unsigned int nr_lines;
struct mutex lock;
};
#if IS_ENABLED(CONFIG_PSE_CONTROLLER)
int pse_controller_register(struct pse_controller_dev *pcdev);
void pse_controller_unregister(struct pse_controller_dev *pcdev);
struct device;
int devm_pse_controller_register(struct device *dev,
struct pse_controller_dev *pcdev);
struct pse_control *of_pse_control_get(struct device_node *node);
void pse_control_put(struct pse_control *psec);
int pse_ethtool_get_status(struct pse_control *psec,
struct netlink_ext_ack *extack,
struct pse_control_status *status);
int pse_ethtool_set_config(struct pse_control *psec,
struct netlink_ext_ack *extack,
const struct pse_control_config *config);
#else
static inline struct pse_control *of_pse_control_get(struct device_node *node)
{
return ERR_PTR(-ENOENT);
}
static inline void pse_control_put(struct pse_control *psec)
{
}
int pse_ethtool_get_status(struct pse_control *psec,
struct netlink_ext_ack *extack,
struct pse_control_status *status)
{
return -ENOTSUPP;
}
int pse_ethtool_set_config(struct pse_control *psec,
struct netlink_ext_ack *extack,
const struct pse_control_config *config)
{
return -ENOTSUPP;
}
#endif
#endif

View File

@ -736,6 +736,51 @@ enum ethtool_module_power_mode {
ETHTOOL_MODULE_POWER_MODE_HIGH,
};
/**
* enum ethtool_podl_pse_admin_state - operational state of the PoDL PSE
* functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
* @ETHTOOL_PODL_PSE_ADMIN_STATE_UNKNOWN: state of PoDL PSE functions are
* unknown
* @ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED: PoDL PSE functions are disabled
* @ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED: PoDL PSE functions are enabled
*/
enum ethtool_podl_pse_admin_state {
ETHTOOL_PODL_PSE_ADMIN_STATE_UNKNOWN = 1,
ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED,
ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED,
};
/**
* enum ethtool_podl_pse_pw_d_status - power detection status of the PoDL PSE.
* IEEE 802.3-2018 30.15.1.1.3 aPoDLPSEPowerDetectionStatus:
* @ETHTOOL_PODL_PSE_PW_D_STATUS_UNKNOWN: PoDL PSE
* @ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED: "The enumeration “disabled” is
* asserted true when the PoDL PSE state diagram variable mr_pse_enable is
* false"
* @ETHTOOL_PODL_PSE_PW_D_STATUS_SEARCHING: "The enumeration “searching” is
* asserted true when either of the PSE state diagram variables
* pi_detecting or pi_classifying is true."
* @ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING: "The enumeration “deliveringPower”
* is asserted true when the PoDL PSE state diagram variable pi_powered is
* true."
* @ETHTOOL_PODL_PSE_PW_D_STATUS_SLEEP: "The enumeration “sleep” is asserted
* true when the PoDL PSE state diagram variable pi_sleeping is true."
* @ETHTOOL_PODL_PSE_PW_D_STATUS_IDLE: "The enumeration “idle” is asserted true
* when the logical combination of the PoDL PSE state diagram variables
* pi_prebiased*!pi_sleeping is true."
* @ETHTOOL_PODL_PSE_PW_D_STATUS_ERROR: "The enumeration “error” is asserted
* true when the PoDL PSE state diagram variable overload_held is true."
*/
enum ethtool_podl_pse_pw_d_status {
ETHTOOL_PODL_PSE_PW_D_STATUS_UNKNOWN = 1,
ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED,
ETHTOOL_PODL_PSE_PW_D_STATUS_SEARCHING,
ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING,
ETHTOOL_PODL_PSE_PW_D_STATUS_SLEEP,
ETHTOOL_PODL_PSE_PW_D_STATUS_IDLE,
ETHTOOL_PODL_PSE_PW_D_STATUS_ERROR,
};
/**
* struct ethtool_gstrings - string set for data tagging
* @cmd: Command number = %ETHTOOL_GSTRINGS

View File

@ -49,6 +49,8 @@ enum {
ETHTOOL_MSG_PHC_VCLOCKS_GET,
ETHTOOL_MSG_MODULE_GET,
ETHTOOL_MSG_MODULE_SET,
ETHTOOL_MSG_PSE_GET,
ETHTOOL_MSG_PSE_SET,
/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
@ -94,6 +96,7 @@ enum {
ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY,
ETHTOOL_MSG_MODULE_GET_REPLY,
ETHTOOL_MSG_MODULE_NTF,
ETHTOOL_MSG_PSE_GET_REPLY,
/* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT,
@ -863,6 +866,19 @@ enum {
ETHTOOL_A_MODULE_MAX = (__ETHTOOL_A_MODULE_CNT - 1)
};
/* Power Sourcing Equipment */
enum {
ETHTOOL_A_PSE_UNSPEC,
ETHTOOL_A_PSE_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_PODL_PSE_ADMIN_STATE, /* u32 */
ETHTOOL_A_PODL_PSE_ADMIN_CONTROL, /* u32 */
ETHTOOL_A_PODL_PSE_PW_D_STATUS, /* u32 */
/* add new constants above here */
__ETHTOOL_A_PSE_CNT,
ETHTOOL_A_PSE_MAX = (__ETHTOOL_A_PSE_CNT - 1)
};
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1

View File

@ -7,4 +7,5 @@ obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
linkstate.o debug.o wol.o features.o privflags.o rings.o \
channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
tunnels.o fec.o eeprom.o stats.o phc_vclocks.o module.o
tunnels.o fec.o eeprom.o stats.o phc_vclocks.o module.o \
pse-pd.o

View File

@ -46,6 +46,7 @@ int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max);
int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info);
extern const struct ethtool_phy_ops *ethtool_phy_ops;
extern const struct ethtool_pse_ops *ethtool_pse_ops;
int ethtool_get_module_info_call(struct net_device *dev,
struct ethtool_modinfo *modinfo);

View File

@ -286,6 +286,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_STATS_GET] = &ethnl_stats_request_ops,
[ETHTOOL_MSG_PHC_VCLOCKS_GET] = &ethnl_phc_vclocks_request_ops,
[ETHTOOL_MSG_MODULE_GET] = &ethnl_module_request_ops,
[ETHTOOL_MSG_PSE_GET] = &ethnl_pse_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@ -1023,6 +1024,22 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_module_set_policy,
.maxattr = ARRAY_SIZE(ethnl_module_set_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_PSE_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
.policy = ethnl_pse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_pse_get_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_PSE_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_pse,
.policy = ethnl_pse_set_policy,
.maxattr = ARRAY_SIZE(ethnl_pse_set_policy) - 1,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {

View File

@ -345,6 +345,7 @@ extern const struct ethnl_request_ops ethnl_module_eeprom_request_ops;
extern const struct ethnl_request_ops ethnl_stats_request_ops;
extern const struct ethnl_request_ops ethnl_phc_vclocks_request_ops;
extern const struct ethnl_request_ops ethnl_module_request_ops;
extern const struct ethnl_request_ops ethnl_pse_request_ops;
extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
@ -383,6 +384,8 @@ extern const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_GROUPS + 1
extern const struct nla_policy ethnl_phc_vclocks_get_policy[ETHTOOL_A_PHC_VCLOCKS_HEADER + 1];
extern const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER + 1];
extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 1];
extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1];
extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1];
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
@ -402,6 +405,7 @@ int ethnl_tunnel_info_start(struct netlink_callback *cb);
int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_set_fec(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_module(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_pse(struct sk_buff *skb, struct genl_info *info);
extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];

185
net/ethtool/pse-pd.c Normal file
View File

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// ethtool interface for for Ethernet PSE (Power Sourcing Equipment)
// and PD (Powered Device)
//
// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
//
#include "common.h"
#include "linux/pse-pd/pse.h"
#include "netlink.h"
#include <linux/ethtool_netlink.h>
#include <linux/ethtool.h>
#include <linux/phy.h>
struct pse_req_info {
struct ethnl_req_info base;
};
struct pse_reply_data {
struct ethnl_reply_data base;
struct pse_control_status status;
};
#define PSE_REPDATA(__reply_base) \
container_of(__reply_base, struct pse_reply_data, base)
/* PSE_GET */
const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1] = {
[ETHTOOL_A_PSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
};
static int pse_get_pse_attributes(struct net_device *dev,
struct netlink_ext_ack *extack,
struct pse_reply_data *data)
{
struct phy_device *phydev = dev->phydev;
if (!phydev) {
NL_SET_ERR_MSG(extack, "No PHY is attached");
return -EOPNOTSUPP;
}
if (!phydev->psec) {
NL_SET_ERR_MSG(extack, "No PSE is attached");
return -EOPNOTSUPP;
}
memset(&data->status, 0, sizeof(data->status));
return pse_ethtool_get_status(phydev->psec, extack, &data->status);
}
static int pse_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct pse_reply_data *data = PSE_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
int ret;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
ret = pse_get_pse_attributes(dev, info->extack, data);
ethnl_ops_complete(dev);
return ret;
}
static int pse_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct pse_reply_data *data = PSE_REPDATA(reply_base);
const struct pse_control_status *st = &data->status;
int len = 0;
if (st->podl_admin_state > 0)
len += nla_total_size(sizeof(u32)); /* _PODL_PSE_ADMIN_STATE */
if (st->podl_pw_status > 0)
len += nla_total_size(sizeof(u32)); /* _PODL_PSE_PW_D_STATUS */
return len;
}
static int pse_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct pse_reply_data *data = PSE_REPDATA(reply_base);
const struct pse_control_status *st = &data->status;
if (st->podl_admin_state > 0 &&
nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE,
st->podl_admin_state))
return -EMSGSIZE;
if (st->podl_pw_status > 0 &&
nla_put_u32(skb, ETHTOOL_A_PODL_PSE_PW_D_STATUS,
st->podl_pw_status))
return -EMSGSIZE;
return 0;
}
const struct ethnl_request_ops ethnl_pse_request_ops = {
.request_cmd = ETHTOOL_MSG_PSE_GET,
.reply_cmd = ETHTOOL_MSG_PSE_GET_REPLY,
.hdr_attr = ETHTOOL_A_PSE_HEADER,
.req_info_size = sizeof(struct pse_req_info),
.reply_data_size = sizeof(struct pse_reply_data),
.prepare_data = pse_prepare_data,
.reply_size = pse_reply_size,
.fill_reply = pse_fill_reply,
};
/* PSE_SET */
const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1] = {
[ETHTOOL_A_PSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_PODL_PSE_ADMIN_CONTROL] =
NLA_POLICY_RANGE(NLA_U32, ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED,
ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED),
};
static int pse_set_pse_config(struct net_device *dev,
struct netlink_ext_ack *extack,
struct nlattr **tb)
{
struct phy_device *phydev = dev->phydev;
struct pse_control_config config = {};
/* Optional attribute. Do not return error if not set. */
if (!tb[ETHTOOL_A_PODL_PSE_ADMIN_CONTROL])
return 0;
/* this values are already validated by the ethnl_pse_set_policy */
config.admin_cotrol = nla_get_u32(tb[ETHTOOL_A_PODL_PSE_ADMIN_CONTROL]);
if (!phydev) {
NL_SET_ERR_MSG(extack, "No PHY is attached");
return -EOPNOTSUPP;
}
if (!phydev->psec) {
NL_SET_ERR_MSG(extack, "No PSE is attached");
return -EOPNOTSUPP;
}
return pse_ethtool_set_config(phydev->psec, extack, &config);
}
int ethnl_set_pse(struct sk_buff *skb, struct genl_info *info)
{
struct ethnl_req_info req_info = {};
struct nlattr **tb = info->attrs;
struct net_device *dev;
int ret;
ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_PSE_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req_info.dev;
rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ret = pse_set_pse_config(dev, info->extack, tb);
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info);
return ret;
}