* [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing
@ 2026-03-09 15:27 Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 01/10] net: phy: phy_link_topology: Add a helper for opportunistic alloc Maxime Chevallier
` (9 more replies)
0 siblings, 10 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Hello everyone,
Here's V7 for the phy_port netlink interface (get-only for now). Changes
are some cleanup-path adjustments and the use of the correct netlink cmd
in the dump messages.
It builds on the recent addition of phy_port representation to enable
listing the front-facing ports of an interface. For now, we don't control
these ports, we merely list their presence and their capabilities.
As the most common use-case of multi-port interfaces is combo-ports that
provide both RJ45 and SFP connectors on a single MAC, there's a lot of
SFP stuff in this series.
This series is in 2 main parts. The first one aims at representing the
SFP cages and modules using phy_port, as combo-ports with RJ45 + SFP are
by far the most common cases for multi-connector setups.
The second part is the netlink interface to list those ports, now that
most use-cases are covered.
Let's see what we can do with some examples of the new ethtool API :
- Get MII interfaces supported by an empty SFP cage :
# ethtool --show-ports eth3
Port for eth3:
Port id: 1
Vacant: yes
Supported MII interfaces : sgmii, 1000base-x, 2500base-x
Port type: sfp
- Get Combo-ports supported modes, on each port :
# ethtool --show-ports eth1
Port for eth1:
Port id: 1
Vacant: no
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
10000baseT/Full
2500baseT/Full
5000baseT/Full
Port type: mdi
Port for eth1:
Port id: 2
Vacant: yes
Supported MII interfaces : 10gbase-r
Port type: sfp
- Get Achievable linkmodes on a SFP module (combo port with a DAC in the
SFP cage)
# ethtool --show-ports eth1
Port for eth1:
Port id: 1
Vacant: no
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
10000baseT/Full
2500baseT/Full
5000baseT/Full
Port type: mdi
Port for eth1:
Port id: 2
Vacant: no
Supported MII interfaces : 10gbase-r
Port type: sfp
Port for eth1:
Port id: 3
Vacant: no
Supported link modes: 10000baseCR/Full
Port type: mdi
Note that here, we have 3 ports :
- The Copper port
- The SFP Cage itself, marked as 'occupied'
- The SFP module
This series builds on top of phy_port and phy_link_topology to allow
tracking the ports of an interface. We maintain a list of supported
linkmodes/interfaces on each port, which allows for fine-grained
reporting of each port's capability.
What this series doesn't do :
- We don't support selecting which port is active. This is the next step.
- We only support PHY-driven combo ports. The end-goal of this whole
journey that started with phy_link_topology is to get support for MII
muxes, such as the one we have on the Turris Omnia. This will eventually
be upstreamed as well.
If you want to play around with it, here's [1] the patched ethtool that I've
been using to produce the outputs above.
Thanks !
Maxime
[1] : https://github.com/minimaxwell/ethtool/tree/mc/ethtool_port
Changelog :
Changes in V7:
- Changed the port cleanup path to use list_for_each_entry_continue_reverse
- Adjusted the cleanup path in phylink for the port vacant state
- Pass the right cmd for the netlink dump message
Changes in V6:
V6: https://lore.kernel.org/r/20260304145444.442334-1-maxime.chevallier@bootlin.com
- Added some comments in th mod_port cleanup
- changed some kmalloc to kmalloc_obj
- Removed some phy_link_topo_del_port that wasn't needed
Changes in V5:
V5: https://lore.kernel.org/r/20260205092317.755906-1-maxime.chevallier@bootlin.com
- Fixed a check on a potentially un-initialized pointer, reported by
Simon
- Fixed a documentation formatting issue
- Remove a stray pr_info
- Rebased on net-next
Changes in V4:
V4 : https://lore.kernel.org/netdev/20260203172839.548524-1-maxime.chevallier@bootlin.com/
- Add a cleanup patch for the of port parsing
- Added a match to sync the port's linkmodes with the PHY's for OF
ports
- Added RTNL assert in the port_get topo helper
- nullify the bus port for phylink support
- Fix some typos
Changes in V3:
V3: https://lore.kernel.org/netdev/20260201151249.642015-1-maxime.chevallier@bootlin.com/
- Remove the sfp bus ops for nophy, and use .module_start() as
suggested by Russell
- Added missing cleanup for the topology, as per AI review
- Fixed a few typos as per Romain's review
- Changed "occupied" to "vacant" as per Romain's review
- Added missing checks for null ports, per AI review
Changes in V2:
V2: https://lore.kernel.org/netdev/20260128204526.170927-1-maxime.chevallier@bootlin.com/
- Fix the cleanup path of phy_link_topo_add_phy, as per AI review
- Fix the cleanup path of phy_sfp_probe, as per AI review
- Fix the call-site of the disconnect_nophy sfp bus ops, per AI review
- Fix the netdev-less case uin phylink, per AI review
- Fix the prototype of phy_link_topo_get_port for the stubs
- Dropped patch 11. It ended-up breaking 'allnoconfig', so instead we
built a phy_interface_names array in net/ethtool/netlink.c
- Fix an ethool-netlink spec discrepancy with the type of an attribute
- Fix the size computation in the netlink port API
- Fix the cleanup path in the netlink port API
V1: https://lore.kernel.org/netdev/20260127134202.8208-1-maxime.chevallier@bootlin.com/
Maxime Chevallier (10):
net: phy: phy_link_topology: Add a helper for opportunistic alloc
net: phy: phy_link_topology: Track ports in phy_link_topology
net: phylink: Register a phy_port for MAC-driven SFP busses
net: phy: Create SFP phy_port before registering upstream
net: phy: Represent PHY-less SFP modules with phy_port
net: phylink: Represent PHY-less SFP modules with phy_port
net: phy: phy_port: Store information about a MII port's vacant state
net: phy: phy_link_topology: Add a helper to retrieve ports
netlink: specs: Add ethernet port listing with ethtool
net: ethtool: Introduce ethtool command to list ports
Documentation/netlink/specs/ethtool.yaml | 50 +++
Documentation/networking/ethtool-netlink.rst | 35 ++
MAINTAINERS | 1 +
drivers/net/phy/phy-caps.h | 2 +
drivers/net/phy/phy_caps.c | 26 ++
drivers/net/phy/phy_device.c | 146 ++++++-
drivers/net/phy/phy_link_topology.c | 82 +++-
drivers/net/phy/phylink.c | 132 +++++-
include/linux/phy.h | 6 +
include/linux/phy_link_topology.h | 39 ++
include/linux/phy_port.h | 6 +
.../uapi/linux/ethtool_netlink_generated.h | 19 +
net/core/dev.c | 1 +
net/ethtool/Makefile | 2 +-
net/ethtool/netlink.c | 26 ++
net/ethtool/netlink.h | 8 +
net/ethtool/port.c | 376 ++++++++++++++++++
17 files changed, 929 insertions(+), 28 deletions(-)
create mode 100644 net/ethtool/port.c
--
2.49.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH net-next v7 01/10] net: phy: phy_link_topology: Add a helper for opportunistic alloc
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 02/10] net: phy: phy_link_topology: Track ports in phy_link_topology Maxime Chevallier
` (8 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
The phy_link_topology structure stores information about the PHY-related
components connected to a net_device. It is opportunistically allocated,
when we add the first item to the topology, as this is not relevant for
all kinds of net_devices.
In preparation for the addition of phy_port tracking in the topology,
let's make a dedicated helper for that allocation sequence.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phy_link_topology.c | 29 +++++++++++++++++++++--------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c
index 1f1eb5d59b38..fdfafd951905 100644
--- a/drivers/net/phy/phy_link_topology.c
+++ b/drivers/net/phy/phy_link_topology.c
@@ -27,21 +27,34 @@ static int netdev_alloc_phy_link_topology(struct net_device *dev)
return 0;
}
+static struct phy_link_topology *phy_link_topo_get_or_alloc(struct net_device *dev)
+{
+ int ret;
+
+ if (dev->link_topo)
+ return dev->link_topo;
+
+ /* The topology is allocated the first time we add an object to it.
+ * It is freed alongside the netdev.
+ */
+ ret = netdev_alloc_phy_link_topology(dev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return dev->link_topo;
+}
+
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_link_topology *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;
- }
+ topo = phy_link_topo_get_or_alloc(dev);
+ if (IS_ERR(topo))
+ return PTR_ERR(topo);
pdn = kzalloc_obj(*pdn);
if (!pdn)
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 02/10] net: phy: phy_link_topology: Track ports in phy_link_topology
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 01/10] net: phy: phy_link_topology: Add a helper for opportunistic alloc Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 03/10] net: phylink: Register a phy_port for MAC-driven SFP busses Maxime Chevallier
` (7 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
phy_port is aimed at representing the various physical interfaces of a
net_device. They can be controlled by various components in the link,
such as the Ethernet PHY, the Ethernet MAC, and SFP module, etc.
Let's therefore make so we keep track of all the ports connected to a
netdev in phy_link_topology. The only ports added for now are phy-driven
ports.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phy_link_topology.c | 53 +++++++++++++++++++++++++++++
include/linux/phy_link_topology.h | 18 ++++++++++
include/linux/phy_port.h | 2 ++
net/core/dev.c | 1 +
4 files changed, 74 insertions(+)
diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c
index fdfafd951905..207128303ca2 100644
--- a/drivers/net/phy/phy_link_topology.c
+++ b/drivers/net/phy/phy_link_topology.c
@@ -7,6 +7,7 @@
*/
#include <linux/phy_link_topology.h>
+#include <linux/phy_port.h>
#include <linux/phy.h>
#include <linux/rtnetlink.h>
#include <linux/xarray.h>
@@ -22,6 +23,9 @@ static int netdev_alloc_phy_link_topology(struct net_device *dev)
xa_init_flags(&topo->phys, XA_FLAGS_ALLOC1);
topo->next_phy_index = 1;
+ xa_init_flags(&topo->ports, XA_FLAGS_ALLOC1);
+ topo->next_port_index = 1;
+
dev->link_topo = topo;
return 0;
@@ -44,12 +48,45 @@ static struct phy_link_topology *phy_link_topo_get_or_alloc(struct net_device *d
return dev->link_topo;
}
+int phy_link_topo_add_port(struct net_device *dev, struct phy_port *port)
+{
+ struct phy_link_topology *topo;
+ int ret;
+
+ topo = phy_link_topo_get_or_alloc(dev);
+ if (IS_ERR(topo))
+ return PTR_ERR(topo);
+
+ /* Attempt to re-use a previously allocated port_id */
+ if (port->id)
+ ret = xa_insert(&topo->ports, port->id, port, GFP_KERNEL);
+ else
+ ret = xa_alloc_cyclic(&topo->ports, &port->id, port,
+ xa_limit_32b, &topo->next_port_index,
+ GFP_KERNEL);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_link_topo_add_port);
+
+void phy_link_topo_del_port(struct net_device *dev, struct phy_port *port)
+{
+ struct phy_link_topology *topo = dev->link_topo;
+
+ if (!topo)
+ return;
+
+ xa_erase(&topo->ports, port->id);
+}
+EXPORT_SYMBOL_GPL(phy_link_topo_del_port);
+
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;
struct phy_device_node *pdn;
+ struct phy_port *port;
int ret;
topo = phy_link_topo_get_or_alloc(dev);
@@ -89,8 +126,20 @@ int phy_link_topo_add_phy(struct net_device *dev,
if (ret < 0)
goto err;
+ /* Add all the PHY's ports to the topology */
+ list_for_each_entry(port, &phy->ports, head) {
+ ret = phy_link_topo_add_port(dev, port);
+ if (ret)
+ goto del_ports;
+ }
+
return 0;
+del_ports:
+ list_for_each_entry_continue_reverse(port, &phy->ports, head)
+ phy_link_topo_del_port(dev, port);
+
+ xa_erase(&topo->phys, phy->phyindex);
err:
kfree(pdn);
return ret;
@@ -102,10 +151,14 @@ void phy_link_topo_del_phy(struct net_device *dev,
{
struct phy_link_topology *topo = dev->link_topo;
struct phy_device_node *pdn;
+ struct phy_port *port;
if (!topo)
return;
+ list_for_each_entry(port, &phy->ports, head)
+ phy_link_topo_del_port(dev, port);
+
pdn = xa_erase(&topo->phys, phy->phyindex);
/* We delete the PHY from the topology, however we don't re-set the
diff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h
index 68a59e25821c..66bceff72b19 100644
--- a/include/linux/phy_link_topology.h
+++ b/include/linux/phy_link_topology.h
@@ -16,11 +16,15 @@
struct xarray;
struct phy_device;
+struct phy_port;
struct sfp_bus;
struct phy_link_topology {
struct xarray phys;
u32 next_phy_index;
+
+ struct xarray ports;
+ u32 next_port_index;
};
struct phy_device_node {
@@ -43,6 +47,9 @@ int phy_link_topo_add_phy(struct net_device *dev,
void phy_link_topo_del_phy(struct net_device *dev, struct phy_device *phy);
+int phy_link_topo_add_port(struct net_device *dev, struct phy_port *port);
+void phy_link_topo_del_port(struct net_device *dev, struct phy_port *port);
+
static inline struct phy_device *
phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)
{
@@ -72,6 +79,17 @@ static inline void phy_link_topo_del_phy(struct net_device *dev,
{
}
+static inline int phy_link_topo_add_port(struct net_device *dev,
+ struct phy_port *port)
+{
+ return 0;
+}
+
+static inline void phy_link_topo_del_port(struct net_device *dev,
+ struct phy_port *port)
+{
+}
+
static inline struct phy_device *
phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)
{
diff --git a/include/linux/phy_port.h b/include/linux/phy_port.h
index 0ef0f5ce4709..4e2a3fdd2f2e 100644
--- a/include/linux/phy_port.h
+++ b/include/linux/phy_port.h
@@ -36,6 +36,7 @@ struct phy_port_ops {
/**
* struct phy_port - A representation of a network device physical interface
*
+ * @id: Unique identifier for the port within the topology
* @head: Used by the port's parent to list ports
* @parent_type: The type of device this port is directly connected to
* @phy: If the parent is PHY_PORT_PHYDEV, the PHY controlling that port
@@ -52,6 +53,7 @@ struct phy_port_ops {
* @is_sfp: Indicates if this port drives an SFP cage.
*/
struct phy_port {
+ u32 id;
struct list_head head;
enum phy_port_parent parent_type;
union {
diff --git a/net/core/dev.c b/net/core/dev.c
index 1cf3ad840697..999f30ede7d1 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -11291,6 +11291,7 @@ static void netdev_free_phy_link_topology(struct net_device *dev)
if (IS_ENABLED(CONFIG_PHYLIB) && topo) {
xa_destroy(&topo->phys);
+ xa_destroy(&topo->ports);
kfree(topo);
dev->link_topo = NULL;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 03/10] net: phylink: Register a phy_port for MAC-driven SFP busses
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 01/10] net: phy: phy_link_topology: Add a helper for opportunistic alloc Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 02/10] net: phy: phy_link_topology: Track ports in phy_link_topology Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 04/10] net: phy: Create SFP phy_port before registering upstream Maxime Chevallier
` (6 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
phy_port tracks the interfaces that a netdevice feeds into. SFP cages are
such ports, but so far we are only tracking the ones that are driven by
PHYs acting as media-converters.
Let's populate a phy_port for MAC driver SFP cages, handled by phylink.
This phy_port represents the SFP cage itself, and not the module that
may be plugged into it. It's therefore not an MDI interface, so only the
'interfaces' field is relevant here.
The phy_port is only populated for 'NETDEV' phylink instances, as
otherwise we don't have any topology to attach the port to.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phylink.c | 53 +++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index aba1b35c7cd7..3212913c9897 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -14,6 +14,8 @@
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
+#include <linux/phy_link_topology.h>
+#include <linux/phy_port.h>
#include <linux/phylink.h>
#include <linux/rtnetlink.h>
#include <linux/spinlock.h>
@@ -93,6 +95,7 @@ struct phylink {
DECLARE_PHY_INTERFACE_MASK(sfp_interfaces);
__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
u8 sfp_port;
+ struct phy_port *sfp_bus_port;
struct eee_config eee_cfg;
@@ -1758,6 +1761,46 @@ static void phylink_fixed_poll(struct timer_list *t)
static const struct sfp_upstream_ops sfp_phylink_ops;
+static int phylink_create_sfp_port(struct phylink *pl)
+{
+ struct phy_port *port;
+ int ret = 0;
+
+ if (!pl->netdev || !pl->sfp_bus)
+ return 0;
+
+ port = phy_port_alloc();
+ if (!port)
+ return -ENOMEM;
+
+ port->is_sfp = true;
+ port->is_mii = true;
+ port->active = true;
+
+ phy_interface_and(port->interfaces, pl->config->supported_interfaces,
+ phylink_sfp_interfaces);
+ phy_port_update_supported(port);
+
+ ret = phy_link_topo_add_port(pl->netdev, port);
+ if (ret)
+ phy_port_destroy(port);
+ else
+ pl->sfp_bus_port = port;
+
+ return ret;
+}
+
+static void phylink_destroy_sfp_port(struct phylink *pl)
+{
+ if (pl->netdev && pl->sfp_bus_port)
+ phy_link_topo_del_port(pl->netdev, pl->sfp_bus_port);
+
+ if (pl->sfp_bus_port)
+ phy_port_destroy(pl->sfp_bus_port);
+
+ pl->sfp_bus_port = NULL;
+}
+
static int phylink_register_sfp(struct phylink *pl,
const struct fwnode_handle *fwnode)
{
@@ -1775,9 +1818,18 @@ static int phylink_register_sfp(struct phylink *pl,
pl->sfp_bus = bus;
+ ret = phylink_create_sfp_port(pl);
+ if (ret) {
+ sfp_bus_put(bus);
+ return ret;
+ }
+
ret = sfp_bus_add_upstream(bus, pl, &sfp_phylink_ops);
sfp_bus_put(bus);
+ if (ret)
+ phylink_destroy_sfp_port(pl);
+
return ret;
}
@@ -1939,6 +1991,7 @@ EXPORT_SYMBOL_GPL(phylink_create);
void phylink_destroy(struct phylink *pl)
{
sfp_bus_del_upstream(pl->sfp_bus);
+ phylink_destroy_sfp_port(pl);
if (pl->link_gpio)
gpiod_put(pl->link_gpio);
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 04/10] net: phy: Create SFP phy_port before registering upstream
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (2 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 03/10] net: phylink: Register a phy_port for MAC-driven SFP busses Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 05/10] net: phy: Represent PHY-less SFP modules with phy_port Maxime Chevallier
` (5 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
When dealing with PHY-driven SFP, we create a phy_port representing the
SFP bus when we know we have such a bus.
We can move the port creation before registering the sfp upstream ops,
as long as we know the SFP bus is there. This will allow passing the
phy_port along with the upstream information to the SFP bus.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phy_device.c | 42 ++++++++++++++++++++++++------------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 3bd415710bf3..152b5477e696 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1665,13 +1665,13 @@ static void phy_del_port(struct phy_device *phydev, struct phy_port *port)
phydev->n_ports--;
}
-static int phy_setup_sfp_port(struct phy_device *phydev)
+static struct phy_port *phy_setup_sfp_port(struct phy_device *phydev)
{
struct phy_port *port = phy_port_alloc();
int ret;
if (!port)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
port->parent_type = PHY_PORT_PHY;
port->phy = phydev;
@@ -1686,10 +1686,12 @@ static int phy_setup_sfp_port(struct phy_device *phydev)
* when attaching the port to the phydev.
*/
ret = phy_add_port(phydev, port);
- if (ret)
+ if (ret) {
phy_port_destroy(port);
+ return ERR_PTR(ret);
+ }
- return ret;
+ return port;
}
/**
@@ -1698,22 +1700,34 @@ static int phy_setup_sfp_port(struct phy_device *phydev)
*/
static int phy_sfp_probe(struct phy_device *phydev)
{
+ struct phy_port *port = NULL;
struct sfp_bus *bus;
- int ret = 0;
+ int ret;
- if (phydev->mdio.dev.fwnode) {
- bus = sfp_bus_find_fwnode(phydev->mdio.dev.fwnode);
- if (IS_ERR(bus))
- return PTR_ERR(bus);
+ if (!phydev->mdio.dev.fwnode)
+ return 0;
+
+ bus = sfp_bus_find_fwnode(phydev->mdio.dev.fwnode);
+ if (IS_ERR(bus))
+ return PTR_ERR(bus);
- phydev->sfp_bus = bus;
+ phydev->sfp_bus = bus;
- ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops);
- sfp_bus_put(bus);
+ if (bus) {
+ port = phy_setup_sfp_port(phydev);
+ if (IS_ERR(port)) {
+ sfp_bus_put(bus);
+ return PTR_ERR(port);
+ }
}
- if (!ret && phydev->sfp_bus)
- ret = phy_setup_sfp_port(phydev);
+ ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops);
+ sfp_bus_put(bus);
+
+ if (ret && port) {
+ phy_del_port(phydev, port);
+ phy_port_destroy(port);
+ }
return ret;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 05/10] net: phy: Represent PHY-less SFP modules with phy_port
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (3 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 04/10] net: phy: Create SFP phy_port before registering upstream Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-12 10:47 ` [net-next,v7,05/10] " Paolo Abeni
2026-03-09 15:27 ` [PATCH net-next v7 06/10] net: phylink: " Maxime Chevallier
` (4 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Now that the SFP bus infrastructure notifies when PHY-less modules are
connected, we can create a phy_port to represent it. Instead of letting
the SFP subsystem handle that, the Bus' upstream is in charge of
maintaining that phy_port and register it to the topology, as the
upstream (in this case a phy device) is directly interacting with the
underlying net_device.
Add a phy_caps helper to get the achievable modes on this module based
on what the phy_port representing the bus supports.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phy-caps.h | 2 +
drivers/net/phy/phy_caps.c | 26 +++++++++++
drivers/net/phy/phy_device.c | 87 ++++++++++++++++++++++++++++++++++++
include/linux/phy.h | 6 +++
4 files changed, 121 insertions(+)
diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h
index 421088e6f6e8..ec3d39a0ae06 100644
--- a/drivers/net/phy/phy-caps.h
+++ b/drivers/net/phy/phy-caps.h
@@ -66,5 +66,7 @@ void phy_caps_medium_get_supported(unsigned long *supported,
enum ethtool_link_medium medium,
int lanes);
u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes);
+void phy_caps_linkmode_filter_ifaces(unsigned long *to, const unsigned long *from,
+ const unsigned long *interfaces);
#endif /* __PHY_CAPS_H */
diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c
index 942d43191561..558e4df4d63c 100644
--- a/drivers/net/phy/phy_caps.c
+++ b/drivers/net/phy/phy_caps.c
@@ -445,3 +445,29 @@ u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes)
return mediums;
}
EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes);
+
+/**
+ * phy_caps_linkmode_filter_ifaces() - Filter linkmodes with an interface list
+ * @to: Stores the filtered linkmodes
+ * @from: Linkmodes to filter
+ * @interfaces: Bitfield of phy_interface_t that we use for filtering
+ *
+ * Filter the provided linkmodes, only to keep the ones we can possibly achieve
+ * when using any of the provided MII interfaces.
+ */
+void phy_caps_linkmode_filter_ifaces(unsigned long *to,
+ const unsigned long *from,
+ const unsigned long *interfaces)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(ifaces_supported) = {};
+ unsigned int ifaces_caps = 0;
+ phy_interface_t interface;
+
+ for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
+ ifaces_caps |= phy_caps_from_interface(interface);
+
+ phy_caps_linkmodes(ifaces_caps, ifaces_supported);
+
+ linkmode_and(to, from, ifaces_supported);
+}
+EXPORT_SYMBOL_GPL(phy_caps_linkmode_filter_ifaces);
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 152b5477e696..8bff64040540 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1483,6 +1483,8 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
struct phy_device *phydev = upstream;
struct net_device *dev = phydev->attached_dev;
+ phydev->has_sfp_mod_phy = true;
+
if (dev)
return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev);
@@ -1504,6 +1506,8 @@ static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)
struct phy_device *phydev = upstream;
struct net_device *dev = phydev->attached_dev;
+ phydev->has_sfp_mod_phy = false;
+
if (dev)
phy_link_topo_del_phy(dev, phy);
}
@@ -1609,6 +1613,75 @@ static void phy_sfp_link_down(void *upstream)
port->ops->link_down(port);
}
+static int phy_add_sfp_mod_port(struct phy_device *phydev)
+{
+ const struct sfp_module_caps *caps;
+ struct phy_port *port;
+ int ret = 0;
+
+ /* Create mod port */
+ port = phy_port_alloc();
+ if (!port)
+ return -ENOMEM;
+
+ port->active = true;
+
+ caps = sfp_get_module_caps(phydev->sfp_bus);
+
+ phy_caps_linkmode_filter_ifaces(port->supported, caps->link_modes,
+ phydev->sfp_bus_port->interfaces);
+
+ if (phydev->attached_dev) {
+ ret = phy_link_topo_add_port(phydev->attached_dev, port);
+ if (ret) {
+ phy_port_destroy(port);
+ return ret;
+ }
+ }
+
+ /* we don't use phy_add_port() here as the module port isn't a direct
+ * interface from the PHY, but rather an extension to the sfp-bus, that
+ * is already represented by its own phy_port
+ */
+ phydev->mod_port = port;
+
+ return 0;
+}
+
+static void phy_del_sfp_mod_port(struct phy_device *phydev)
+{
+ if (!phydev->mod_port)
+ return;
+
+ if (phydev->attached_dev)
+ phy_link_topo_del_port(phydev->attached_dev, phydev->mod_port);
+
+ phy_port_destroy(phydev->mod_port);
+ phydev->mod_port = NULL;
+}
+
+static int phy_sfp_module_start(void *upstream)
+{
+ struct phy_device *phydev = upstream;
+
+ /* If there's a downstream SFP module, and it doesn't contain a PHY
+ * device, let's create a phy_port to represent that module.
+ */
+ if (!phydev->has_sfp_mod_phy)
+ return phy_add_sfp_mod_port(phydev);
+
+ return 0;
+}
+
+static void phy_sfp_module_stop(void *upstream)
+{
+ struct phy_device *phydev = upstream;
+
+ /* Called upon module removal or upstream removal */
+ if (!phydev->has_sfp_mod_phy)
+ phy_del_sfp_mod_port(phydev);
+}
+
static const struct sfp_upstream_ops sfp_phydev_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
@@ -1618,6 +1691,8 @@ static const struct sfp_upstream_ops sfp_phydev_ops = {
.link_down = phy_sfp_link_down,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
+ .module_start = phy_sfp_module_start,
+ .module_stop = phy_sfp_module_stop,
};
static int phy_add_port(struct phy_device *phydev, struct phy_port *port)
@@ -1727,8 +1802,11 @@ static int phy_sfp_probe(struct phy_device *phydev)
if (ret && port) {
phy_del_port(phydev, port);
phy_port_destroy(port);
+ port = NULL;
}
+ phydev->sfp_bus_port = port;
+
return ret;
}
@@ -1818,6 +1896,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
err = phy_link_topo_add_phy(dev, phydev, PHY_UPSTREAM_MAC, dev);
if (err)
goto error;
+
+ if (phydev->mod_port) {
+ err = phy_link_topo_add_port(dev, phydev->mod_port);
+ if (err)
+ goto error;
+ }
}
/* Some Ethernet drivers try to connect to a PHY device before
@@ -1989,6 +2073,8 @@ void phy_detach(struct phy_device *phydev)
phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
phy_link_topo_del_phy(dev, phydev);
+ if (phydev->mod_port)
+ phy_link_topo_del_port(dev, phydev->mod_port);
}
phydev->phy_link_change = NULL;
@@ -3830,6 +3916,7 @@ static int phy_remove(struct device *dev)
sfp_bus_del_upstream(phydev->sfp_bus);
phydev->sfp_bus = NULL;
+ phydev->sfp_bus_port = NULL;
if (phydev->drv && phydev->drv->remove)
phydev->drv->remove(phydev);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 6f9979a26892..9114fdff4c4f 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -582,6 +582,7 @@ struct phy_oatc14_sqi_capability {
* @wol_enabled: Set to true if the PHY or the attached MAC have Wake-on-LAN
* enabled.
* @is_genphy_driven: PHY is driven by one of the generic PHY drivers
+ * @has_sfp_mod_phy: Set true if downstream SFP bus's module contains a PHY
* @state: State of the PHY for management purposes
* @dev_flags: Device-specific flags used by the PHY driver.
*
@@ -594,6 +595,8 @@ struct phy_oatc14_sqi_capability {
* @phylink: Pointer to phylink instance for this PHY
* @sfp_bus_attached: Flag indicating whether the SFP bus has been attached
* @sfp_bus: SFP bus attached to this PHY's fiber port
+ * @sfp_bus_port: The phy_port connected to the downstream SFP bus
+ * @mod_port: phy_port representing the SFP module, if it is phy-less
* @attached_dev: The attached enet driver's device instance ptr
* @adjust_link: Callback for the enet controller to respond to changes: in the
* link state.
@@ -704,6 +707,7 @@ struct phy_device {
unsigned irq_rerun:1;
unsigned default_timestamp:1;
+ unsigned has_sfp_mod_phy:1;
int rate_matching;
@@ -782,6 +786,8 @@ struct phy_device {
/* This may be modified under the rtnl lock */
bool sfp_bus_attached;
struct sfp_bus *sfp_bus;
+ struct phy_port *sfp_bus_port;
+ struct phy_port *mod_port;
struct phylink *phylink;
struct net_device *attached_dev;
struct mii_timestamper *mii_ts;
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 06/10] net: phylink: Represent PHY-less SFP modules with phy_port
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (4 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 05/10] net: phy: Represent PHY-less SFP modules with phy_port Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 07/10] net: phy: phy_port: Store information about a MII port's vacant state Maxime Chevallier
` (3 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Let phylink handle the phy_port for PHY-less modules, and register it to
the topology.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phylink.c | 71 +++++++++++++++++++++++++++++++++++++--
1 file changed, 68 insertions(+), 3 deletions(-)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 3212913c9897..79f7811dec22 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -96,6 +96,7 @@ struct phylink {
__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
u8 sfp_port;
struct phy_port *sfp_bus_port;
+ struct phy_port *mod_port;
struct eee_config eee_cfg;
@@ -1783,10 +1784,21 @@ static int phylink_create_sfp_port(struct phylink *pl)
ret = phy_link_topo_add_port(pl->netdev, port);
if (ret)
- phy_port_destroy(port);
- else
- pl->sfp_bus_port = port;
+ goto out_destroy_port;
+
+ pl->sfp_bus_port = port;
+
+ if (pl->mod_port) {
+ ret = phy_link_topo_add_port(pl->netdev, pl->mod_port);
+ if (ret)
+ goto out_del_bus_port;
+ }
+ return 0;
+out_del_bus_port:
+ phy_link_topo_del_port(pl->netdev, port);
+out_destroy_port:
+ phy_port_destroy(port);
return ret;
}
@@ -3919,14 +3931,65 @@ static void phylink_sfp_module_remove(void *upstream)
phy_interface_zero(pl->sfp_interfaces);
}
+static int phylink_add_sfp_mod_port(struct phylink *pl)
+{
+ const struct sfp_module_caps *caps;
+ struct phy_port *port;
+ int ret = 0;
+
+ if (!pl->sfp_bus_port)
+ return 0;
+
+ /* Create mod port */
+ port = phy_port_alloc();
+ if (!port)
+ return -ENOMEM;
+
+ port->active = true;
+
+ caps = sfp_get_module_caps(pl->sfp_bus);
+
+ phy_caps_linkmode_filter_ifaces(port->supported, caps->link_modes,
+ pl->sfp_bus_port->interfaces);
+
+ if (pl->netdev) {
+ ret = phy_link_topo_add_port(pl->netdev, port);
+ if (ret) {
+ phy_port_destroy(port);
+ return ret;
+ }
+ }
+
+ pl->mod_port = port;
+
+ return 0;
+}
+
+static void phylink_del_sfp_mod_port(struct phylink *pl)
+{
+ if (!pl->mod_port)
+ return;
+
+ if (pl->netdev)
+ phy_link_topo_del_port(pl->netdev, pl->mod_port);
+
+ phy_port_destroy(pl->mod_port);
+ pl->mod_port = NULL;
+}
+
static int phylink_sfp_module_start(void *upstream)
{
struct phylink *pl = upstream;
+ int ret;
/* If this SFP module has a PHY, start the PHY now. */
if (pl->phydev) {
phy_start(pl->phydev);
return 0;
+ } else {
+ ret = phylink_add_sfp_mod_port(pl);
+ if (ret)
+ return ret;
}
/* If the module may have a PHY but we didn't detect one we
@@ -3945,6 +4008,8 @@ static void phylink_sfp_module_stop(void *upstream)
/* If this SFP module has a PHY, stop it. */
if (pl->phydev)
phy_stop(pl->phydev);
+ else
+ phylink_del_sfp_mod_port(pl);
}
static void phylink_sfp_link_down(void *upstream)
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 07/10] net: phy: phy_port: Store information about a MII port's vacant state
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (5 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 06/10] net: phylink: " Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 08/10] net: phy: phy_link_topology: Add a helper to retrieve ports Maxime Chevallier
` (2 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
MII phy_ports are not meant to be connected directly to a link partner.
They are meant to feed into some media converter devices, so far we only
support SFP modules for that.
We have information about what MII they can handle, however we don't
store anything about whether they are currently connected to an SFP
module or not. As phy_port aims at listing the front-facing ports, let's
store an "vacant" bit to know whether or not a MII port is currently
vacant (i.e. there's no module in the SFP cage), or not (i.e.
there's an SFP module).
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/phy_device.c | 17 +++++++++++++----
drivers/net/phy/phylink.c | 14 +++++++++++++-
include/linux/phy_port.h | 4 ++++
3 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 8bff64040540..5d26d75b5f6b 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1550,6 +1550,7 @@ static int phy_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id)
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
struct phy_port *port;
+ int ret = 0;
phy_interface_t iface;
@@ -1578,9 +1579,12 @@ static int phy_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id)
phydev->port = caps->port;
if (port->ops && port->ops->configure_mii)
- return port->ops->configure_mii(port, true, iface);
+ ret = port->ops->configure_mii(port, true, iface);
- return 0;
+ if (!ret)
+ port->vacant = false;
+
+ return ret;
}
static void phy_sfp_module_remove(void *upstream)
@@ -1588,8 +1592,12 @@ static void phy_sfp_module_remove(void *upstream)
struct phy_device *phydev = upstream;
struct phy_port *port = phy_get_sfp_port(phydev);
- if (port && port->ops && port->ops->configure_mii)
- port->ops->configure_mii(port, false, PHY_INTERFACE_MODE_NA);
+ if (port) {
+ if (port->ops && port->ops->configure_mii)
+ port->ops->configure_mii(port, false,
+ PHY_INTERFACE_MODE_NA);
+ port->vacant = true;
+ }
if (phydev->n_ports == 1)
phydev->port = PORT_NONE;
@@ -1756,6 +1764,7 @@ static struct phy_port *phy_setup_sfp_port(struct phy_device *phydev)
*/
port->is_mii = true;
port->is_sfp = true;
+ port->vacant = true;
/* The port->supported and port->interfaces list will be populated
* when attaching the port to the phydev.
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 79f7811dec22..dcf06b9670a2 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1777,6 +1777,7 @@ static int phylink_create_sfp_port(struct phylink *pl)
port->is_sfp = true;
port->is_mii = true;
port->active = true;
+ port->vacant = true;
phy_interface_and(port->interfaces, pl->config->supported_interfaces,
phylink_sfp_interfaces);
@@ -3908,6 +3909,7 @@ static int phylink_sfp_module_insert(void *upstream,
{
const struct sfp_module_caps *caps;
struct phylink *pl = upstream;
+ int ret;
ASSERT_RTNL();
@@ -3917,17 +3919,27 @@ static int phylink_sfp_module_insert(void *upstream,
pl->sfp_may_have_phy = caps->may_have_phy;
pl->sfp_port = caps->port;
+ if (pl->sfp_bus_port)
+ pl->sfp_bus_port->vacant = false;
+
/* If this module may have a PHY connecting later, defer until later */
if (pl->sfp_may_have_phy)
return 0;
- return phylink_sfp_config_optical(pl);
+ ret = phylink_sfp_config_optical(pl);
+ if (ret && pl->sfp_bus_port)
+ pl->sfp_bus_port->vacant = true;
+
+ return ret;
}
static void phylink_sfp_module_remove(void *upstream)
{
struct phylink *pl = upstream;
+ if (pl->sfp_bus_port)
+ pl->sfp_bus_port->vacant = true;
+
phy_interface_zero(pl->sfp_interfaces);
}
diff --git a/include/linux/phy_port.h b/include/linux/phy_port.h
index 4e2a3fdd2f2e..b0b1fa6a67a9 100644
--- a/include/linux/phy_port.h
+++ b/include/linux/phy_port.h
@@ -51,6 +51,9 @@ struct phy_port_ops {
* @is_mii: Indicates if this port is MII (Media Independent Interface),
* or MDI (Media Dependent Interface).
* @is_sfp: Indicates if this port drives an SFP cage.
+ * @vacant: For MII ports, indicates whether or not the port has a connection to
+ * another device (e.g. for SFP ports, indicates the absence or presence
+ * of an SFP module)
*/
struct phy_port {
u32 id;
@@ -71,6 +74,7 @@ struct phy_port {
unsigned int active:1;
unsigned int is_mii:1;
unsigned int is_sfp:1;
+ unsigned int vacant:1;
};
struct phy_port *phy_port_alloc(void);
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 08/10] net: phy: phy_link_topology: Add a helper to retrieve ports
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (6 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 07/10] net: phy: phy_port: Store information about a MII port's vacant state Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 09/10] netlink: specs: Add ethernet port listing with ethtool Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports Maxime Chevallier
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
In order to allow netlink access to phy_ports, let's add a helper to
retrieve them. When handling a port coming from phy_link_topology, the
caller must hold rtnl until it's done with it.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
include/linux/phy_link_topology.h | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h
index 66bceff72b19..ac2f5c80f1a6 100644
--- a/include/linux/phy_link_topology.h
+++ b/include/linux/phy_link_topology.h
@@ -13,6 +13,7 @@
#include <linux/ethtool.h>
#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
struct xarray;
struct phy_device;
@@ -66,6 +67,20 @@ phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)
return NULL;
}
+static inline struct phy_port *
+phy_link_topo_get_port(struct net_device *dev, u32 port_id)
+{
+ struct phy_link_topology *topo = dev->link_topo;
+
+ ASSERT_RTNL();
+
+ if (!topo)
+ return NULL;
+
+ /* Caller must hold RTNL while handling the phy_port */
+ return xa_load(&topo->ports, port_id);
+}
+
#else
static inline int phy_link_topo_add_phy(struct net_device *dev,
struct phy_device *phy,
@@ -95,6 +110,12 @@ phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)
{
return NULL;
}
+
+static inline struct phy_port *
+phy_link_topo_get_port(struct net_device *dev, u32 port_id)
+{
+ return NULL;
+}
#endif
#endif /* __PHY_LINK_TOPOLOGY_H */
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 09/10] netlink: specs: Add ethernet port listing with ethtool
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (7 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 08/10] net: phy: phy_link_topology: Add a helper to retrieve ports Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports Maxime Chevallier
9 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Ethernet network interfaces may have more than one front-facing port.
The phy_port infrastructure was introduced to keep track of
these ports, and allow userspace to know about the presence and
capability of these ports. Add a ethnl netlink message to report this
information.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Documentation/netlink/specs/ethtool.yaml | 50 +++++++++++++++++++
Documentation/networking/ethtool-netlink.rst | 35 +++++++++++++
.../uapi/linux/ethtool_netlink_generated.h | 19 +++++++
3 files changed, 104 insertions(+)
diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 0a2d2343f79a..ab0ff17d9195 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -210,6 +210,10 @@ definitions:
-
name: discard
value: 31
+ -
+ name: port-type
+ type: enum
+ entries: [mdi, sfp]
attribute-sets:
-
@@ -1890,6 +1894,32 @@ attribute-sets:
name: link
type: nest
nested-attributes: mse-snapshot
+ -
+ name: port
+ attr-cnt-name: --ethtool-a-port-cnt
+ attributes:
+ -
+ name: header
+ type: nest
+ nested-attributes: header
+ -
+ name: id
+ type: u32
+ -
+ name: supported-modes
+ type: nest
+ nested-attributes: bitset
+ -
+ name: supported-interfaces
+ type: nest
+ nested-attributes: bitset
+ -
+ name: type
+ type: u8
+ enum: port-type
+ -
+ name: vacant
+ type: u8
operations:
enum-model: directional
@@ -2842,6 +2872,26 @@ operations:
- worst-channel
- link
dump: *mse-get-op
+ -
+ name: port-get
+ doc: Get ports attached to an interface
+
+ attribute-set: port
+
+ do: &port-get-op
+ request:
+ attributes:
+ - header
+ - id
+ reply:
+ attributes:
+ - header
+ - id
+ - supported-modes
+ - supported-interfaces
+ - type
+ - vacant
+ dump: *port-get-op
mcast-groups:
list:
diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 32179168eb73..6ac807a8cae5 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -2526,6 +2526,40 @@ Within each channel nest, only the metrics supported by the PHY will be present.
See ``struct phy_mse_snapshot`` kernel documentation in
``include/linux/phy.h``.
+PORT_GET
+========
+
+Retrieve information about the physical connection points of a network device,
+referred to as "ports". User needs to specify a PORT_ID for the DO operation,
+in which case the DO request returns information about that specific port.
+
+As there can be more than one port, the DUMP operation can be used to list the
+ports present on a given interface, by passing an interface index or name in
+the dump request.
+
+Request contents:
+
+ ===================================== ====== ===============================
+ ``ETHTOOL_A_PORT_HEADER`` nested request header
+ ``ETHTOOL_A_PORT_ID`` u32 port id
+ ===================================== ====== ===============================
+
+Kernel response contents:
+
+ ======================================= ====== =============================
+ ``ETHTOOL_A_PORT_HEADER`` nested request header
+ ``ETHTOOL_A_PORT_ID`` u32 the port's unique identifier,
+ per netdevice.
+ ``ETHTOOL_A_PORT_SUPPORTED_MODES`` bitset bitset of supported linkmodes
+ ``ETHTOOL_A_PORT_SUPPORTED_INTERFACES`` bitset bitset of supported MII
+ interfaces
+ ``ETHTOOL_A_PORT_TYPE`` u8 the port type
+ ``ETHTOOL_A_PORT_VACANT`` u8 for non-mdi ports, indicates
+ if the port is connected to
+ another device that could
+ expose a MDI
+ ======================================= ====== =============================
+
Request translation
===================
@@ -2636,4 +2670,5 @@ are netlink only.
n/a ``ETHTOOL_MSG_PHY_GET``
``SIOCGHWTSTAMP`` ``ETHTOOL_MSG_TSCONFIG_GET``
``SIOCSHWTSTAMP`` ``ETHTOOL_MSG_TSCONFIG_SET``
+ n/a ``ETHTOOL_MSG_PORT_GET``
=================================== =====================================
diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h
index 556a0c834df5..f2a5bb666c4c 100644
--- a/include/uapi/linux/ethtool_netlink_generated.h
+++ b/include/uapi/linux/ethtool_netlink_generated.h
@@ -78,6 +78,11 @@ enum ethtool_pse_event {
ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64,
};
+enum ethtool_port_type {
+ ETHTOOL_PORT_TYPE_MDI,
+ ETHTOOL_PORT_TYPE_SFP,
+};
+
enum {
ETHTOOL_A_HEADER_UNSPEC,
ETHTOOL_A_HEADER_DEV_INDEX,
@@ -837,6 +842,18 @@ enum {
ETHTOOL_A_MSE_MAX = (__ETHTOOL_A_MSE_CNT - 1)
};
+enum {
+ ETHTOOL_A_PORT_HEADER = 1,
+ ETHTOOL_A_PORT_ID,
+ ETHTOOL_A_PORT_SUPPORTED_MODES,
+ ETHTOOL_A_PORT_SUPPORTED_INTERFACES,
+ ETHTOOL_A_PORT_TYPE,
+ ETHTOOL_A_PORT_VACANT,
+
+ __ETHTOOL_A_PORT_CNT,
+ ETHTOOL_A_PORT_MAX = (__ETHTOOL_A_PORT_CNT - 1)
+};
+
enum {
ETHTOOL_MSG_USER_NONE = 0,
ETHTOOL_MSG_STRSET_GET = 1,
@@ -890,6 +907,7 @@ enum {
ETHTOOL_MSG_RSS_CREATE_ACT,
ETHTOOL_MSG_RSS_DELETE_ACT,
ETHTOOL_MSG_MSE_GET,
+ ETHTOOL_MSG_PORT_GET,
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
@@ -951,6 +969,7 @@ enum {
ETHTOOL_MSG_RSS_CREATE_NTF,
ETHTOOL_MSG_RSS_DELETE_NTF,
ETHTOOL_MSG_MSE_GET_REPLY,
+ ETHTOOL_MSG_PORT_GET_REPLY,
__ETHTOOL_MSG_KERNEL_CNT,
ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
` (8 preceding siblings ...)
2026-03-09 15:27 ` [PATCH net-next v7 09/10] netlink: specs: Add ethernet port listing with ethtool Maxime Chevallier
@ 2026-03-09 15:27 ` Maxime Chevallier
2026-03-13 2:07 ` Jakub Kicinski
9 siblings, 1 reply; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-09 15:27 UTC (permalink / raw)
To: davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Russell King, Heiner Kallweit
Cc: Maxime Chevallier, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Expose the phy_port information to userspace, so that we can know how
many ports are available on a given interface, as well as their
capabilities. For MDI ports, we report the list of supported linkmodes
based on what the PHY that drives this port says.
For MII ports, i.e. empty SFP cages, we report the MII linkmodes that we
can output on this port.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
MAINTAINERS | 1 +
net/ethtool/Makefile | 2 +-
net/ethtool/netlink.c | 26 +++
net/ethtool/netlink.h | 8 +
net/ethtool/port.c | 376 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 412 insertions(+), 1 deletion(-)
create mode 100644 net/ethtool/port.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2265e2c9bfbe..668af8739c21 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18421,6 +18421,7 @@ F: Documentation/devicetree/bindings/net/ethernet-connector.yaml
F: Documentation/networking/phy-port.rst
F: drivers/net/phy/phy_port.c
F: include/linux/phy_port.h
+F: net/ethtool/port.c
K: struct\s+phy_port|phy_port_
NETWORKING [GENERAL]
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 629c10916670..9b5b09670008 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -9,4 +9,4 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.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 \
- phy.o tsconfig.o mse.o
+ phy.o tsconfig.o mse.o port.o
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6e5f0f4f815a..90674aed7777 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -18,6 +18,8 @@ static u32 ethnl_bcast_seq;
ETHTOOL_FLAG_OMIT_REPLY)
#define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS)
+char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN] __ro_after_init;
+
const struct nla_policy ethnl_header_policy[] = {
[ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
[ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
@@ -421,6 +423,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops,
[ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops,
[ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops,
+ [ETHTOOL_MSG_PORT_GET] = ðnl_port_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -1544,6 +1547,16 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_mse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
},
+ {
+ .cmd = ETHTOOL_MSG_PORT_GET,
+ .doit = ethnl_default_doit,
+ .start = ethnl_port_dump_start,
+ .dumpit = ethnl_port_dumpit,
+ .done = ethnl_port_dump_done,
+ .policy = ethnl_port_get_policy,
+ .maxattr = ARRAY_SIZE(ethnl_port_get_policy) - 1,
+ },
+
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
@@ -1566,10 +1579,23 @@ static struct genl_family ethtool_genl_family __ro_after_init = {
/* module setup */
+static void __init ethnl_phy_names_populate(void)
+{
+ const char *name;
+ int i;
+
+ for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) {
+ name = phy_modes(i);
+ strscpy(phy_interface_names[i], name, ETH_GSTRING_LEN);
+ }
+}
+
static int __init ethnl_init(void)
{
int ret;
+ ethnl_phy_names_populate();
+
ret = genl_register_family(ðtool_genl_family);
if (WARN(ret < 0, "ethtool: genetlink family registration failed"))
return ret;
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 89010eaa67df..e849bc63ac58 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -5,11 +5,14 @@
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>
+#include <linux/phy.h>
#include <net/genetlink.h>
#include <net/sock.h>
struct ethnl_req_info;
+extern char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN];
+
u32 ethnl_bcast_seq_next(void);
int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net,
@@ -443,6 +446,7 @@ extern const struct ethnl_request_ops ethnl_mm_request_ops;
extern const struct ethnl_request_ops ethnl_phy_request_ops;
extern const struct ethnl_request_ops ethnl_tsconfig_request_ops;
extern const struct ethnl_request_ops ethnl_mse_request_ops;
+extern const struct ethnl_request_ops ethnl_port_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];
@@ -499,6 +503,7 @@ extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1];
extern const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFIG_HEADER + 1];
extern const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFIG_MAX + 1];
extern const struct nla_policy ethnl_mse_get_policy[ETHTOOL_A_MSE_HEADER + 1];
+extern const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 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);
@@ -514,6 +519,9 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_tsinfo_done(struct netlink_callback *cb);
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info);
+int ethnl_port_dump_start(struct netlink_callback *cb);
+int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int ethnl_port_dump_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];
diff --git a/net/ethtool/port.c b/net/ethtool/port.c
new file mode 100644
index 000000000000..abf24350eb65
--- /dev/null
+++ b/net/ethtool/port.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2026 Bootlin
+ *
+ */
+#include "common.h"
+#include "bitset.h"
+#include "netlink.h"
+
+#include <linux/phy.h>
+#include <linux/phy_link_topology.h>
+#include <linux/phy_port.h>
+#include <net/netdev_lock.h>
+
+struct port_req_info {
+ struct ethnl_req_info base;
+ u32 port_id;
+};
+
+struct port_reply_data {
+ struct ethnl_reply_data base;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
+ u32 port_id;
+ bool mii;
+ bool sfp;
+ bool vacant;
+};
+
+#define PORT_REQINFO(__req_base) \
+ container_of(__req_base, struct port_req_info, base)
+
+#define PORT_REPDATA(__reply_base) \
+ container_of(__reply_base, struct port_reply_data, base)
+
+const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 1] = {
+ [ETHTOOL_A_PORT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+ [ETHTOOL_A_PORT_ID] = { .type = NLA_U32},
+};
+
+static int port_parse_request(struct ethnl_req_info *req_info,
+ struct nlattr **tb,
+ struct netlink_ext_ack *extack)
+{
+ struct port_req_info *request = PORT_REQINFO(req_info);
+
+ /* PORT id is required for GET requests */
+ if (tb[ETHTOOL_A_PORT_ID])
+ request->port_id = nla_get_u32(tb[ETHTOOL_A_PORT_ID]);
+
+ if (!request->port_id) {
+ NL_SET_ERR_MSG(extack, "port id missing");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int port_prepare_data(const struct ethnl_req_info *req_info,
+ struct ethnl_reply_data *reply_data,
+ const struct genl_info *info)
+{
+ struct port_reply_data *reply = PORT_REPDATA(reply_data);
+ struct port_req_info *request = PORT_REQINFO(req_info);
+ struct phy_port *port;
+
+ /* RTNL must be held while holding a ref to the phy_port. Here, caller
+ * holds RTNL.
+ */
+ port = phy_link_topo_get_port(req_info->dev, request->port_id);
+ if (!port)
+ return -ENODEV;
+
+ linkmode_copy(reply->supported, port->supported);
+ phy_interface_copy(reply->interfaces, port->interfaces);
+ reply->port_id = port->id;
+ reply->mii = port->is_mii;
+ reply->sfp = port->is_sfp;
+ reply->vacant = port->vacant;
+
+ return 0;
+}
+
+static int port_reply_size(const struct ethnl_req_info *req_info,
+ const struct ethnl_reply_data *reply_data)
+{
+ bool compact = req_info->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
+ struct port_reply_data *reply = PORT_REPDATA(reply_data);
+ size_t size = 0;
+ int ret;
+
+ /* ETHTOOL_A_PORT_ID */
+ size += nla_total_size(sizeof(u32));
+
+ if (!reply->mii) {
+ /* ETHTOOL_A_PORT_SUPPORTED_MODES */
+ ret = ethnl_bitset_size(reply->supported, NULL,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ link_mode_names, compact);
+ if (ret < 0)
+ return ret;
+
+ size += ret;
+ } else {
+ /* ETHTOOL_A_PORT_SUPPORTED_INTERFACES */
+ ret = ethnl_bitset_size(reply->interfaces, NULL,
+ PHY_INTERFACE_MODE_MAX,
+ phy_interface_names, compact);
+ if (ret < 0)
+ return ret;
+
+ size += ret;
+ }
+
+ /* ETHTOOL_A_PORT_TYPE */
+ size += nla_total_size(sizeof(u8));
+
+ /* ETHTOOL_A_PORT_VACANT */
+ size += nla_total_size(sizeof(u8));
+
+ return size;
+}
+
+static int port_fill_reply(struct sk_buff *skb,
+ const struct ethnl_req_info *req_info,
+ const struct ethnl_reply_data *reply_data)
+{
+ bool compact = req_info->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
+ struct port_reply_data *reply = PORT_REPDATA(reply_data);
+ int ret, port_type = ETHTOOL_PORT_TYPE_MDI;
+
+ if (nla_put_u32(skb, ETHTOOL_A_PORT_ID, reply->port_id))
+ return -EMSGSIZE;
+
+ if (!reply->mii) {
+ ret = ethnl_put_bitset(skb, ETHTOOL_A_PORT_SUPPORTED_MODES,
+ reply->supported, NULL,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ link_mode_names, compact);
+ if (ret < 0)
+ return -EMSGSIZE;
+ } else {
+ ret = ethnl_put_bitset(skb, ETHTOOL_A_PORT_SUPPORTED_INTERFACES,
+ reply->interfaces, NULL,
+ PHY_INTERFACE_MODE_MAX,
+ phy_interface_names, compact);
+ if (ret < 0)
+ return -EMSGSIZE;
+ }
+
+ if (reply->mii || reply->sfp)
+ port_type = ETHTOOL_PORT_TYPE_SFP;
+
+ if (nla_put_u8(skb, ETHTOOL_A_PORT_TYPE, port_type) ||
+ nla_put_u8(skb, ETHTOOL_A_PORT_VACANT, reply->vacant))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+struct port_dump_ctx {
+ struct port_req_info *req_info;
+ struct port_reply_data *reply_data;
+ unsigned long ifindex;
+ unsigned long pos_portid;
+};
+
+static struct port_dump_ctx *
+port_dump_ctx_get(struct netlink_callback *cb)
+{
+ return (struct port_dump_ctx *)cb->ctx;
+}
+
+int ethnl_port_dump_start(struct netlink_callback *cb)
+{
+ const struct genl_dumpit_info *info = genl_dumpit_info(cb);
+ struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
+ struct nlattr **tb = info->info.attrs;
+ struct port_reply_data *reply_data;
+ struct port_req_info *req_info;
+ int ret;
+
+ BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
+
+ req_info = kzalloc_obj(*req_info);
+ if (!req_info)
+ return -ENOMEM;
+
+ reply_data = kmalloc_obj(*reply_data);
+ if (!reply_data) {
+ ret = -ENOMEM;
+ goto free_req_info;
+ }
+
+ ret = ethnl_parse_header_dev_get(&req_info->base, tb[ETHTOOL_A_PORT_HEADER],
+ genl_info_net(&info->info),
+ info->info.extack, false);
+ if (ret < 0)
+ goto free_rep_data;
+
+ ctx->ifindex = 0;
+
+ /* For filtered DUMP requests, let's just store the ifindex. We'll check
+ * again if the netdev is still there when looping over the netdev list
+ * in the DUMP loop.
+ */
+ if (req_info->base.dev) {
+ ctx->ifindex = req_info->base.dev->ifindex;
+ netdev_put(req_info->base.dev, &req_info->base.dev_tracker);
+ req_info->base.dev = NULL;
+ }
+
+ ctx->req_info = req_info;
+ ctx->reply_data = reply_data;
+
+ return 0;
+
+free_rep_data:
+ kfree(reply_data);
+free_req_info:
+ kfree(req_info);
+
+ return ret;
+}
+
+static int port_dump_one(struct sk_buff *skb, struct net_device *dev,
+ struct netlink_callback *cb)
+{
+ struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
+ void *ehdr;
+ int ret;
+
+ ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_PORT_GET_REPLY);
+ if (!ehdr)
+ return -EMSGSIZE;
+
+ memset(ctx->reply_data, 0, sizeof(struct port_reply_data));
+ ctx->reply_data->base.dev = dev;
+
+ rtnl_lock();
+ netdev_lock_ops(dev);
+
+ ret = port_prepare_data(&ctx->req_info->base, &ctx->reply_data->base,
+ genl_info_dump(cb));
+
+ netdev_unlock_ops(dev);
+ rtnl_unlock();
+
+ if (ret < 0)
+ goto out;
+
+ ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_PORT_HEADER);
+ if (ret < 0)
+ goto out;
+
+ ret = port_fill_reply(skb, &ctx->req_info->base, &ctx->reply_data->base);
+
+out:
+ ctx->reply_data->base.dev = NULL;
+ if (ret < 0)
+ genlmsg_cancel(skb, ehdr);
+ else
+ genlmsg_end(skb, ehdr);
+
+ return ret;
+}
+
+static int port_dump_one_dev(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
+ struct net_device *dev;
+ struct phy_port *port;
+ int ret;
+
+ dev = ctx->req_info->base.dev;
+
+ if (!dev->link_topo)
+ return 0;
+
+ xa_for_each_start(&dev->link_topo->ports, ctx->pos_portid, port,
+ ctx->pos_portid) {
+ ctx->req_info->port_id = ctx->pos_portid;
+
+ ret = port_dump_one(skb, dev, cb);
+ if (ret)
+ return ret;
+ }
+
+ ctx->pos_portid = 0;
+
+ return 0;
+}
+
+static int port_dump_all_dev(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
+ struct net *net = sock_net(skb->sk);
+ netdevice_tracker dev_tracker;
+ struct net_device *dev;
+ int ret = 0;
+
+ rcu_read_lock();
+ for_each_netdev_dump(net, dev, ctx->ifindex) {
+ netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
+ rcu_read_unlock();
+
+ ctx->req_info->base.dev = dev;
+ ret = port_dump_one_dev(skb, cb);
+
+ rcu_read_lock();
+ netdev_put(dev, &dev_tracker);
+ ctx->req_info->base.dev = NULL;
+
+ if (ret < 0 && ret != -EOPNOTSUPP) {
+ if (likely(skb->len))
+ ret = skb->len;
+ break;
+ }
+ ret = 0;
+ }
+ rcu_read_unlock();
+
+ return ret;
+}
+
+int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ const struct genl_dumpit_info *info = genl_dumpit_info(cb);
+ struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
+ int ret = 0;
+
+ if (ctx->ifindex) {
+ netdevice_tracker dev_tracker;
+ struct net_device *dev;
+
+ dev = netdev_get_by_index(genl_info_net(&info->info),
+ ctx->ifindex, &dev_tracker,
+ GFP_KERNEL);
+ if (!dev)
+ return -ENODEV;
+
+ ctx->req_info->base.dev = dev;
+ ret = port_dump_one_dev(skb, cb);
+ if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
+ ret = skb->len;
+
+ netdev_put(dev, &dev_tracker);
+ } else {
+ ret = port_dump_all_dev(skb, cb);
+ }
+
+ return ret;
+}
+
+int ethnl_port_dump_done(struct netlink_callback *cb)
+{
+ struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
+
+ kfree(ctx->req_info);
+ kfree(ctx->reply_data);
+
+ return 0;
+}
+
+const struct ethnl_request_ops ethnl_port_request_ops = {
+ .request_cmd = ETHTOOL_MSG_PORT_GET,
+ .reply_cmd = ETHTOOL_MSG_PORT_GET_REPLY,
+ .hdr_attr = ETHTOOL_A_PORT_HEADER,
+ .req_info_size = sizeof(struct port_req_info),
+ .reply_data_size = sizeof(struct port_reply_data),
+
+ .parse_request = port_parse_request,
+ .prepare_data = port_prepare_data,
+ .reply_size = port_reply_size,
+ .fill_reply = port_fill_reply,
+};
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [net-next,v7,05/10] net: phy: Represent PHY-less SFP modules with phy_port
2026-03-09 15:27 ` [PATCH net-next v7 05/10] net: phy: Represent PHY-less SFP modules with phy_port Maxime Chevallier
@ 2026-03-12 10:47 ` Paolo Abeni
2026-03-12 10:51 ` Paolo Abeni
0 siblings, 1 reply; 20+ messages in thread
From: Paolo Abeni @ 2026-03-12 10:47 UTC (permalink / raw)
To: maxime.chevallier
Cc: o.rempel, herve.codina, andrew, edumazet, linux-kernel,
hkallweit1, netdev, kuba, romain.gantois, f.fainelli,
christophe.leroy, kory.maincent, mwojtas, dimitri.fedrau,
nicveronese, pabeni, horms, daniel, kabel, linux, vladimir.oltean,
davem, thomas.petazzoni, bjorn
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: phy: Represent PHY-less SFP modules with phy_port
Now that the SFP bus infrastructure notifies when PHY-less modules are
connected, we can create a phy_port to represent it. Instead of letting
the SFP subsystem handle that, the Bus' upstream is in charge of
maintaining that phy_port and register it to the topology, as the
upstream (in this case a phy device) is directly interacting with the
underlying net_device.
Add a phy_caps helper to get the achievable modes on this module based
on what the phy_port representing the bus supports.
> diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
> index 4b06644866b2..faa58a780477 100644
> --- a/drivers/net/phy/phy_device.c
> +++ b/drivers/net/phy/phy_device.c
[ ... ]
> @@ -1483,6 +1483,8 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
> struct phy_device *phydev = upstream;
> struct net_device *dev = phydev->attached_dev;
>
> + phydev->has_sfp_mod_phy = true;
> +
> if (dev)
> return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev);
If phy_link_topo_add_phy() fails, does has_sfp_mod_phy get cleaned up
correctly?
Looking at the SFP framework, when phy_link_topo_add_phy() fails here,
sfp_sm_probe_phy() in sfp.c will free the PHY but won't call
disconnect_phy (because sfp->mod_phy was not set). This leaves
has_sfp_mod_phy stuck as true.
On a subsequent SFP module insertion, phy_sfp_module_start() checks
!phydev->has_sfp_mod_phy:
drivers/net/phy/phy_device.c:phy_sfp_module_start() {
if (!phydev->has_sfp_mod_phy)
return phy_add_sfp_mod_port(phydev);
}
Seeing has_sfp_mod_phy is true (stale), it skips creating the mod_port
for a PHY-less module. The module then won't be represented in the link
topology.
Would it be better to either set has_sfp_mod_phy after
phy_link_topo_add_phy() succeeds, or clear it in an error path?
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [net-next,v7,05/10] net: phy: Represent PHY-less SFP modules with phy_port
2026-03-12 10:47 ` [net-next,v7,05/10] " Paolo Abeni
@ 2026-03-12 10:51 ` Paolo Abeni
0 siblings, 0 replies; 20+ messages in thread
From: Paolo Abeni @ 2026-03-12 10:51 UTC (permalink / raw)
To: maxime.chevallier
Cc: o.rempel, herve.codina, andrew, edumazet, linux-kernel,
hkallweit1, netdev, kuba, romain.gantois, f.fainelli,
christophe.leroy, kory.maincent, mwojtas, dimitri.fedrau,
nicveronese, horms, daniel, kabel, linux, vladimir.oltean, davem,
thomas.petazzoni, bjorn
On 3/12/26 11:47 AM, Paolo Abeni wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
>
> For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
> ---
> net: phy: Represent PHY-less SFP modules with phy_port
>
> Now that the SFP bus infrastructure notifies when PHY-less modules are
> connected, we can create a phy_port to represent it. Instead of letting
> the SFP subsystem handle that, the Bus' upstream is in charge of
> maintaining that phy_port and register it to the topology, as the
> upstream (in this case a phy device) is directly interacting with the
> underlying net_device.
>
> Add a phy_caps helper to get the achievable modes on this module based
> on what the phy_port representing the bus supports.
>
>> diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
>> index 4b06644866b2..faa58a780477 100644
>> --- a/drivers/net/phy/phy_device.c
>> +++ b/drivers/net/phy/phy_device.c
>
> [ ... ]
>
>> @@ -1483,6 +1483,8 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
>> struct phy_device *phydev = upstream;
>> struct net_device *dev = phydev->attached_dev;
>>
>> + phydev->has_sfp_mod_phy = true;
>> +
>> if (dev)
>> return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev);
>
> If phy_link_topo_add_phy() fails, does has_sfp_mod_phy get cleaned up
> correctly?
>
> Looking at the SFP framework, when phy_link_topo_add_phy() fails here,
> sfp_sm_probe_phy() in sfp.c will free the PHY but won't call
> disconnect_phy (because sfp->mod_phy was not set). This leaves
> has_sfp_mod_phy stuck as true.
>
> On a subsequent SFP module insertion, phy_sfp_module_start() checks
> !phydev->has_sfp_mod_phy:
>
> drivers/net/phy/phy_device.c:phy_sfp_module_start() {
> if (!phydev->has_sfp_mod_phy)
> return phy_add_sfp_mod_port(phydev);
> }
>
> Seeing has_sfp_mod_phy is true (stale), it skips creating the mod_port
> for a PHY-less module. The module then won't be represented in the link
> topology.
>
> Would it be better to either set has_sfp_mod_phy after
> phy_link_topo_add_phy() succeeds, or clear it in an error path?
Out of sheer ignorance IDK if phy_sfp_module_start() could really happen
in practice after a phy_sfp_connect_phy() failure, but the inconsistency
is likely worthy a cleanup?
Leaving the series alive in PW, in case the phy crew disagree or a
follow-up is preferred to a repost.
/P
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-09 15:27 ` [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports Maxime Chevallier
@ 2026-03-13 2:07 ` Jakub Kicinski
2026-03-13 8:35 ` Maxime Chevallier
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Jakub Kicinski @ 2026-03-13 2:07 UTC (permalink / raw)
To: Maxime Chevallier
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
On Mon, 9 Mar 2026 16:27:46 +0100 Maxime Chevallier wrote:
> Expose the phy_port information to userspace, so that we can know how
> many ports are available on a given interface, as well as their
> capabilities. For MDI ports, we report the list of supported linkmodes
> based on what the PHY that drives this port says.
> For MII ports, i.e. empty SFP cages, we report the MII linkmodes that we
> can output on this port.
> diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
> index 6e5f0f4f815a..90674aed7777 100644
> --- a/net/ethtool/netlink.c
> +++ b/net/ethtool/netlink.c
> @@ -18,6 +18,8 @@ static u32 ethnl_bcast_seq;
> ETHTOOL_FLAG_OMIT_REPLY)
> #define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS)
>
> +char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN] __ro_after_init;
> +
> const struct nla_policy ethnl_header_policy[] = {
> [ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
> [ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
> @@ -421,6 +423,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
> [ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops,
> [ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops,
> [ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops,
> + [ETHTOOL_MSG_PORT_GET] = ðnl_port_request_ops,
> };
>
> static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
> @@ -1544,6 +1547,16 @@ static const struct genl_ops ethtool_genl_ops[] = {
> .policy = ethnl_mse_get_policy,
> .maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
> },
> + {
> + .cmd = ETHTOOL_MSG_PORT_GET,
> + .doit = ethnl_default_doit,
> + .start = ethnl_port_dump_start,
> + .dumpit = ethnl_port_dumpit,
> + .done = ethnl_port_dump_done,
> + .policy = ethnl_port_get_policy,
> + .maxattr = ARRAY_SIZE(ethnl_port_get_policy) - 1,
> + },
> +
spurious nl
> };
>
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2026 Bootlin
> + *
> + */
so many lines for a copyright :(
> +#include "common.h"
> +#include "bitset.h"
> +#include "netlink.h"
alpha sort
> +#define PORT_REQINFO(__req_base) \
> + container_of(__req_base, struct port_req_info, base)
> +
> +#define PORT_REPDATA(__reply_base) \
> + container_of(__reply_base, struct port_reply_data, base)
> +
> +const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 1] = {
> + [ETHTOOL_A_PORT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
> + [ETHTOOL_A_PORT_ID] = { .type = NLA_U32},
misaligned bracket
> +};
> +
> +static int port_parse_request(struct ethnl_req_info *req_info,
> + struct nlattr **tb,
> + struct netlink_ext_ack *extack)
> +{
> + struct port_req_info *request = PORT_REQINFO(req_info);
> +
> + /* PORT id is required for GET requests */
> + if (tb[ETHTOOL_A_PORT_ID])
GENL_REQ_ATTR_CHECK ?
I guess you'll have to move it to prepare cause we don't get info
here :(
> + request->port_id = nla_get_u32(tb[ETHTOOL_A_PORT_ID]);
> +
> + if (!request->port_id) {
NLA_POLICY_MIN(, 1)
> + NL_SET_ERR_MSG(extack, "port id missing");
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> + /* ETHTOOL_A_PORT_TYPE */
> + size += nla_total_size(sizeof(u8));
please don't use u8, it wastes 3B on padding
> + /* ETHTOOL_A_PORT_VACANT */
> + size += nla_total_size(sizeof(u8));
> +
> + return size;
> +}
> +
> +static int port_fill_reply(struct sk_buff *skb,
> + const struct ethnl_req_info *req_info,
> + const struct ethnl_reply_data *reply_data)
> +{
> + bool compact = req_info->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
> + struct port_reply_data *reply = PORT_REPDATA(reply_data);
> + int ret, port_type = ETHTOOL_PORT_TYPE_MDI;
> +
> + if (nla_put_u32(skb, ETHTOOL_A_PORT_ID, reply->port_id))
> + return -EMSGSIZE;
> +
> + if (!reply->mii) {
> + ret = ethnl_put_bitset(skb, ETHTOOL_A_PORT_SUPPORTED_MODES,
> + reply->supported, NULL,
> + __ETHTOOL_LINK_MODE_MASK_NBITS,
> + link_mode_names, compact);
> + if (ret < 0)
> + return -EMSGSIZE;
return ret?
> +static int port_dump_all_dev(struct sk_buff *skb, struct netlink_callback *cb)
> +{
> + struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
> + struct net *net = sock_net(skb->sk);
> + netdevice_tracker dev_tracker;
> + struct net_device *dev;
> + int ret = 0;
> +
> + rcu_read_lock();
> + for_each_netdev_dump(net, dev, ctx->ifindex) {
> + netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
> + rcu_read_unlock();
> +
> + ctx->req_info->base.dev = dev;
> + ret = port_dump_one_dev(skb, cb);
> +
> + rcu_read_lock();
> + netdev_put(dev, &dev_tracker);
> + ctx->req_info->base.dev = NULL;
> +
> + if (ret < 0 && ret != -EOPNOTSUPP) {
> + if (likely(skb->len))
> + ret = skb->len;
Why the skb->len check? netlink plumbing should handle that these days
if err = -EMSGSIZE during dump.
> + break;
> + }
> + ret = 0;
> + }
> + rcu_read_unlock();
> +
> + return ret;
> +}
> +
> +int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
> +{
> + const struct genl_dumpit_info *info = genl_dumpit_info(cb);
> + struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
> + int ret = 0;
> +
> + if (ctx->ifindex) {
> + netdevice_tracker dev_tracker;
> + struct net_device *dev;
> +
> + dev = netdev_get_by_index(genl_info_net(&info->info),
> + ctx->ifindex, &dev_tracker,
> + GFP_KERNEL);
> + if (!dev)
> + return -ENODEV;
> +
> + ctx->req_info->base.dev = dev;
> + ret = port_dump_one_dev(skb, cb);
> + if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
> + ret = skb->len;
ditto
> + netdev_put(dev, &dev_tracker);
> + } else {
> + ret = port_dump_all_dev(skb, cb);
> + }
> +
> + return ret;
> +}
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-13 2:07 ` Jakub Kicinski
@ 2026-03-13 8:35 ` Maxime Chevallier
2026-03-18 7:28 ` Maxime Chevallier
2026-03-19 9:27 ` Maxime Chevallier
2 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-13 8:35 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Hi Jakub,
Thanks a lot for taking a look at this :)
On 13/03/2026 03:07, Jakub Kicinski wrote:
> On Mon, 9 Mar 2026 16:27:46 +0100 Maxime Chevallier wrote:
>> Expose the phy_port information to userspace, so that we can know how
>> many ports are available on a given interface, as well as their
>> capabilities. For MDI ports, we report the list of supported linkmodes
>> based on what the PHY that drives this port says.
>> For MII ports, i.e. empty SFP cages, we report the MII linkmodes that we
>> can output on this port.
>
>> diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
>> index 6e5f0f4f815a..90674aed7777 100644
>> --- a/net/ethtool/netlink.c
>> +++ b/net/ethtool/netlink.c
>> @@ -18,6 +18,8 @@ static u32 ethnl_bcast_seq;
>> ETHTOOL_FLAG_OMIT_REPLY)
>> #define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS)
>>
>> +char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN] __ro_after_init;
>> +
>> const struct nla_policy ethnl_header_policy[] = {
>> [ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
>> [ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
>> @@ -421,6 +423,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
>> [ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops,
>> [ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops,
>> [ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops,
>> + [ETHTOOL_MSG_PORT_GET] = ðnl_port_request_ops,
>> };
>>
>> static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
>> @@ -1544,6 +1547,16 @@ static const struct genl_ops ethtool_genl_ops[] = {
>> .policy = ethnl_mse_get_policy,
>> .maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
>> },
>> + {
>> + .cmd = ETHTOOL_MSG_PORT_GET,
>> + .doit = ethnl_default_doit,
>> + .start = ethnl_port_dump_start,
>> + .dumpit = ethnl_port_dumpit,
>> + .done = ethnl_port_dump_done,
>> + .policy = ethnl_port_get_policy,
>> + .maxattr = ARRAY_SIZE(ethnl_port_get_policy) - 1,
>> + },
>> +
>
> spurious nl
>
>> };
>>
>
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright 2026 Bootlin
>> + *
>> + */
>
> so many lines for a copyright :(
>
>> +#include "common.h"
>> +#include "bitset.h"
>> +#include "netlink.h"
>
> alpha sort
>
>> +#define PORT_REQINFO(__req_base) \
>> + container_of(__req_base, struct port_req_info, base)
>> +
>> +#define PORT_REPDATA(__reply_base) \
>> + container_of(__reply_base, struct port_reply_data, base)
>> +
>> +const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 1] = {
>> + [ETHTOOL_A_PORT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
>> + [ETHTOOL_A_PORT_ID] = { .type = NLA_U32},
>
> misaligned bracket
>
>> +};
>> +
>> +static int port_parse_request(struct ethnl_req_info *req_info,
>> + struct nlattr **tb,
>> + struct netlink_ext_ack *extack)
>> +{
>> + struct port_req_info *request = PORT_REQINFO(req_info);
>> +
>> + /* PORT id is required for GET requests */
>> + if (tb[ETHTOOL_A_PORT_ID])
>
> GENL_REQ_ATTR_CHECK ?
> I guess you'll have to move it to prepare cause we don't get info
> here :(
>
>> + request->port_id = nla_get_u32(tb[ETHTOOL_A_PORT_ID]);
>> +
>> + if (!request->port_id) {
>
> NLA_POLICY_MIN(, 1)
>
>> + NL_SET_ERR_MSG(extack, "port id missing");
>> + return -ENODEV;
>> + }
>> +
>> + return 0;
>> +}
>
>> + /* ETHTOOL_A_PORT_TYPE */
>> + size += nla_total_size(sizeof(u8));
>
> please don't use u8, it wastes 3B on padding
>
>> + /* ETHTOOL_A_PORT_VACANT */
>> + size += nla_total_size(sizeof(u8));
>> +
>> + return size;
>> +}
>> +
>> +static int port_fill_reply(struct sk_buff *skb,
>> + const struct ethnl_req_info *req_info,
>> + const struct ethnl_reply_data *reply_data)
>> +{
>> + bool compact = req_info->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
>> + struct port_reply_data *reply = PORT_REPDATA(reply_data);
>> + int ret, port_type = ETHTOOL_PORT_TYPE_MDI;
>> +
>> + if (nla_put_u32(skb, ETHTOOL_A_PORT_ID, reply->port_id))
>> + return -EMSGSIZE;
>> +
>> + if (!reply->mii) {
>> + ret = ethnl_put_bitset(skb, ETHTOOL_A_PORT_SUPPORTED_MODES,
>> + reply->supported, NULL,
>> + __ETHTOOL_LINK_MODE_MASK_NBITS,
>> + link_mode_names, compact);
>> + if (ret < 0)
>> + return -EMSGSIZE;
>
> return ret?
>
>> +static int port_dump_all_dev(struct sk_buff *skb, struct netlink_callback *cb)
>> +{
>> + struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
>> + struct net *net = sock_net(skb->sk);
>> + netdevice_tracker dev_tracker;
>> + struct net_device *dev;
>> + int ret = 0;
>> +
>> + rcu_read_lock();
>> + for_each_netdev_dump(net, dev, ctx->ifindex) {
>> + netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
>> + rcu_read_unlock();
>> +
>> + ctx->req_info->base.dev = dev;
>> + ret = port_dump_one_dev(skb, cb);
>> +
>> + rcu_read_lock();
>> + netdev_put(dev, &dev_tracker);
>> + ctx->req_info->base.dev = NULL;
>> +
>> + if (ret < 0 && ret != -EOPNOTSUPP) {
>> + if (likely(skb->len))
>> + ret = skb->len;
>
> Why the skb->len check? netlink plumbing should handle that these days
> if err = -EMSGSIZE during dump.
>
>> + break;
>> + }
>> + ret = 0;
>> + }
>> + rcu_read_unlock();
>> +
>> + return ret;
>> +}
>> +
>> +int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
>> +{
>> + const struct genl_dumpit_info *info = genl_dumpit_info(cb);
>> + struct port_dump_ctx *ctx = port_dump_ctx_get(cb);
>> + int ret = 0;
>> +
>> + if (ctx->ifindex) {
>> + netdevice_tracker dev_tracker;
>> + struct net_device *dev;
>> +
>> + dev = netdev_get_by_index(genl_info_net(&info->info),
>> + ctx->ifindex, &dev_tracker,
>> + GFP_KERNEL);
>> + if (!dev)
>> + return -ENODEV;
>> +
>> + ctx->req_info->base.dev = dev;
>> + ret = port_dump_one_dev(skb, cb);
>> + if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
>> + ret = skb->len;
>
> ditto
>
>> + netdev_put(dev, &dev_tracker);
>> + } else {
>> + ret = port_dump_all_dev(skb, cb);
>> + }
>> +
>> + return ret;
>> +}
I agree with all you comments, I'll address for next round :)
Maxime
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-13 2:07 ` Jakub Kicinski
2026-03-13 8:35 ` Maxime Chevallier
@ 2026-03-18 7:28 ` Maxime Chevallier
2026-03-18 22:00 ` Jakub Kicinski
2026-03-19 9:27 ` Maxime Chevallier
2 siblings, 1 reply; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-18 7:28 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
Hi again Jakub
On 13/03/2026 03:07, Jakub Kicinski wrote:
> On Mon, 9 Mar 2026 16:27:46 +0100 Maxime Chevallier wrote:
>> Expose the phy_port information to userspace, so that we can know how
>> many ports are available on a given interface, as well as their
>> capabilities. For MDI ports, we report the list of supported linkmodes
>> based on what the PHY that drives this port says.
>> For MII ports, i.e. empty SFP cages, we report the MII linkmodes that we
>> can output on this port.
>
[...]
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright 2026 Bootlin
>> + *
>> + */
>
> so many lines for a copyright :(
>
>> +#include "common.h"
>> +#include "bitset.h"
>> +#include "netlink.h"
>
> alpha sort
So that's not as easy as it looks, "bitset.h" is really barebones and
needs definitions for stuff like ETH_GSTRING_LEN, but also all the
u32/bool types, etc.
In all files in net/ethtool/*.c the local includes list isnt' properly
sorted due to that.
So either we fix that, or keep them sorted that way. If you have a
preference let me know, for now I'm letting these out of order but I
can sort this out (pun intended) if you think it's worth :)
Maxime
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-18 7:28 ` Maxime Chevallier
@ 2026-03-18 22:00 ` Jakub Kicinski
0 siblings, 0 replies; 20+ messages in thread
From: Jakub Kicinski @ 2026-03-18 22:00 UTC (permalink / raw)
To: Maxime Chevallier
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
On Wed, 18 Mar 2026 08:28:00 +0100 Maxime Chevallier wrote:
> >> +#include "common.h"
> >> +#include "bitset.h"
> >> +#include "netlink.h"
> >
> > alpha sort
>
> So that's not as easy as it looks, "bitset.h" is really barebones and
> needs definitions for stuff like ETH_GSTRING_LEN, but also all the
> u32/bool types, etc.
>
> In all files in net/ethtool/*.c the local includes list isnt' properly
> sorted due to that.
That's not right, let's fix it. Headers should be self-sufficient.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-13 2:07 ` Jakub Kicinski
2026-03-13 8:35 ` Maxime Chevallier
2026-03-18 7:28 ` Maxime Chevallier
@ 2026-03-19 9:27 ` Maxime Chevallier
2026-03-19 14:53 ` Jakub Kicinski
2 siblings, 1 reply; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-19 9:27 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
On 13/03/2026 03:07, Jakub Kicinski wrote:
> On Mon, 9 Mar 2026 16:27:46 +0100 Maxime Chevallier wrote:
>> Expose the phy_port information to userspace, so that we can know how
>> many ports are available on a given interface, as well as their
>> capabilities. For MDI ports, we report the list of supported linkmodes
>> based on what the PHY that drives this port says.
>> For MII ports, i.e. empty SFP cages, we report the MII linkmodes that we
>> can output on this port.
>
>> diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
>> index 6e5f0f4f815a..90674aed7777 100644
>> --- a/net/ethtool/netlink.c
>> +++ b/net/ethtool/netlink.c
>> @@ -18,6 +18,8 @@ static u32 ethnl_bcast_seq;
>> ETHTOOL_FLAG_OMIT_REPLY)
>> #define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS)
>>
>> +char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN] __ro_after_init;
>> +
>> const struct nla_policy ethnl_header_policy[] = {
>> [ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
>> [ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
>> @@ -421,6 +423,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
>> [ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops,
>> [ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops,
>> [ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops,
>> + [ETHTOOL_MSG_PORT_GET] = ðnl_port_request_ops,
>> };
>>
>> static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
>> @@ -1544,6 +1547,16 @@ static const struct genl_ops ethtool_genl_ops[] = {
>> .policy = ethnl_mse_get_policy,
>> .maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
>> },
>> + {
>> + .cmd = ETHTOOL_MSG_PORT_GET,
>> + .doit = ethnl_default_doit,
>> + .start = ethnl_port_dump_start,
>> + .dumpit = ethnl_port_dumpit,
>> + .done = ethnl_port_dump_done,
>> + .policy = ethnl_port_get_policy,
>> + .maxattr = ARRAY_SIZE(ethnl_port_get_policy) - 1,
>> + },
>> +
>
> spurious nl
>
>> };
>>
>
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright 2026 Bootlin
>> + *
>> + */
>
> so many lines for a copyright :(
>
>> +#include "common.h"
>> +#include "bitset.h"
>> +#include "netlink.h"
>
> alpha sort
>
>> +#define PORT_REQINFO(__req_base) \
>> + container_of(__req_base, struct port_req_info, base)
>> +
>> +#define PORT_REPDATA(__reply_base) \
>> + container_of(__reply_base, struct port_reply_data, base)
>> +
>> +const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 1] = {
>> + [ETHTOOL_A_PORT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
>> + [ETHTOOL_A_PORT_ID] = { .type = NLA_U32},
>
> misaligned bracket
>
>> +};
>> +
>> +static int port_parse_request(struct ethnl_req_info *req_info,
>> + struct nlattr **tb,
>> + struct netlink_ext_ack *extack)
>> +{
>> + struct port_req_info *request = PORT_REQINFO(req_info);
>> +
>> + /* PORT id is required for GET requests */
>> + if (tb[ETHTOOL_A_PORT_ID])
>
> GENL_REQ_ATTR_CHECK ?
> I guess you'll have to move it to prepare cause we don't get info
> here :(
Sorry for my comments coming apart on your review, I'm getting this new
iteration done in-between other tasks :(
I can't move that check to the prepare_data() step, as we need the
request->port_id field to be populated at that stage for the DUMP
op (for each port, we set request->port_id, then we call prepare_data
and so on).
Maxime
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-19 9:27 ` Maxime Chevallier
@ 2026-03-19 14:53 ` Jakub Kicinski
2026-03-19 17:52 ` Maxime Chevallier
0 siblings, 1 reply; 20+ messages in thread
From: Jakub Kicinski @ 2026-03-19 14:53 UTC (permalink / raw)
To: Maxime Chevallier
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
On Thu, 19 Mar 2026 10:27:11 +0100 Maxime Chevallier wrote:
> > GENL_REQ_ATTR_CHECK ?
> > I guess you'll have to move it to prepare cause we don't get info
> > here :(
>
> Sorry for my comments coming apart on your review, I'm getting this new
> iteration done in-between other tasks :(
>
> I can't move that check to the prepare_data() step, as we need the
> request->port_id field to be populated at that stage for the DUMP
> op (for each port, we set request->port_id, then we call prepare_data
> and so on).
Not sure I get what you're saying - that it doesn't work or that it
leads to "duplicated" work? Alternative is to add info to parse_request
args which I suspect is useful long term anyway.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports
2026-03-19 14:53 ` Jakub Kicinski
@ 2026-03-19 17:52 ` Maxime Chevallier
0 siblings, 0 replies; 20+ messages in thread
From: Maxime Chevallier @ 2026-03-19 17:52 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, Andrew Lunn, Eric Dumazet, Paolo Abeni, Russell King,
Heiner Kallweit, netdev, linux-kernel, thomas.petazzoni,
Christophe Leroy, Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Björn Töpel
On 19/03/2026 15:53, Jakub Kicinski wrote:
> On Thu, 19 Mar 2026 10:27:11 +0100 Maxime Chevallier wrote:
>>> GENL_REQ_ATTR_CHECK ?
>>> I guess you'll have to move it to prepare cause we don't get info
>>> here :(
>>
>> Sorry for my comments coming apart on your review, I'm getting this new
>> iteration done in-between other tasks :(
>>
>> I can't move that check to the prepare_data() step, as we need the
>> request->port_id field to be populated at that stage for the DUMP
>> op (for each port, we set request->port_id, then we call prepare_data
>> and so on).
>
> Not sure I get what you're saying - that it doesn't work or that it
> leads to "duplicated" work?
What I mean is that it doesn't work yeah.
> Alternative is to add info to parse_request
> args which I suspect is useful long term anyway.
good point, I like the idea a lot :) OK let me give this a try, I need
to see how that looks like.
Thanks for the suggestion,
Maxime
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-03-19 17:52 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 15:27 [PATCH net-next v7 00/10] net: phy_port: SFP modules representation and phy_port listing Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 01/10] net: phy: phy_link_topology: Add a helper for opportunistic alloc Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 02/10] net: phy: phy_link_topology: Track ports in phy_link_topology Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 03/10] net: phylink: Register a phy_port for MAC-driven SFP busses Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 04/10] net: phy: Create SFP phy_port before registering upstream Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 05/10] net: phy: Represent PHY-less SFP modules with phy_port Maxime Chevallier
2026-03-12 10:47 ` [net-next,v7,05/10] " Paolo Abeni
2026-03-12 10:51 ` Paolo Abeni
2026-03-09 15:27 ` [PATCH net-next v7 06/10] net: phylink: " Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 07/10] net: phy: phy_port: Store information about a MII port's vacant state Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 08/10] net: phy: phy_link_topology: Add a helper to retrieve ports Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 09/10] netlink: specs: Add ethernet port listing with ethtool Maxime Chevallier
2026-03-09 15:27 ` [PATCH net-next v7 10/10] net: ethtool: Introduce ethtool command to list ports Maxime Chevallier
2026-03-13 2:07 ` Jakub Kicinski
2026-03-13 8:35 ` Maxime Chevallier
2026-03-18 7:28 ` Maxime Chevallier
2026-03-18 22:00 ` Jakub Kicinski
2026-03-19 9:27 ` Maxime Chevallier
2026-03-19 14:53 ` Jakub Kicinski
2026-03-19 17:52 ` Maxime Chevallier
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox