Merge branch 'phy-listing-and-topology-tracking'

Maxime Chevallier says:

====================
Introduce PHY listing and link_topology tracking

This is V18 of the phy_link_topology series, aiming at improving support
for multiple PHYs being attached to the same MAC.

V18 is a simple rebase of the V17 on top of net-next, gathering the
tested-by and reviewed-by tags from Christophe (thanks !).

This iteration is also one patch shorter than V17 (patch 12/14 in V17 is gone),
as one of the patches used to fix an issue that has now been resolved by
Simon Horman in

743ff02152 ethtool: Don't check for NULL info in prepare_data callbacks

As a remainder, here's what the PHY listings would look like :
 - eth0 has a 88x3310 acting as media converter, and an SFP module with
   an embedded 88e1111 PHY
 - eth2 has a 88e1510 PHY

PHY for eth0:
PHY index: 1
Driver name: mv88x3310
PHY device name: f212a600.mdio-mii:00
Downstream SFP bus name: sfp-eth0
Upstream type: MAC

PHY for eth0:
PHY index: 2
Driver name: Marvell 88E1111
PHY device name: i2c:sfp-eth0:16
Upstream type: PHY
Upstream PHY index: 1
Upstream SFP name: sfp-eth0

PHY for eth2:
PHY index: 1
Driver name: Marvell 88E1510
PHY device name: f212a200.mdio-mii:00
Upstream type: MAC

Ethtool patches : https://github.com/minimaxwell/ethtool/tree/mc/topo-v16
(this branch is compatible with this V18 series)

Link to V17: https://lore.kernel.org/netdev/20240709063039.2909536-1-maxime.chevallier@bootlin.com/
Link to V16: https://lore.kernel.org/netdev/20240705132706.13588-1-maxime.chevallier@bootlin.com/
Link to V15: https://lore.kernel.org/netdev/20240703140806.271938-1-maxime.chevallier@bootlin.com/
Link to V14: https://lore.kernel.org/netdev/20240701131801.1227740-1-maxime.chevallier@bootlin.com/
Link to V13: https://lore.kernel.org/netdev/20240607071836.911403-1-maxime.chevallier@bootlin.com/
Link to v12: https://lore.kernel.org/netdev/20240605124920.720690-1-maxime.chevallier@bootlin.com/
Link to v11: https://lore.kernel.org/netdev/20240404093004.2552221-1-maxime.chevallier@bootlin.com/
Link to V10: https://lore.kernel.org/netdev/20240304151011.1610175-1-maxime.chevallier@bootlin.com/
Link to V9: https://lore.kernel.org/netdev/20240228114728.51861-1-maxime.chevallier@bootlin.com/
Link to V8: https://lore.kernel.org/netdev/20240220184217.3689988-1-maxime.chevallier@bootlin.com/
Link to V7: https://lore.kernel.org/netdev/20240213150431.1796171-1-maxime.chevallier@bootlin.com/
Link to V6: https://lore.kernel.org/netdev/20240126183851.2081418-1-maxime.chevallier@bootlin.com/
Link to V5: https://lore.kernel.org/netdev/20231221180047.1924733-1-maxime.chevallier@bootlin.com/
Link to V4: https://lore.kernel.org/netdev/20231215171237.1152563-1-maxime.chevallier@bootlin.com/
Link to V3: https://lore.kernel.org/netdev/20231201163704.1306431-1-maxime.chevallier@bootlin.com/
Link to V2: https://lore.kernel.org/netdev/20231117162323.626979-1-maxime.chevallier@bootlin.com/
Link to V1: https://lore.kernel.org/netdev/20230907092407.647139-1-maxime.chevallier@bootlin.com/

More discussions on specific issues that happened in 6.9-rc:

https://lore.kernel.org/netdev/20240412104615.3779632-1-maxime.chevallier@bootlin.com/
https://lore.kernel.org/netdev/20240429131008.439231-1-maxime.chevallier@bootlin.com/
https://lore.kernel.org/netdev/20240507102822.2023826-1-maxime.chevallier@bootlin.com/
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2024-08-23 13:04:35 +01:00
commit b34a6e73fa
30 changed files with 1056 additions and 51 deletions

View File

@ -39,6 +39,11 @@ definitions:
- ovld-detected
- power-not-available
- short-detected
-
name: phy-upstream-type
enum-name:
type: enum
entries: [ mac, phy ]
attribute-sets:
-
@ -54,6 +59,9 @@ attribute-sets:
name: flags
type: u32
enum: header-flags
-
name: phy-index
type: u32
-
name: bitset-bit
@ -1089,6 +1097,35 @@ attribute-sets:
-
name: total
type: uint
-
name: phy
attributes:
-
name: header
type: nest
nested-attributes: header
-
name: index
type: u32
-
name: drvname
type: string
-
name: name
type: string
-
name: upstream-type
type: u32
enum: phy-upstream-type
-
name: upstream-index
type: u32
-
name: upstream-sfp-name
type: string
-
name: downstream-sfp-name
type: string
operations:
enum-model: directional
@ -1887,3 +1924,24 @@ operations:
- status-msg
- done
- total
-
name: phy-get
doc: Get PHY devices attached to an interface
attribute-set: phy
do: &phy-get-op
request:
attributes:
- header
reply:
attributes:
- header
- index
- drvname
- name
- upstream-type
- upstream-index
- upstream-sfp-name
- downstream-sfp-name
dump: *phy-get-op

View File

@ -57,6 +57,7 @@ Structure of this header is
``ETHTOOL_A_HEADER_DEV_INDEX`` u32 device ifindex
``ETHTOOL_A_HEADER_DEV_NAME`` string device name
``ETHTOOL_A_HEADER_FLAGS`` u32 flags common for all requests
``ETHTOOL_A_HEADER_PHY_INDEX`` u32 phy device index
============================== ====== =============================
``ETHTOOL_A_HEADER_DEV_INDEX`` and ``ETHTOOL_A_HEADER_DEV_NAME`` identify the
@ -81,6 +82,12 @@ the behaviour is backward compatible, i.e. requests from old clients not aware
of the flag should be interpreted the way the client expects. A client must
not set flags it does not understand.
``ETHTOOL_A_HEADER_PHY_INDEX`` identifies the Ethernet PHY the message relates to.
As there are numerous commands that are related to PHY configuration, and because
there may be more than one PHY on the link, the PHY index can be passed in the
request for the commands that needs it. It is, however, not mandatory, and if it
is not passed for commands that target a PHY, the net_device.phydev pointer
is used.
Bit sets
========
@ -2184,6 +2191,49 @@ string.
The ``ETHTOOL_A_MODULE_FW_FLASH_DONE`` and ``ETHTOOL_A_MODULE_FW_FLASH_TOTAL``
attributes encode the completed and total amount of work, respectively.
PHY_GET
=======
Retrieve information about a given Ethernet PHY sitting on the link. The DO
operation returns all available information about dev->phydev. User can also
specify a PHY_INDEX, in which case the DO request returns information about that
specific PHY.
As there can be more than one PHY, the DUMP operation can be used to list the PHYs
present on a given interface, by passing an interface index or name in
the dump request.
For more information, refer to :ref:`phy_link_topology`
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_PHY_HEADER`` nested request header
==================================== ====== ==========================
Kernel response contents:
===================================== ====== ===============================
``ETHTOOL_A_PHY_HEADER`` nested request header
``ETHTOOL_A_PHY_INDEX`` u32 the phy's unique index, that can
be used for phy-specific
requests
``ETHTOOL_A_PHY_DRVNAME`` string the phy driver name
``ETHTOOL_A_PHY_NAME`` string the phy device name
``ETHTOOL_A_PHY_UPSTREAM_TYPE`` u32 the type of device this phy is
connected to
``ETHTOOL_A_PHY_UPSTREAM_INDEX`` u32 the PHY index of the upstream
PHY
``ETHTOOL_A_PHY_UPSTREAM_SFP_NAME`` string if this PHY is connected to
its parent PHY through an SFP
bus, the name of this sfp bus
``ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME`` string if the phy controls an sfp bus,
the name of the sfp bus
===================================== ====== ===============================
When ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` is PHY_UPSTREAM_PHY, the PHY's parent is
another PHY.
Request translation
===================
@ -2291,4 +2341,5 @@ are netlink only.
n/a ``ETHTOOL_MSG_MM_GET``
n/a ``ETHTOOL_MSG_MM_SET``
n/a ``ETHTOOL_MSG_MODULE_FW_FLASH_ACT``
n/a ``ETHTOOL_MSG_PHY_GET``
=================================== =====================================

View File

@ -91,6 +91,7 @@ Contents:
operstates
packet_mmap
phonet
phy-link-topology
pktgen
plip
ppp_generic

View File

@ -0,0 +1,121 @@
.. SPDX-License-Identifier: GPL-2.0
.. _phy_link_topology:
=================
PHY link topology
=================
Overview
========
The PHY link topology representation in the networking stack aims at representing
the hardware layout for any given Ethernet link.
An Ethernet interface from userspace's point of view is nothing but a
:c:type:`struct net_device <net_device>`, which exposes configuration options
through the legacy ioctls and the ethtool netlink commands. The base assumption
when designing these configuration APIs were that the link looks something like ::
+-----------------------+ +----------+ +--------------+
| Ethernet Controller / | | Ethernet | | Connector / |
| MAC | ------ | PHY | ---- | Port | ---... to LP
+-----------------------+ +----------+ +--------------+
struct net_device struct phy_device
Commands that needs to configure the PHY will go through the net_device.phydev
field to reach the PHY and perform the relevant configuration.
This assumption falls apart in more complex topologies that can arise when,
for example, using SFP transceivers (although that's not the only specific case).
Here, we have 2 basic scenarios. Either the MAC is able to output a serialized
interface, that can directly be fed to an SFP cage, such as SGMII, 1000BaseX,
10GBaseR, etc.
The link topology then looks like this (when an SFP module is inserted) ::
+-----+ SGMII +------------+
| MAC | ------- | SFP Module |
+-----+ +------------+
Knowing that some modules embed a PHY, the actual link is more like ::
+-----+ SGMII +--------------+
| MAC | -------- | PHY (on SFP) |
+-----+ +--------------+
In this case, the SFP PHY is handled by phylib, and registered by phylink through
its SFP upstream ops.
Now some Ethernet controllers aren't able to output a serialized interface, so
we can't directly connect them to an SFP cage. However, some PHYs can be used
as media-converters, to translate the non-serialized MAC MII interface to a
serialized MII interface fed to the SFP ::
+-----+ RGMII +-----------------------+ SGMII +--------------+
| MAC | ------- | PHY (media converter) | ------- | PHY (on SFP) |
+-----+ +-----------------------+ +--------------+
This is where the model of having a single net_device.phydev pointer shows its
limitations, as we now have 2 PHYs on the link.
The phy_link topology framework aims at providing a way to keep track of every
PHY on the link, for use by both kernel drivers and subsystems, but also to
report the topology to userspace, allowing to target individual PHYs in configuration
commands.
API
===
The :c:type:`struct phy_link_topology <phy_link_topology>` is a per-netdevice
resource, that gets initialized at netdevice creation. Once it's initialized,
it is then possible to register PHYs to the topology through :
:c:func:`phy_link_topo_add_phy`
Besides registering the PHY to the topology, this call will also assign a unique
index to the PHY, which can then be reported to userspace to refer to this PHY
(akin to the ifindex). This index is a u32, ranging from 1 to U32_MAX. The value
0 is reserved to indicate the PHY doesn't belong to any topology yet.
The PHY can then be removed from the topology through
:c:func:`phy_link_topo_del_phy`
These function are already hooked into the phylib subsystem, so all PHYs that
are linked to a net_device through :c:func:`phy_attach_direct` will automatically
join the netdev's topology.
PHYs that are on a SFP module will also be automatically registered IF the SFP
upstream is phylink (so, no media-converter).
PHY drivers that can be used as SFP upstream need to call :c:func:`phy_sfp_attach_phy`
and :c:func:`phy_sfp_detach_phy`, which can be used as a
.attach_phy / .detach_phy implementation for the
:c:type:`struct sfp_upstream_ops <sfp_upstream_ops>`.
UAPI
====
There exist a set of netlink commands to query the link topology from userspace,
see ``Documentation/networking/ethtool-netlink.rst``.
The whole point of having a topology representation is to assign the phyindex
field in :c:type:`struct phy_device <phy_device>`. This index is reported to
userspace using the ``ETHTOOL_MSG_PHY_GET`` ethtnl command. Performing a DUMP operation
will result in all PHYs from all net_device being listed. The DUMP command
accepts either a ``ETHTOOL_A_HEADER_DEV_INDEX`` or ``ETHTOOL_A_HEADER_DEV_NAME``
to be passed in the request to filter the DUMP to a single net_device.
The retrieved index can then be passed as a request parameter using the
``ETHTOOL_A_HEADER_PHY_INDEX`` field in the following ethnl commands :
* ``ETHTOOL_MSG_STRSET_GET`` to get the stats string set from a given PHY
* ``ETHTOOL_MSG_CABLE_TEST_ACT`` and ``ETHTOOL_MSG_CABLE_TEST_ACT``, to perform
cable testing on a given PHY on the link (most likely the outermost PHY)
* ``ETHTOOL_MSG_PSE_SET`` and ``ETHTOOL_MSG_PSE_GET`` for PHY-controlled PoE and PSE settings
* ``ETHTOOL_MSG_PLCA_GET_CFG``, ``ETHTOOL_MSG_PLCA_SET_CFG`` and ``ETHTOOL_MSG_PLCA_GET_STATUS``
to set the PLCA (Physical Layer Collision Avoidance) parameters
Note that the PHY index can be passed to other requests, which will silently
ignore it if present and irrelevant.

View File

@ -8341,6 +8341,7 @@ F: include/linux/mii.h
F: include/linux/of_net.h
F: include/linux/phy.h
F: include/linux/phy_fixed.h
F: include/linux/phy_link_topology.h
F: include/linux/phylib_stubs.h
F: include/linux/platform_data/mdio-bcm-unimac.h
F: include/linux/platform_data/mdio-gpio.h

View File

@ -2,7 +2,7 @@
# Makefile for Linux PHY drivers
libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \
linkmode.o
linkmode.o phy_link_topology.o
mdio-bus-y += mdio_bus.o mdio_device.o
ifdef CONFIG_MDIO_DEVICE

View File

@ -553,6 +553,8 @@ static const struct sfp_upstream_ops sfp_phy_ops = {
.link_down = mv2222_sfp_link_down,
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
};
static int mv2222_probe(struct phy_device *phydev)

View File

@ -3613,6 +3613,8 @@ static const struct sfp_upstream_ops m88e1510_sfp_ops = {
.module_remove = m88e1510_sfp_remove,
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
};
static int m88e1510_probe(struct phy_device *phydev)

View File

@ -503,6 +503,8 @@ static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
static const struct sfp_upstream_ops mv3310_sfp_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
.module_insert = mv3310_sfp_insert,
};

View File

@ -29,6 +29,7 @@
#include <linux/phy.h>
#include <linux/phylib_stubs.h>
#include <linux/phy_led_triggers.h>
#include <linux/phy_link_topology.h>
#include <linux/pse-pd/pse.h>
#include <linux/property.h>
#include <linux/rtnetlink.h>
@ -1384,6 +1385,48 @@ phy_standalone_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(phy_standalone);
/**
* phy_sfp_connect_phy - Connect the SFP module's PHY to the upstream PHY
* @upstream: pointer to the upstream phy device
* @phy: pointer to the SFP module's phy device
*
* This helper allows keeping track of PHY devices on the link. It adds the
* SFP module's phy to the phy namespace of the upstream phy
*
* Return: 0 on success, otherwise a negative error code.
*/
int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
{
struct phy_device *phydev = upstream;
struct net_device *dev = phydev->attached_dev;
if (dev)
return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev);
return 0;
}
EXPORT_SYMBOL(phy_sfp_connect_phy);
/**
* phy_sfp_disconnect_phy - Disconnect the SFP module's PHY from the upstream PHY
* @upstream: pointer to the upstream phy device
* @phy: pointer to the SFP module's phy device
*
* This helper allows keeping track of PHY devices on the link. It removes the
* SFP module's phy to the phy namespace of the upstream phy. As the module phy
* will be destroyed, re-inserting the same module will add a new phy with a
* new index.
*/
void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)
{
struct phy_device *phydev = upstream;
struct net_device *dev = phydev->attached_dev;
if (dev)
phy_link_topo_del_phy(dev, phy);
}
EXPORT_SYMBOL(phy_sfp_disconnect_phy);
/**
* phy_sfp_attach - attach the SFP bus to the PHY upstream network device
* @upstream: pointer to the phy device
@ -1526,6 +1569,10 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
if (phydev->sfp_bus_attached)
dev->sfp_bus = phydev->sfp_bus;
err = phy_link_topo_add_phy(dev, phydev, PHY_UPSTREAM_MAC, dev);
if (err)
goto error;
}
/* Some Ethernet drivers try to connect to a PHY device before
@ -1953,6 +2000,7 @@ void phy_detach(struct phy_device *phydev)
if (dev) {
phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
phy_link_topo_del_phy(dev, phydev);
}
phydev->phylink = NULL;

View File

@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Infrastructure to handle all PHY devices connected to a given netdev,
* either directly or indirectly attached.
*
* Copyright (c) 2023 Maxime Chevallier<maxime.chevallier@bootlin.com>
*/
#include <linux/phy_link_topology.h>
#include <linux/phy.h>
#include <linux/rtnetlink.h>
#include <linux/xarray.h>
static int netdev_alloc_phy_link_topology(struct net_device *dev)
{
struct phy_link_topology *topo;
topo = kzalloc(sizeof(*topo), GFP_KERNEL);
if (!topo)
return -ENOMEM;
xa_init_flags(&topo->phys, XA_FLAGS_ALLOC1);
topo->next_phy_index = 1;
dev->link_topo = topo;
return 0;
}
int phy_link_topo_add_phy(struct net_device *dev,
struct phy_device *phy,
enum phy_upstream upt, void *upstream)
{
struct phy_link_topology *topo = dev->link_topo;
struct phy_device_node *pdn;
int ret;
if (!topo) {
ret = netdev_alloc_phy_link_topology(dev);
if (ret)
return ret;
topo = dev->link_topo;
}
pdn = kzalloc(sizeof(*pdn), GFP_KERNEL);
if (!pdn)
return -ENOMEM;
pdn->phy = phy;
switch (upt) {
case PHY_UPSTREAM_MAC:
pdn->upstream.netdev = (struct net_device *)upstream;
if (phy_on_sfp(phy))
pdn->parent_sfp_bus = pdn->upstream.netdev->sfp_bus;
break;
case PHY_UPSTREAM_PHY:
pdn->upstream.phydev = (struct phy_device *)upstream;
if (phy_on_sfp(phy))
pdn->parent_sfp_bus = pdn->upstream.phydev->sfp_bus;
break;
default:
ret = -EINVAL;
goto err;
}
pdn->upstream_type = upt;
/* Attempt to re-use a previously allocated phy_index */
if (phy->phyindex)
ret = xa_insert(&topo->phys, phy->phyindex, pdn, GFP_KERNEL);
else
ret = xa_alloc_cyclic(&topo->phys, &phy->phyindex, pdn,
xa_limit_32b, &topo->next_phy_index,
GFP_KERNEL);
if (ret)
goto err;
return 0;
err:
kfree(pdn);
return ret;
}
EXPORT_SYMBOL_GPL(phy_link_topo_add_phy);
void phy_link_topo_del_phy(struct net_device *dev,
struct phy_device *phy)
{
struct phy_link_topology *topo = dev->link_topo;
struct phy_device_node *pdn;
if (!topo)
return;
pdn = xa_erase(&topo->phys, phy->phyindex);
/* We delete the PHY from the topology, however we don't re-set the
* phy->phyindex field. If the PHY isn't gone, we can re-assign it the
* same index next time it's added back to the topology
*/
kfree(pdn);
}
EXPORT_SYMBOL_GPL(phy_link_topo_del_phy);

View File

@ -3423,7 +3423,8 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
return ret;
}
static void phylink_sfp_disconnect_phy(void *upstream)
static void phylink_sfp_disconnect_phy(void *upstream,
struct phy_device *phydev)
{
phylink_disconnect_phy(upstream);
}

View File

@ -770,6 +770,8 @@ static const struct sfp_upstream_ops at8031_sfp_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.module_insert = at8031_sfp_insert,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
};
static int at8031_parse_dt(struct phy_device *phydev)

View File

@ -699,6 +699,8 @@ static const struct sfp_upstream_ops qca807x_sfp_ops = {
.detach = phy_sfp_detach,
.module_insert = qca807x_sfp_insert,
.module_remove = qca807x_sfp_remove,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
};
static int qca807x_probe(struct phy_device *phydev)

View File

@ -487,7 +487,7 @@ static void sfp_unregister_bus(struct sfp_bus *bus)
bus->socket_ops->stop(bus->sfp);
bus->socket_ops->detach(bus->sfp);
if (bus->phydev && ops && ops->disconnect_phy)
ops->disconnect_phy(bus->upstream);
ops->disconnect_phy(bus->upstream, bus->phydev);
}
bus->registered = false;
}
@ -722,6 +722,28 @@ void sfp_bus_del_upstream(struct sfp_bus *bus)
}
EXPORT_SYMBOL_GPL(sfp_bus_del_upstream);
/**
* sfp_get_name() - Get the SFP device name
* @bus: a pointer to the &struct sfp_bus structure for the sfp module
*
* Gets the SFP device's name, if @bus has a registered socket. Callers must
* hold RTNL, and the returned name is only valid until RTNL is released.
*
* Returns:
* - The name of the SFP device registered with sfp_register_socket()
* - %NULL if no device was registered on @bus
*/
const char *sfp_get_name(struct sfp_bus *bus)
{
ASSERT_RTNL();
if (bus->sfp_dev)
return dev_name(bus->sfp_dev);
return NULL;
}
EXPORT_SYMBOL_GPL(sfp_get_name);
/* Socket driver entry points */
int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev)
{
@ -743,7 +765,7 @@ void sfp_remove_phy(struct sfp_bus *bus)
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
if (ops && ops->disconnect_phy)
ops->disconnect_phy(bus->upstream);
ops->disconnect_phy(bus->upstream, bus->phydev);
bus->phydev = NULL;
}
EXPORT_SYMBOL_GPL(sfp_remove_phy);

View File

@ -40,7 +40,6 @@
#include <net/dcbnl.h>
#endif
#include <net/netprio_cgroup.h>
#include <linux/netdev_features.h>
#include <linux/neighbour.h>
#include <linux/netdevice_xmit.h>
@ -81,6 +80,7 @@ struct xdp_frame;
struct xdp_metadata_ops;
struct xdp_md;
struct ethtool_netdev_state;
struct phy_link_topology;
typedef u32 xdp_features_t;
@ -1951,6 +1951,7 @@ enum netdev_reg_state {
* @fcoe_ddp_xid: Max exchange id for FCoE LRO by ddp
*
* @priomap: XXX: need comments on this one
* @link_topo: Physical link topology tracking attached PHYs
* @phydev: Physical device may attach itself
* for hardware timestamping
* @sfp_bus: attached &struct sfp_bus structure.
@ -2342,6 +2343,7 @@ struct net_device {
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
struct netprio_map __rcu *priomap;
#endif
struct phy_link_topology *link_topo;
struct phy_device *phydev;
struct sfp_bus *sfp_bus;
struct lock_class_key *qdisc_tx_busylock;

View File

@ -554,6 +554,9 @@ struct macsec_ops;
* @drv: Pointer to the driver for this PHY instance
* @devlink: Create a link between phy dev and mac dev, if the external phy
* used by current mac interface is managed by another mac interface.
* @phyindex: Unique id across the phy's parent tree of phys to address the PHY
* from userspace, similar to ifindex. A zero index means the PHY
* wasn't assigned an id yet.
* @phy_id: UID for this device found during discovery
* @c45_ids: 802.3-c45 Device Identifiers if is_c45.
* @is_c45: Set to true if this PHY uses clause 45 addressing.
@ -656,6 +659,7 @@ struct phy_device {
struct device_link *devlink;
u32 phyindex;
u32 phy_id;
struct phy_c45_device_ids c45_ids;
@ -1777,6 +1781,8 @@ int phy_suspend(struct phy_device *phydev);
int phy_resume(struct phy_device *phydev);
int __phy_resume(struct phy_device *phydev);
int phy_loopback(struct phy_device *phydev, bool enable);
int phy_sfp_connect_phy(void *upstream, struct phy_device *phy);
void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy);
void phy_sfp_attach(void *upstream, struct sfp_bus *bus);
void phy_sfp_detach(void *upstream, struct sfp_bus *bus);
int phy_sfp_probe(struct phy_device *phydev,

View File

@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PHY device list allow maintaining a list of PHY devices that are
* part of a netdevice's link topology. PHYs can for example be chained,
* as is the case when using a PHY that exposes an SFP module, on which an
* SFP transceiver that embeds a PHY is connected.
*
* This list can then be used by userspace to leverage individual PHY
* capabilities.
*/
#ifndef __PHY_LINK_TOPOLOGY_H
#define __PHY_LINK_TOPOLOGY_H
#include <linux/ethtool.h>
#include <linux/netdevice.h>
struct xarray;
struct phy_device;
struct sfp_bus;
struct phy_link_topology {
struct xarray phys;
u32 next_phy_index;
};
struct phy_device_node {
enum phy_upstream upstream_type;
union {
struct net_device *netdev;
struct phy_device *phydev;
} upstream;
struct sfp_bus *parent_sfp_bus;
struct phy_device *phy;
};
#if IS_ENABLED(CONFIG_PHYLIB)
int phy_link_topo_add_phy(struct net_device *dev,
struct phy_device *phy,
enum phy_upstream upt, void *upstream);
void phy_link_topo_del_phy(struct net_device *dev, struct phy_device *phy);
static inline struct phy_device *
phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)
{
struct phy_link_topology *topo = dev->link_topo;
struct phy_device_node *pdn;
if (!topo)
return NULL;
pdn = xa_load(&topo->phys, phyindex);
if (pdn)
return pdn->phy;
return NULL;
}
#else
static inline int phy_link_topo_add_phy(struct net_device *dev,
struct phy_device *phy,
enum phy_upstream upt, void *upstream)
{
return 0;
}
static inline void phy_link_topo_del_phy(struct net_device *dev,
struct phy_device *phy)
{
}
static inline struct phy_device *
phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)
{
return NULL;
}
#endif
#endif /* __PHY_LINK_TOPOLOGY_H */

View File

@ -550,7 +550,7 @@ struct sfp_upstream_ops {
void (*link_down)(void *priv);
void (*link_up)(void *priv);
int (*connect_phy)(void *priv, struct phy_device *);
void (*disconnect_phy)(void *priv);
void (*disconnect_phy)(void *priv, struct phy_device *);
};
#if IS_ENABLED(CONFIG_SFP)
@ -576,6 +576,7 @@ struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode);
int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
const struct sfp_upstream_ops *ops);
void sfp_bus_del_upstream(struct sfp_bus *bus);
const char *sfp_get_name(struct sfp_bus *bus);
#else
static inline int sfp_parse_port(struct sfp_bus *bus,
const struct sfp_eeprom_id *id,
@ -654,6 +655,11 @@ static inline int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
static inline void sfp_bus_del_upstream(struct sfp_bus *bus)
{
}
static inline const char *sfp_get_name(struct sfp_bus *bus)
{
return NULL;
}
#endif
#endif

View File

@ -2533,4 +2533,20 @@ struct ethtool_link_settings {
* __u32 map_lp_advertising[link_mode_masks_nwords];
*/
};
/**
* enum phy_upstream - Represents the upstream component a given PHY device
* is connected to, as in what is on the other end of the MII bus. Most PHYs
* will be attached to an Ethernet MAC controller, but in some cases, there's
* an intermediate PHY used as a media-converter, which will driver another
* MII interface as its output.
* @PHY_UPSTREAM_MAC: Upstream component is a MAC (a switch port,
* or ethernet controller)
* @PHY_UPSTREAM_PHY: Upstream component is a PHY (likely a media converter)
*/
enum phy_upstream {
PHY_UPSTREAM_MAC,
PHY_UPSTREAM_PHY,
};
#endif /* _UAPI_LINUX_ETHTOOL_H */

View File

@ -58,6 +58,7 @@ enum {
ETHTOOL_MSG_MM_GET,
ETHTOOL_MSG_MM_SET,
ETHTOOL_MSG_MODULE_FW_FLASH_ACT,
ETHTOOL_MSG_PHY_GET,
/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
@ -111,6 +112,8 @@ enum {
ETHTOOL_MSG_MM_GET_REPLY,
ETHTOOL_MSG_MM_NTF,
ETHTOOL_MSG_MODULE_FW_FLASH_NTF,
ETHTOOL_MSG_PHY_GET_REPLY,
ETHTOOL_MSG_PHY_NTF,
/* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT,
@ -134,6 +137,7 @@ enum {
ETHTOOL_A_HEADER_DEV_INDEX, /* u32 */
ETHTOOL_A_HEADER_DEV_NAME, /* string */
ETHTOOL_A_HEADER_FLAGS, /* u32 - ETHTOOL_FLAG_* */
ETHTOOL_A_HEADER_PHY_INDEX, /* u32 */
/* add new constants above here */
__ETHTOOL_A_HEADER_CNT,
@ -1054,6 +1058,22 @@ enum {
ETHTOOL_A_MODULE_FW_FLASH_MAX = (__ETHTOOL_A_MODULE_FW_FLASH_CNT - 1)
};
enum {
ETHTOOL_A_PHY_UNSPEC,
ETHTOOL_A_PHY_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_PHY_INDEX, /* u32 */
ETHTOOL_A_PHY_DRVNAME, /* string */
ETHTOOL_A_PHY_NAME, /* string */
ETHTOOL_A_PHY_UPSTREAM_TYPE, /* u32 */
ETHTOOL_A_PHY_UPSTREAM_INDEX, /* u32 */
ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, /* string */
ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, /* string */
/* add new constants above here */
__ETHTOOL_A_PHY_CNT,
ETHTOOL_A_PHY_MAX = (__ETHTOOL_A_PHY_CNT - 1)
};
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1

View File

@ -158,6 +158,7 @@
#include <net/page_pool/types.h>
#include <net/page_pool/helpers.h>
#include <net/rps.h>
#include <linux/phy_link_topology.h>
#include "dev.h"
#include "net-sysfs.h"
@ -10321,6 +10322,17 @@ static void netdev_do_free_pcpu_stats(struct net_device *dev)
}
}
static void netdev_free_phy_link_topology(struct net_device *dev)
{
struct phy_link_topology *topo = dev->link_topo;
if (IS_ENABLED(CONFIG_PHYLIB) && topo) {
xa_destroy(&topo->phys);
kfree(topo);
dev->link_topo = NULL;
}
}
/**
* register_netdevice() - register a network device
* @dev: device to register
@ -11099,6 +11111,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
#ifdef CONFIG_NET_SCHED
hash_init(dev->qdisc_hash);
#endif
dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM;
setup(dev);
@ -11191,6 +11204,8 @@ void free_netdev(struct net_device *dev)
free_percpu(dev->xdp_bulkq);
dev->xdp_bulkq = NULL;
netdev_free_phy_link_topology(dev);
/* Compatibility with error handling in drivers */
if (dev->reg_state == NETREG_UNINITIALIZED ||
dev->reg_state == NETREG_DUMMY) {

View File

@ -8,4 +8,5 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.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 mm.o \
module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o
module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o \
phy.o

View File

@ -13,7 +13,7 @@
const struct nla_policy ethnl_cable_test_act_policy[] = {
[ETHTOOL_A_CABLE_TEST_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
NLA_POLICY_NESTED(ethnl_header_policy_phy),
};
static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
@ -58,6 +58,7 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
struct ethnl_req_info req_info = {};
const struct ethtool_phy_ops *ops;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
struct net_device *dev;
int ret;
@ -69,12 +70,16 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
return ret;
dev = req_info.dev;
if (!dev->phydev) {
rtnl_lock();
phydev = ethnl_req_get_phydev(&req_info,
tb[ETHTOOL_A_CABLE_TEST_HEADER],
info->extack);
if (IS_ERR_OR_NULL(phydev)) {
ret = -EOPNOTSUPP;
goto out_dev_put;
}
rtnl_lock();
ops = ethtool_phy_ops;
if (!ops || !ops->start_cable_test) {
ret = -EOPNOTSUPP;
@ -85,13 +90,12 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
if (ret < 0)
goto out_rtnl;
ret = ops->start_cable_test(dev->phydev, info->extack);
ret = ops->start_cable_test(phydev, info->extack);
ethnl_ops_complete(dev);
if (!ret)
ethnl_cable_test_started(dev->phydev,
ETHTOOL_MSG_CABLE_TEST_NTF);
ethnl_cable_test_started(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);
out_rtnl:
rtnl_unlock();
@ -216,7 +220,7 @@ static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
const struct nla_policy ethnl_cable_test_tdr_act_policy[] = {
[ETHTOOL_A_CABLE_TEST_TDR_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
NLA_POLICY_NESTED(ethnl_header_policy_phy),
[ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED },
};
@ -305,6 +309,7 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
struct ethnl_req_info req_info = {};
const struct ethtool_phy_ops *ops;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
struct phy_tdr_config cfg;
struct net_device *dev;
int ret;
@ -317,10 +322,6 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
return ret;
dev = req_info.dev;
if (!dev->phydev) {
ret = -EOPNOTSUPP;
goto out_dev_put;
}
ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
info, &cfg);
@ -328,6 +329,14 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
goto out_dev_put;
rtnl_lock();
phydev = ethnl_req_get_phydev(&req_info,
tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
info->extack);
if (!IS_ERR_OR_NULL(phydev)) {
ret = -EOPNOTSUPP;
goto out_dev_put;
}
ops = ethtool_phy_ops;
if (!ops || !ops->start_cable_test_tdr) {
ret = -EOPNOTSUPP;
@ -338,12 +347,12 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
if (ret < 0)
goto out_rtnl;
ret = ops->start_cable_test_tdr(dev->phydev, info->extack, &cfg);
ret = ops->start_cable_test_tdr(phydev, info->extack, &cfg);
ethnl_ops_complete(dev);
if (!ret)
ethnl_cable_test_started(dev->phydev,
ethnl_cable_test_started(phydev,
ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
out_rtnl:

View File

@ -2,6 +2,7 @@
#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include <linux/phy_link_topology.h>
#include <linux/pm_runtime.h>
#include "netlink.h"
#include "module_fw.h"
@ -31,6 +32,24 @@ const struct nla_policy ethnl_header_policy_stats[] = {
ETHTOOL_FLAGS_STATS),
};
const struct nla_policy ethnl_header_policy_phy[] = {
[ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
[ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
.len = ALTIFNAMSIZ - 1 },
[ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32,
ETHTOOL_FLAGS_BASIC),
[ETHTOOL_A_HEADER_PHY_INDEX] = NLA_POLICY_MIN(NLA_U32, 1),
};
const struct nla_policy ethnl_header_policy_phy_stats[] = {
[ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
[ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
.len = ALTIFNAMSIZ - 1 },
[ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32,
ETHTOOL_FLAGS_STATS),
[ETHTOOL_A_HEADER_PHY_INDEX] = NLA_POLICY_MIN(NLA_U32, 1),
};
int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
enum ethnl_sock_type type)
{
@ -119,7 +138,7 @@ int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *header, struct net *net,
struct netlink_ext_ack *extack, bool require_dev)
{
struct nlattr *tb[ARRAY_SIZE(ethnl_header_policy)];
struct nlattr *tb[ARRAY_SIZE(ethnl_header_policy_phy)];
const struct nlattr *devname_attr;
struct net_device *dev = NULL;
u32 flags = 0;
@ -134,7 +153,7 @@ int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
/* No validation here, command policy should have a nested policy set
* for the header, therefore validation should have already been done.
*/
ret = nla_parse_nested(tb, ARRAY_SIZE(ethnl_header_policy) - 1, header,
ret = nla_parse_nested(tb, ARRAY_SIZE(ethnl_header_policy_phy) - 1, header,
NULL, extack);
if (ret < 0)
return ret;
@ -175,11 +194,45 @@ int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
return -EINVAL;
}
if (tb[ETHTOOL_A_HEADER_PHY_INDEX]) {
if (dev) {
req_info->phy_index = nla_get_u32(tb[ETHTOOL_A_HEADER_PHY_INDEX]);
} else {
NL_SET_ERR_MSG_ATTR(extack, header,
"phy_index set without a netdev");
return -EINVAL;
}
}
req_info->dev = dev;
req_info->flags = flags;
return 0;
}
struct phy_device *ethnl_req_get_phydev(const struct ethnl_req_info *req_info,
const struct nlattr *header,
struct netlink_ext_ack *extack)
{
struct phy_device *phydev;
ASSERT_RTNL();
if (!req_info->dev)
return NULL;
if (!req_info->phy_index)
return req_info->dev->phydev;
phydev = phy_link_topo_get_phy(req_info->dev, req_info->phy_index);
if (!phydev) {
NL_SET_ERR_MSG_ATTR(extack, header,
"no phy matching phyindex");
return ERR_PTR(-ENODEV);
}
return phydev;
}
/**
* ethnl_fill_reply_header() - Put common header into a reply message
* @skb: skb with the message
@ -1181,6 +1234,15 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_module_fw_flash_act_policy,
.maxattr = ARRAY_SIZE(ethnl_module_fw_flash_act_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_PHY_GET,
.doit = ethnl_phy_doit,
.start = ethnl_phy_start,
.dumpit = ethnl_phy_dumpit,
.done = ethnl_phy_done,
.policy = ethnl_phy_get_policy,
.maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {

View File

@ -251,6 +251,9 @@ static inline unsigned int ethnl_reply_header_size(void)
* @dev: network device the request is for (may be null)
* @dev_tracker: refcount tracker for @dev reference
* @flags: request flags common for all request types
* @phy_index: phy_device index connected to @dev this request is for. Can be
* 0 if the request doesn't target a phy, or if the @dev's attached
* phy is targeted.
*
* This is a common base for request specific structures holding data from
* parsed userspace request. These always embed struct ethnl_req_info at
@ -260,6 +263,7 @@ struct ethnl_req_info {
struct net_device *dev;
netdevice_tracker dev_tracker;
u32 flags;
u32 phy_index;
};
static inline void ethnl_parse_header_dev_put(struct ethnl_req_info *req_info)
@ -267,6 +271,27 @@ static inline void ethnl_parse_header_dev_put(struct ethnl_req_info *req_info)
netdev_put(req_info->dev, &req_info->dev_tracker);
}
/**
* ethnl_req_get_phydev() - Gets the phy_device targeted by this request,
* if any. Must be called under rntl_lock().
* @req_info: The ethnl request to get the phy from.
* @header: The netlink header, used for error reporting.
* @extack: The netlink extended ACK, for error reporting.
*
* The caller must hold RTNL, until it's done interacting with the returned
* phy_device.
*
* Return: A phy_device pointer corresponding either to the passed phy_index
* if one is provided. If not, the phy_device attached to the
* net_device targeted by this request is returned. If there's no
* targeted net_device, or no phy_device is attached, NULL is
* returned. If the provided phy_index is invalid, an error pointer
* is returned.
*/
struct phy_device *ethnl_req_get_phydev(const struct ethnl_req_info *req_info,
const struct nlattr *header,
struct netlink_ext_ack *extack);
/**
* struct ethnl_reply_data - base type of reply data for GET requests
* @dev: device for current reply message; in single shot requests it is
@ -409,9 +434,12 @@ extern const struct ethnl_request_ops ethnl_rss_request_ops;
extern const struct ethnl_request_ops ethnl_plca_cfg_request_ops;
extern const struct ethnl_request_ops ethnl_plca_status_request_ops;
extern const struct ethnl_request_ops ethnl_mm_request_ops;
extern const struct ethnl_request_ops ethnl_phy_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];
extern const struct nla_policy ethnl_header_policy_phy[ETHTOOL_A_HEADER_PHY_INDEX + 1];
extern const struct nla_policy ethnl_header_policy_phy_stats[ETHTOOL_A_HEADER_PHY_INDEX + 1];
extern const struct nla_policy ethnl_strset_get_policy[ETHTOOL_A_STRSET_COUNTS_ONLY + 1];
extern const struct nla_policy ethnl_linkinfo_get_policy[ETHTOOL_A_LINKINFO_HEADER + 1];
extern const struct nla_policy ethnl_linkinfo_set_policy[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL + 1];
@ -456,6 +484,7 @@ extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADE
extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1];
extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1];
extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1];
extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1];
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);
@ -466,6 +495,10 @@ int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info);
int ethnl_rss_dump_start(struct netlink_callback *cb);
int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_phy_start(struct netlink_callback *cb);
int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_phy_done(struct netlink_callback *cb);
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];

308
net/ethtool/phy.c Normal file
View File

@ -0,0 +1,308 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Bootlin
*
*/
#include "common.h"
#include "netlink.h"
#include <linux/phy.h>
#include <linux/phy_link_topology.h>
#include <linux/sfp.h>
struct phy_req_info {
struct ethnl_req_info base;
struct phy_device_node *pdn;
};
#define PHY_REQINFO(__req_base) \
container_of(__req_base, struct phy_req_info, base)
const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1] = {
[ETHTOOL_A_PHY_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
};
/* Caller holds rtnl */
static ssize_t
ethnl_phy_reply_size(const struct ethnl_req_info *req_base,
struct netlink_ext_ack *extack)
{
struct phy_req_info *req_info = PHY_REQINFO(req_base);
struct phy_device_node *pdn = req_info->pdn;
struct phy_device *phydev = pdn->phy;
size_t size = 0;
ASSERT_RTNL();
/* ETHTOOL_A_PHY_INDEX */
size += nla_total_size(sizeof(u32));
/* ETHTOOL_A_DRVNAME */
if (phydev->drv)
size += nla_total_size(strlen(phydev->drv->name) + 1);
/* ETHTOOL_A_NAME */
size += nla_total_size(strlen(dev_name(&phydev->mdio.dev)) + 1);
/* ETHTOOL_A_PHY_UPSTREAM_TYPE */
size += nla_total_size(sizeof(u32));
if (phy_on_sfp(phydev)) {
const char *upstream_sfp_name = sfp_get_name(pdn->parent_sfp_bus);
/* ETHTOOL_A_PHY_UPSTREAM_SFP_NAME */
if (upstream_sfp_name)
size += nla_total_size(strlen(upstream_sfp_name) + 1);
/* ETHTOOL_A_PHY_UPSTREAM_INDEX */
size += nla_total_size(sizeof(u32));
}
/* ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME */
if (phydev->sfp_bus) {
const char *sfp_name = sfp_get_name(phydev->sfp_bus);
if (sfp_name)
size += nla_total_size(strlen(sfp_name) + 1);
}
return size;
}
static int
ethnl_phy_fill_reply(const struct ethnl_req_info *req_base, struct sk_buff *skb)
{
struct phy_req_info *req_info = PHY_REQINFO(req_base);
struct phy_device_node *pdn = req_info->pdn;
struct phy_device *phydev = pdn->phy;
enum phy_upstream ptype;
ptype = pdn->upstream_type;
if (nla_put_u32(skb, ETHTOOL_A_PHY_INDEX, phydev->phyindex) ||
nla_put_string(skb, ETHTOOL_A_PHY_NAME, dev_name(&phydev->mdio.dev)) ||
nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_TYPE, ptype))
return -EMSGSIZE;
if (phydev->drv &&
nla_put_string(skb, ETHTOOL_A_PHY_DRVNAME, phydev->drv->name))
return -EMSGSIZE;
if (ptype == PHY_UPSTREAM_PHY) {
struct phy_device *upstream = pdn->upstream.phydev;
const char *sfp_upstream_name;
/* Parent index */
if (nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_INDEX, upstream->phyindex))
return -EMSGSIZE;
if (pdn->parent_sfp_bus) {
sfp_upstream_name = sfp_get_name(pdn->parent_sfp_bus);
if (sfp_upstream_name &&
nla_put_string(skb, ETHTOOL_A_PHY_UPSTREAM_SFP_NAME,
sfp_upstream_name))
return -EMSGSIZE;
}
}
if (phydev->sfp_bus) {
const char *sfp_name = sfp_get_name(phydev->sfp_bus);
if (sfp_name &&
nla_put_string(skb, ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME,
sfp_name))
return -EMSGSIZE;
}
return 0;
}
static int ethnl_phy_parse_request(struct ethnl_req_info *req_base,
struct nlattr **tb,
struct netlink_ext_ack *extack)
{
struct phy_link_topology *topo = req_base->dev->link_topo;
struct phy_req_info *req_info = PHY_REQINFO(req_base);
struct phy_device *phydev;
phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_PHY_HEADER],
extack);
if (!phydev)
return 0;
if (IS_ERR(phydev))
return PTR_ERR(phydev);
if (!topo)
return 0;
req_info->pdn = xa_load(&topo->phys, phydev->phyindex);
return 0;
}
int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info)
{
struct phy_req_info req_info = {};
struct nlattr **tb = info->attrs;
struct sk_buff *rskb;
void *reply_payload;
int reply_len;
int ret;
ret = ethnl_parse_header_dev_get(&req_info.base,
tb[ETHTOOL_A_PHY_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
rtnl_lock();
ret = ethnl_phy_parse_request(&req_info.base, tb, info->extack);
if (ret < 0)
goto err_unlock_rtnl;
/* No PHY, return early */
if (!req_info.pdn->phy)
goto err_unlock_rtnl;
ret = ethnl_phy_reply_size(&req_info.base, info->extack);
if (ret < 0)
goto err_unlock_rtnl;
reply_len = ret + ethnl_reply_header_size();
rskb = ethnl_reply_init(reply_len, req_info.base.dev,
ETHTOOL_MSG_PHY_GET_REPLY,
ETHTOOL_A_PHY_HEADER,
info, &reply_payload);
if (!rskb) {
ret = -ENOMEM;
goto err_unlock_rtnl;
}
ret = ethnl_phy_fill_reply(&req_info.base, rskb);
if (ret)
goto err_free_msg;
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info.base);
genlmsg_end(rskb, reply_payload);
return genlmsg_reply(rskb, info);
err_free_msg:
nlmsg_free(rskb);
err_unlock_rtnl:
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info.base);
return ret;
}
struct ethnl_phy_dump_ctx {
struct phy_req_info *phy_req_info;
unsigned long ifindex;
unsigned long phy_index;
};
int ethnl_phy_start(struct netlink_callback *cb)
{
const struct genl_info *info = genl_info_dump(cb);
struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx;
int ret;
BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
ctx->phy_req_info = kzalloc(sizeof(*ctx->phy_req_info), GFP_KERNEL);
if (!ctx->phy_req_info)
return -ENOMEM;
ret = ethnl_parse_header_dev_get(&ctx->phy_req_info->base,
info->attrs[ETHTOOL_A_PHY_HEADER],
sock_net(cb->skb->sk), cb->extack,
false);
ctx->ifindex = 0;
ctx->phy_index = 0;
if (ret)
kfree(ctx->phy_req_info);
return ret;
}
int ethnl_phy_done(struct netlink_callback *cb)
{
struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx;
if (ctx->phy_req_info->base.dev)
ethnl_parse_header_dev_put(&ctx->phy_req_info->base);
kfree(ctx->phy_req_info);
return 0;
}
static int ethnl_phy_dump_one_dev(struct sk_buff *skb, struct net_device *dev,
struct netlink_callback *cb)
{
struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx;
struct phy_req_info *pri = ctx->phy_req_info;
struct phy_device_node *pdn;
int ret = 0;
void *ehdr;
pri->base.dev = dev;
if (!dev->link_topo)
return 0;
xa_for_each_start(&dev->link_topo->phys, ctx->phy_index, pdn, ctx->phy_index) {
ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_PHY_GET_REPLY);
if (!ehdr) {
ret = -EMSGSIZE;
break;
}
ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_PHY_HEADER);
if (ret < 0) {
genlmsg_cancel(skb, ehdr);
break;
}
pri->pdn = pdn;
ret = ethnl_phy_fill_reply(&pri->base, skb);
if (ret < 0) {
genlmsg_cancel(skb, ehdr);
break;
}
genlmsg_end(skb, ehdr);
}
return ret;
}
int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx;
struct net *net = sock_net(skb->sk);
struct net_device *dev;
int ret = 0;
rtnl_lock();
if (ctx->phy_req_info->base.dev) {
ret = ethnl_phy_dump_one_dev(skb, ctx->phy_req_info->base.dev, cb);
} else {
for_each_netdev_dump(net, dev, ctx->ifindex) {
ret = ethnl_phy_dump_one_dev(skb, dev, cb);
if (ret)
break;
ctx->phy_index = 0;
}
}
rtnl_unlock();
return ret;
}

View File

@ -25,7 +25,7 @@ struct plca_reply_data {
const struct nla_policy ethnl_plca_get_cfg_policy[] = {
[ETHTOOL_A_PLCA_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
NLA_POLICY_NESTED(ethnl_header_policy_phy),
};
static void plca_update_sint(int *dst, struct nlattr **tb, u32 attrid,
@ -58,10 +58,14 @@ static int plca_get_cfg_prepare_data(const struct ethnl_req_info *req_base,
struct plca_reply_data *data = PLCA_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
const struct ethtool_phy_ops *ops;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
int ret;
phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_PLCA_HEADER],
info->extack);
// check that the PHY device is available and connected
if (!dev->phydev) {
if (IS_ERR_OR_NULL(phydev)) {
ret = -EOPNOTSUPP;
goto out;
}
@ -80,7 +84,7 @@ static int plca_get_cfg_prepare_data(const struct ethnl_req_info *req_base,
memset(&data->plca_cfg, 0xff,
sizeof_field(struct plca_reply_data, plca_cfg));
ret = ops->get_plca_cfg(dev->phydev, &data->plca_cfg);
ret = ops->get_plca_cfg(phydev, &data->plca_cfg);
ethnl_ops_complete(dev);
out:
@ -129,7 +133,7 @@ static int plca_get_cfg_fill_reply(struct sk_buff *skb,
const struct nla_policy ethnl_plca_set_cfg_policy[] = {
[ETHTOOL_A_PLCA_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
NLA_POLICY_NESTED(ethnl_header_policy_phy),
[ETHTOOL_A_PLCA_ENABLED] = NLA_POLICY_MAX(NLA_U8, 1),
[ETHTOOL_A_PLCA_NODE_ID] = NLA_POLICY_MAX(NLA_U32, 255),
[ETHTOOL_A_PLCA_NODE_CNT] = NLA_POLICY_RANGE(NLA_U32, 1, 255),
@ -141,15 +145,17 @@ const struct nla_policy ethnl_plca_set_cfg_policy[] = {
static int
ethnl_set_plca(struct ethnl_req_info *req_info, struct genl_info *info)
{
struct net_device *dev = req_info->dev;
const struct ethtool_phy_ops *ops;
struct nlattr **tb = info->attrs;
struct phy_plca_cfg plca_cfg;
struct phy_device *phydev;
bool mod = false;
int ret;
phydev = ethnl_req_get_phydev(req_info, tb[ETHTOOL_A_PLCA_HEADER],
info->extack);
// check that the PHY device is available and connected
if (!dev->phydev)
if (IS_ERR_OR_NULL(phydev))
return -EOPNOTSUPP;
ops = ethtool_phy_ops;
@ -168,7 +174,7 @@ ethnl_set_plca(struct ethnl_req_info *req_info, struct genl_info *info)
if (!mod)
return 0;
ret = ops->set_plca_cfg(dev->phydev, &plca_cfg, info->extack);
ret = ops->set_plca_cfg(phydev, &plca_cfg, info->extack);
return ret < 0 ? ret : 1;
}
@ -191,7 +197,7 @@ const struct ethnl_request_ops ethnl_plca_cfg_request_ops = {
const struct nla_policy ethnl_plca_get_status_policy[] = {
[ETHTOOL_A_PLCA_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
NLA_POLICY_NESTED(ethnl_header_policy_phy),
};
static int plca_get_status_prepare_data(const struct ethnl_req_info *req_base,
@ -201,10 +207,14 @@ static int plca_get_status_prepare_data(const struct ethnl_req_info *req_base,
struct plca_reply_data *data = PLCA_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
const struct ethtool_phy_ops *ops;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
int ret;
phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_PLCA_HEADER],
info->extack);
// check that the PHY device is available and connected
if (!dev->phydev) {
if (IS_ERR_OR_NULL(phydev)) {
ret = -EOPNOTSUPP;
goto out;
}
@ -223,7 +233,7 @@ static int plca_get_status_prepare_data(const struct ethnl_req_info *req_base,
memset(&data->plca_st, 0xff,
sizeof_field(struct plca_reply_data, plca_st));
ret = ops->get_plca_status(dev->phydev, &data->plca_st);
ret = ops->get_plca_status(phydev, &data->plca_st);
ethnl_ops_complete(dev);
out:
return ret;

View File

@ -28,17 +28,15 @@ struct pse_reply_data {
/* 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),
[ETHTOOL_A_PSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy),
};
static int pse_get_pse_attributes(struct net_device *dev,
static int pse_get_pse_attributes(struct phy_device *phydev,
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");
NL_SET_ERR_MSG(extack, "No PHY found");
return -EOPNOTSUPP;
}
@ -58,13 +56,20 @@ static int pse_prepare_data(const struct ethnl_req_info *req_base,
{
struct pse_reply_data *data = PSE_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
int ret;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
ret = pse_get_pse_attributes(dev, info->extack, data);
phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_PSE_HEADER],
info->extack);
if (IS_ERR(phydev))
return -ENODEV;
ret = pse_get_pse_attributes(phydev, info->extack, data);
ethnl_ops_complete(dev);
@ -206,7 +211,7 @@ static void pse_cleanup_data(struct ethnl_reply_data *reply_base)
/* 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_PSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy),
[ETHTOOL_A_PODL_PSE_ADMIN_CONTROL] =
NLA_POLICY_RANGE(NLA_U32, ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED,
ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED),
@ -219,12 +224,12 @@ const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1] = {
static int
ethnl_set_pse_validate(struct ethnl_req_info *req_info, struct genl_info *info)
{
struct net_device *dev = req_info->dev;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
phydev = dev->phydev;
if (!phydev) {
phydev = ethnl_req_get_phydev(req_info, tb[ETHTOOL_A_PSE_HEADER],
info->extack);
if (IS_ERR_OR_NULL(phydev)) {
NL_SET_ERR_MSG(info->extack, "No PHY is attached");
return -EOPNOTSUPP;
}
@ -255,12 +260,14 @@ ethnl_set_pse_validate(struct ethnl_req_info *req_info, struct genl_info *info)
static int
ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info)
{
struct net_device *dev = req_info->dev;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
int ret = 0;
phydev = dev->phydev;
phydev = ethnl_req_get_phydev(req_info, tb[ETHTOOL_A_PSE_HEADER],
info->extack);
if (IS_ERR_OR_NULL(phydev))
return -ENODEV;
if (tb[ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT]) {
unsigned int pw_limit;

View File

@ -126,7 +126,7 @@ struct strset_reply_data {
const struct nla_policy ethnl_strset_get_policy[] = {
[ETHTOOL_A_STRSET_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
NLA_POLICY_NESTED(ethnl_header_policy_phy),
[ETHTOOL_A_STRSET_STRINGSETS] = { .type = NLA_NESTED },
[ETHTOOL_A_STRSET_COUNTS_ONLY] = { .type = NLA_FLAG },
};
@ -233,17 +233,18 @@ static void strset_cleanup_data(struct ethnl_reply_data *reply_base)
}
static int strset_prepare_set(struct strset_info *info, struct net_device *dev,
unsigned int id, bool counts_only)
struct phy_device *phydev, unsigned int id,
bool counts_only)
{
const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;
const struct ethtool_ops *ops = dev->ethtool_ops;
void *strings;
int count, ret;
if (id == ETH_SS_PHY_STATS && dev->phydev &&
if (id == ETH_SS_PHY_STATS && phydev &&
!ops->get_ethtool_phy_stats && phy_ops &&
phy_ops->get_sset_count)
ret = phy_ops->get_sset_count(dev->phydev);
ret = phy_ops->get_sset_count(phydev);
else if (ops->get_sset_count && ops->get_strings)
ret = ops->get_sset_count(dev, id);
else
@ -258,10 +259,10 @@ static int strset_prepare_set(struct strset_info *info, struct net_device *dev,
strings = kcalloc(count, ETH_GSTRING_LEN, GFP_KERNEL);
if (!strings)
return -ENOMEM;
if (id == ETH_SS_PHY_STATS && dev->phydev &&
if (id == ETH_SS_PHY_STATS && phydev &&
!ops->get_ethtool_phy_stats && phy_ops &&
phy_ops->get_strings)
phy_ops->get_strings(dev->phydev, strings);
phy_ops->get_strings(phydev, strings);
else
ops->get_strings(dev, id, strings);
info->strings = strings;
@ -279,6 +280,8 @@ static int strset_prepare_data(const struct ethnl_req_info *req_base,
const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
struct strset_reply_data *data = STRSET_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
struct nlattr **tb = info->attrs;
struct phy_device *phydev;
unsigned int i;
int ret;
@ -296,6 +299,13 @@ static int strset_prepare_data(const struct ethnl_req_info *req_base,
return 0;
}
phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_HEADER_FLAGS],
info->extack);
/* phydev can be NULL, check for errors only */
if (IS_ERR(phydev))
return PTR_ERR(phydev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto err_strset;
@ -304,7 +314,7 @@ static int strset_prepare_data(const struct ethnl_req_info *req_base,
!data->sets[i].per_dev)
continue;
ret = strset_prepare_set(&data->sets[i], dev, i,
ret = strset_prepare_set(&data->sets[i], dev, phydev, i,
req_info->counts_only);
if (ret < 0)
goto err_ops;