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:
commit
331834898f
|
@ -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:
|
||||
|
|
|
@ -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 = <®_t1l1>;
|
||||
#pse-cells = <0>;
|
||||
};
|
|
@ -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
|
||||
|
||||
...
|
|
@ -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
|
||||
===================
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
|
@ -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");
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -286,6 +286,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
|
|||
[ETHTOOL_MSG_STATS_GET] = ðnl_stats_request_ops,
|
||||
[ETHTOOL_MSG_PHC_VCLOCKS_GET] = ðnl_phc_vclocks_request_ops,
|
||||
[ETHTOOL_MSG_MODULE_GET] = ðnl_module_request_ops,
|
||||
[ETHTOOL_MSG_PSE_GET] = ðnl_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[] = {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue