Hi everyone,
Here's a V2 for the netdevsim PHY support, including a bugfix for NETDEVSIM=m as well as a round of shellcheck cleanups for ethtool-phy.sh.
The idea of this series is to allow attaching virtual PHY devices to netdevsim, so that we can test PHY-related ethtool commands. This can be extended in the future for phylib testing as well.
V1: https://lore.kernel.org/netdev/20250702082806.706973-1-maxime.chevallier@boo...
Maxime Chevallier (3): net: netdevsim: Add PHY support in netdevsim selftests: ethtool: Drop the unused old_netdevs variable selftests: ethtool: Introduce ethernet PHY selftests on netdevsim
drivers/net/netdevsim/Makefile | 4 + drivers/net/netdevsim/dev.c | 2 + drivers/net/netdevsim/netdev.c | 8 + drivers/net/netdevsim/netdevsim.h | 25 ++ drivers/net/netdevsim/phy.c | 398 ++++++++++++++++++ .../selftests/drivers/net/netdevsim/config | 1 + .../drivers/net/netdevsim/ethtool-common.sh | 19 +- .../drivers/net/netdevsim/ethtool-phy.sh | 64 +++ 8 files changed, 518 insertions(+), 3 deletions(-) create mode 100644 drivers/net/netdevsim/phy.c create mode 100755 tools/testing/selftests/drivers/net/netdevsim/ethtool-phy.sh
With the introduction of phy_link_topology, we have the ability to keep track of PHY devices that sit behind a net_device. While we still can only attach one single PHY to a netdev, we can look at all these PHYs through netlink, with the ETHTOOL_MSG_PHY_GET command.
Moreover, netlink commands that are targeting PHY devices also now allow specifying which PHY we want to address in a given netlink command.
That whole process comes with its own complexity, and a few bugs were dicovered over the months following the introduction of phy_link_topology.
As devices with multiple PHYs are fairly uncommon, testing the corner cases of multi-phy setups proves to be difficult.
To that extent, introduce PHY support in netdevsim. The main goal (for now) is not to be able to test PHYlib, but these phy-specific netlink interfaces.
These netdevsim PHYs use a custom phy_driver that relies on re-implementing the phy_driver callbacks. In other words, this is not a PHY driver that relies on mdio emulation, and will not work with any of the genphy helpers.
The debugfs API for PHY creation and deletion works as follows :
PHY device creation :
echo $ID > /sys/kernel/debug/netdevsim/netdevsimXXX/ports/YY/phy_add
if $ID is 0, then the PHY parent will be the netdev corresponding to the port's netdev. The first PHY that is added with the netdev as a parent will be attached to the netdev.
if $ID > 0, the index must correspond to a previously added PHY. This allows creating any arbitrary tree of PHYs.
Upon PHY addition, a phyXX directory will be created, XX being the phyindex of the PHY in the topology:
[...]/ports/YY/phyXX/
This directory contains a "link" file, allowing to toggle the virtual PHY's link state.
One can then list the PHYs with "ethtool --show-phys ethX".
Signed-off-by: Maxime Chevallier maxime.chevallier@bootlin.com --- drivers/net/netdevsim/Makefile | 4 + drivers/net/netdevsim/dev.c | 2 + drivers/net/netdevsim/netdev.c | 8 + drivers/net/netdevsim/netdevsim.h | 25 ++ drivers/net/netdevsim/phy.c | 398 ++++++++++++++++++++++++++++++ 5 files changed, 437 insertions(+) create mode 100644 drivers/net/netdevsim/phy.c
diff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile index f8de93bc5f5b..49f4c515e5e3 100644 --- a/drivers/net/netdevsim/Makefile +++ b/drivers/net/netdevsim/Makefile @@ -21,3 +21,7 @@ endif ifneq ($(CONFIG_MACSEC),) netdevsim-objs += macsec.o endif + +ifneq ($(CONFIG_PHYLIB),) +netdevsim-objs += phy.o +endif diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index b3647691060c..1ebb4f5b3bdd 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -1510,6 +1510,7 @@ static int nsim_dev_reload_create(struct nsim_dev *nsim_dev, devlink = priv_to_devlink(nsim_dev); nsim_dev = devlink_priv(devlink); INIT_LIST_HEAD(&nsim_dev->port_list); + INIT_LIST_HEAD(&nsim_dev->phy_list); nsim_dev->fw_update_status = true; nsim_dev->fw_update_overwrite_mask = 0;
@@ -1583,6 +1584,7 @@ int nsim_drv_probe(struct nsim_bus_dev *nsim_bus_dev) nsim_dev->switch_id.id_len = sizeof(nsim_dev->switch_id.id); get_random_bytes(nsim_dev->switch_id.id, nsim_dev->switch_id.id_len); INIT_LIST_HEAD(&nsim_dev->port_list); + INIT_LIST_HEAD(&nsim_dev->phy_list); nsim_dev->fw_update_status = true; nsim_dev->fw_update_overwrite_mask = 0; nsim_dev->max_macs = NSIM_DEV_MAX_MACS_DEFAULT; diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index e36d3e846c2d..ff891536f691 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -952,6 +952,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
nsim_macsec_init(ns); nsim_ipsec_init(ns); + nsim_phy_init(ns);
err = register_netdevice(ns->netdev); if (err) @@ -968,6 +969,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns) return 0;
err_ipsec_teardown: + nsim_phy_teardown(ns); nsim_ipsec_teardown(ns); nsim_macsec_teardown(ns); nsim_bpf_uninit(ns); @@ -1058,6 +1060,7 @@ void nsim_destroy(struct netdevsim *ns) RCU_INIT_POINTER(ns->peer, NULL); unregister_netdevice(dev); if (nsim_dev_port_is_pf(ns->nsim_dev_port)) { + nsim_phy_teardown(ns); nsim_macsec_teardown(ns); nsim_ipsec_teardown(ns); nsim_bpf_uninit(ns); @@ -1098,6 +1101,10 @@ static int __init nsim_module_init(void) { int err;
+ err = nsim_phy_drv_register(); + if (err) + return err; + err = nsim_dev_init(); if (err) return err; @@ -1124,6 +1131,7 @@ static void __exit nsim_module_exit(void) rtnl_link_unregister(&nsim_link_ops); nsim_bus_exit(); nsim_dev_exit(); + nsim_phy_drv_unregister(); }
module_init(nsim_module_init); diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index 809dd29fc5fe..c73d16e67a3c 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -314,6 +314,7 @@ struct nsim_dev { struct list_head bpf_bound_maps; struct netdev_phys_item_id switch_id; struct list_head port_list; + struct list_head phy_list; bool fw_update_status; u32 fw_update_overwrite_mask; u32 max_macs; @@ -419,6 +420,30 @@ static inline void nsim_macsec_teardown(struct netdevsim *ns) } #endif
+#if IS_ENABLED(CONFIG_PHYLIB) +void nsim_phy_init(struct netdevsim *ns); +void nsim_phy_teardown(struct netdevsim *dev); +int nsim_phy_drv_register(void); +void nsim_phy_drv_unregister(void); +#else +static inline void nsim_phy_init(struct netdevsim *ns) +{ +} + +static inline void nsim_phy_teardown(struct netdevsim *ns); +{ +} + +static inline int __init nsim_phy_drv_register(void) +{ + return 0; +} + +static inline void __exit nsim_phy_drv_unregister(void) +{ +} +#endif + struct nsim_bus_dev { struct device dev; struct list_head list; diff --git a/drivers/net/netdevsim/phy.c b/drivers/net/netdevsim/phy.c new file mode 100644 index 000000000000..237931e568fb --- /dev/null +++ b/drivers/net/netdevsim/phy.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Maxime Chevallier maxime.chevallier@bootlin.com + +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/slab.h> +#include <linux/phy.h> +#include <linux/phy_fixed.h> +#include <linux/phy_link_topology.h> +#include <linux/platform_device.h> + +#include "netdevsim.h" + +static atomic_t bus_num = ATOMIC_INIT(0); + +/* Dumb MDIO bus for the virtual PHY to sit on */ +struct nsim_mdiobus { + struct platform_device *pdev; + struct mii_bus *mii; +}; + +static int nsim_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +{ + return 0; +} + +static int nsim_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, + u16 val) +{ + return 0; +} + +struct nsim_phy_device { + struct phy_device *phy; + struct dentry *phy_dir; + + struct list_head node; + + bool link; +}; + +/* Virtual PHY driver for netdevsim */ +static int nsim_match_phy_device(struct phy_device *phydev, + const struct phy_driver *drv) +{ + struct mii_bus *mii = phydev->mdio.bus; + + return (mii->read == nsim_mdio_read) && + (mii->write == nsim_mdio_write); +} + +static int nsim_get_features(struct phy_device *phydev) +{ + /* Act like a 1G PHY */ + linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, phydev->supported); + + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported); + + return 0; +} + +static int nsim_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int nsim_read_status(struct phy_device *phydev) +{ + struct nsim_phy_device *ns_phy = phydev->priv; + + if (!ns_phy) + return 0; + + if (ns_phy->link) { + phydev->speed = SPEED_1000; + phydev->duplex = DUPLEX_FULL; + } else { + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + } + + phydev->link = ns_phy->link; + + return 0; +} + +static struct phy_driver nsim_virtual_phy_drv[] = { + { + .name = "Netdevsim virtual PHY driver", + .get_features = nsim_get_features, + .match_phy_device = nsim_match_phy_device, + .config_aneg = nsim_config_aneg, + .read_status = nsim_read_status, + }, +}; + +static struct nsim_mdiobus *nsim_mdiobus_create(void) +{ + struct nsim_mdiobus *mb; + + mb = kzalloc(sizeof(*mb), GFP_KERNEL); + if (!mb) + return NULL; + + mb->pdev = platform_device_register_simple("nsim MDIO bus", + atomic_read(&bus_num), + NULL, 0); + if (IS_ERR(mb->pdev)) + goto free_mb; + + mb->mii = mdiobus_alloc(); + if (!mb->mii) + goto free_pdev; + + snprintf(mb->mii->id, MII_BUS_ID_SIZE, "nsim-%d", atomic_read(&bus_num)); + atomic_inc(&bus_num); + mb->mii->name = "nsim MDIO Bus"; + mb->mii->priv = mb; + mb->mii->parent = &mb->pdev->dev; + mb->mii->read = &nsim_mdio_read; + mb->mii->write = &nsim_mdio_write; + mb->mii->phy_mask = ~0; + + if (mdiobus_register(mb->mii)) + goto free_mdiobus; + + return mb; + +free_mdiobus: + atomic_dec(&bus_num); + mdiobus_free(mb->mii); +free_pdev: + platform_device_unregister(mb->pdev); +free_mb: + kfree(mb); + + return NULL; +} + +static void nsim_mdiobus_destroy(struct nsim_mdiobus *mb) +{ + mdiobus_unregister(mb->mii); + mdiobus_free(mb->mii); + atomic_dec(&bus_num); + platform_device_unregister(mb->pdev); + kfree(mb); +} + +static struct nsim_phy_device *nsim_phy_register(void) +{ + struct nsim_phy_device *ns_phy; + struct nsim_mdiobus *mb; + int err; + + mb = nsim_mdiobus_create(); + if (IS_ERR(mb)) + return ERR_CAST(mb); + + ns_phy = kzalloc(sizeof(*ns_phy), GFP_KERNEL); + if (!ns_phy) { + err = -ENOMEM; + goto out_mdio; + } + + INIT_LIST_HEAD(&ns_phy->node); + + ns_phy->phy = get_phy_device(mb->mii, 0, false); + if (IS_ERR(ns_phy->phy)) { + err = PTR_ERR(ns_phy->phy); + goto out_phy_free; + } + + err = phy_device_register(ns_phy->phy); + if (err) + goto out_phy; + + ns_phy->phy->priv = ns_phy; + + return ns_phy; + +out_phy: + phy_device_free(ns_phy->phy); +out_phy_free: + kfree(ns_phy); +out_mdio: + nsim_mdiobus_destroy(mb); + return ERR_PTR(err); +} + +static void nsim_phy_destroy(struct nsim_phy_device *ns_phy) +{ + struct phy_device *phydev = ns_phy->phy; + struct mii_bus *mii = phydev->mdio.bus; + struct nsim_mdiobus *mb = mii->priv; + + debugfs_remove_recursive(ns_phy->phy_dir); + + phy_device_remove(phydev); + list_del(&ns_phy->node); + kfree(ns_phy); + + nsim_mdiobus_destroy(mb); +} + +static int nsim_phy_state_link_set(void *data, u64 val) +{ + struct nsim_phy_device *ns_phy = (struct nsim_phy_device *)data; + + ns_phy->link = !!val; + + phy_trigger_machine(ns_phy->phy); + + return 0; +} + +static int nsim_phy_state_link_get(void *data, u64 *val) +{ + struct nsim_phy_device *ns_phy = (struct nsim_phy_device *)data; + + *val = ns_phy->link; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(nsim_phy_state_link_fops, nsim_phy_state_link_get, + nsim_phy_state_link_set, "%llu\n"); + +static void nsim_phy_debugfs_create(struct nsim_dev_port *port, + struct nsim_phy_device *ns_phy) +{ + char phy_dir_name[sizeof("phy") + 10]; + + sprintf(phy_dir_name, "phy%u", ns_phy->phy->phyindex); + + /* create debugfs stuff */ + ns_phy->phy_dir = debugfs_create_dir(phy_dir_name, port->ddir); + + debugfs_create_file("link", 0600, ns_phy->phy_dir, ns_phy, &nsim_phy_state_link_fops); +} + +static void nsim_adjust_link(struct net_device *dev) +{ + phy_print_status(dev->phydev); +} + +static ssize_t +nsim_phy_add_write(struct file *file, const char __user *data, + size_t count, loff_t *ppos) +{ + struct net_device *dev = file->private_data; + struct netdevsim *ns = netdev_priv(dev); + struct nsim_phy_device *ns_phy; + struct phy_device *pphy; + u32 parent_id; + char buf[10]; + ssize_t ret; + int err; + + if (*ppos != 0) + return 0; + + if (count >= sizeof(buf)) + return -ENOSPC; + + ret = copy_from_user(buf, data, count); + if (ret) + return -EFAULT; + buf[count] = '\0'; + + ret = kstrtouint(buf, 10, &parent_id); + if (ret) + return -EINVAL; + + ns_phy = nsim_phy_register(); + if (IS_ERR(ns_phy)) + return PTR_ERR(ns_phy); + + if (!parent_id) { + if (!dev->phydev) { + err = phy_connect_direct(dev, ns_phy->phy, nsim_adjust_link, + PHY_INTERFACE_MODE_NA); + if (err) + return err; + + phy_attached_info(ns_phy->phy); + + phy_start(ns_phy->phy); + } else { + phy_link_topo_add_phy(dev, ns_phy->phy, PHY_UPSTREAM_MAC, dev); + } + } else { + pphy = phy_link_topo_get_phy(dev, parent_id); + if (!pphy) + return -EINVAL; + + phy_link_topo_add_phy(dev, ns_phy->phy, PHY_UPSTREAM_PHY, pphy); + } + + nsim_phy_debugfs_create(ns->nsim_dev_port, ns_phy); + + list_add(&ns_phy->node, &ns->nsim_dev->phy_list); + + return count; +} + +static const struct file_operations nsim_phy_add_fops = { + .open = simple_open, + .write = nsim_phy_add_write, + .llseek = generic_file_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t +nsim_phy_del_write(struct file *file, const char __user *data, + size_t count, loff_t *ppos) +{ + struct net_device *dev = file->private_data; + struct nsim_phy_device *ns_phy; + struct phy_device *phydev; + u32 phy_index; + char buf[10]; + ssize_t ret; + + if (*ppos != 0) + return 0; + + if (count >= sizeof(buf)) + return -ENOSPC; + + ret = copy_from_user(buf, data, count); + if (ret) + return -EFAULT; + buf[count] = '\0'; + + ret = kstrtouint(buf, 10, &phy_index); + if (ret) + return -EINVAL; + + phydev = phy_link_topo_get_phy(dev, phy_index); + if (!phydev) + return -ENODEV; + + ns_phy = phydev->priv; + + if (dev->phydev && dev->phydev == phydev) { + phy_stop(phydev); + phy_detach(phydev); + } else { + phy_link_topo_del_phy(dev, phydev); + } + + nsim_phy_destroy(ns_phy); + + return count; +} + +static const struct file_operations nsim_phy_del_fops = { + .open = simple_open, + .write = nsim_phy_del_write, + .llseek = generic_file_llseek, + .owner = THIS_MODULE, +}; + +void nsim_phy_init(struct netdevsim *ns) +{ + debugfs_create_file("phy_add", 0200, ns->nsim_dev_port->ddir, + ns->netdev, &nsim_phy_add_fops); + + debugfs_create_file("phy_del", 0200, ns->nsim_dev_port->ddir, + ns->netdev, &nsim_phy_del_fops); +} + +void nsim_phy_teardown(struct netdevsim *ns) +{ + struct nsim_phy_device *ns_phy, *pos; + + list_for_each_entry_safe(ns_phy, pos, &ns->nsim_dev->phy_list, node) + nsim_phy_destroy(ns_phy); +} + +int __init nsim_phy_drv_register(void) +{ + return phy_drivers_register(nsim_virtual_phy_drv, + ARRAY_SIZE(nsim_virtual_phy_drv), + THIS_MODULE); +} + +void __exit nsim_phy_drv_unregister(void) +{ + phy_drivers_unregister(nsim_virtual_phy_drv, + ARRAY_SIZE(nsim_virtual_phy_drv)); +}
On Tue, 8 Jul 2025 13:55:28 +0200 Maxime Chevallier maxime.chevallier@bootlin.com wrote:
With the introduction of phy_link_topology, we have the ability to keep track of PHY devices that sit behind a net_device. While we still can only attach one single PHY to a netdev, we can look at all these PHYs through netlink, with the ETHTOOL_MSG_PHY_GET command.
Moreover, netlink commands that are targeting PHY devices also now allow specifying which PHY we want to address in a given netlink command.
That whole process comes with its own complexity, and a few bugs were dicovered over the months following the introduction of phy_link_topology.
As devices with multiple PHYs are fairly uncommon, testing the corner cases of multi-phy setups proves to be difficult.
To that extent, introduce PHY support in netdevsim. The main goal (for now) is not to be able to test PHYlib, but these phy-specific netlink interfaces.
These netdevsim PHYs use a custom phy_driver that relies on re-implementing the phy_driver callbacks. In other words, this is not a PHY driver that relies on mdio emulation, and will not work with any of the genphy helpers.
The debugfs API for PHY creation and deletion works as follows :
PHY device creation :
echo $ID > /sys/kernel/debug/netdevsim/netdevsimXXX/ports/YY/phy_add
if $ID is 0, then the PHY parent will be the netdev corresponding to the port's netdev. The first PHY that is added with the netdev as a parent will be attached to the netdev.
if $ID > 0, the index must correspond to a previously added PHY. This allows creating any arbitrary tree of PHYs.
Upon PHY addition, a phyXX directory will be created, XX being the phyindex of the PHY in the topology:
[...]/ports/YY/phyXX/
This directory contains a "link" file, allowing to toggle the virtual PHY's link state.
One can then list the PHYs with "ethtool --show-phys ethX".
Signed-off-by: Maxime Chevallier maxime.chevallier@bootlin.com
drivers/net/netdevsim/Makefile | 4 + drivers/net/netdevsim/dev.c | 2 + drivers/net/netdevsim/netdev.c | 8 + drivers/net/netdevsim/netdevsim.h | 25 ++ drivers/net/netdevsim/phy.c | 398 ++++++++++++++++++++++++++++++ 5 files changed, 437 insertions(+) create mode 100644 drivers/net/netdevsim/phy.c
diff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile index f8de93bc5f5b..49f4c515e5e3 100644 --- a/drivers/net/netdevsim/Makefile +++ b/drivers/net/netdevsim/Makefile @@ -21,3 +21,7 @@ endif ifneq ($(CONFIG_MACSEC),) netdevsim-objs += macsec.o endif
+ifneq ($(CONFIG_PHYLIB),) +netdevsim-objs += phy.o +endif diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index b3647691060c..1ebb4f5b3bdd 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -1510,6 +1510,7 @@ static int nsim_dev_reload_create(struct nsim_dev *nsim_dev, devlink = priv_to_devlink(nsim_dev); nsim_dev = devlink_priv(devlink); INIT_LIST_HEAD(&nsim_dev->port_list);
- INIT_LIST_HEAD(&nsim_dev->phy_list); nsim_dev->fw_update_status = true; nsim_dev->fw_update_overwrite_mask = 0;
@@ -1583,6 +1584,7 @@ int nsim_drv_probe(struct nsim_bus_dev *nsim_bus_dev) nsim_dev->switch_id.id_len = sizeof(nsim_dev->switch_id.id); get_random_bytes(nsim_dev->switch_id.id, nsim_dev->switch_id.id_len); INIT_LIST_HEAD(&nsim_dev->port_list);
- INIT_LIST_HEAD(&nsim_dev->phy_list); nsim_dev->fw_update_status = true; nsim_dev->fw_update_overwrite_mask = 0; nsim_dev->max_macs = NSIM_DEV_MAX_MACS_DEFAULT;
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index e36d3e846c2d..ff891536f691 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -952,6 +952,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns) nsim_macsec_init(ns); nsim_ipsec_init(ns);
- nsim_phy_init(ns);
err = register_netdevice(ns->netdev); if (err) @@ -968,6 +969,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns) return 0; err_ipsec_teardown:
- nsim_phy_teardown(ns); nsim_ipsec_teardown(ns); nsim_macsec_teardown(ns); nsim_bpf_uninit(ns);
@@ -1058,6 +1060,7 @@ void nsim_destroy(struct netdevsim *ns) RCU_INIT_POINTER(ns->peer, NULL); unregister_netdevice(dev); if (nsim_dev_port_is_pf(ns->nsim_dev_port)) {
nsim_macsec_teardown(ns); nsim_ipsec_teardown(ns); nsim_bpf_uninit(ns);nsim_phy_teardown(ns);
@@ -1098,6 +1101,10 @@ static int __init nsim_module_init(void) { int err;
- err = nsim_phy_drv_register();
- if (err)
return err;
- err = nsim_dev_init(); if (err) return err;
@@ -1124,6 +1131,7 @@ static void __exit nsim_module_exit(void) rtnl_link_unregister(&nsim_link_ops); nsim_bus_exit(); nsim_dev_exit();
- nsim_phy_drv_unregister();
} module_init(nsim_module_init); diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index 809dd29fc5fe..c73d16e67a3c 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -314,6 +314,7 @@ struct nsim_dev { struct list_head bpf_bound_maps; struct netdev_phys_item_id switch_id; struct list_head port_list;
- struct list_head phy_list; bool fw_update_status; u32 fw_update_overwrite_mask; u32 max_macs;
@@ -419,6 +420,30 @@ static inline void nsim_macsec_teardown(struct netdevsim *ns) } #endif +#if IS_ENABLED(CONFIG_PHYLIB) +void nsim_phy_init(struct netdevsim *ns); +void nsim_phy_teardown(struct netdevsim *dev); +int nsim_phy_drv_register(void); +void nsim_phy_drv_unregister(void); +#else +static inline void nsim_phy_init(struct netdevsim *ns) +{ +}
+static inline void nsim_phy_teardown(struct netdevsim *ns);
Meh... that's why this fails, stray ';' that slept through the cracks when building netdevsim with CONFIG_PHYLIB=n ...
I'll address that,
Maxime
old_netdevs is unused in ethtool-common.sh. Only the UDP tunnels test uses that variable, but it maintains it locally.
Signed-off-by: Maxime Chevallier maxime.chevallier@bootlin.com --- .../testing/selftests/drivers/net/netdevsim/ethtool-common.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh b/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh index 80160579e0cc..d9c7a3d397a9 100644 --- a/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh +++ b/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh @@ -43,13 +43,11 @@ function check { }
function make_netdev { - # Make a netdevsim - old_netdevs=$(ls /sys/class/net) - if ! $(lsmod | grep -q netdevsim); then modprobe netdevsim fi
+ # Make a netdevsim echo $NSIM_ID $@ > /sys/bus/netdevsim/new_device udevadm settle # get new device name
Now that netdevsim supports PHY device simulation, we can start writing some tests to cover a little bit all PHY-related ethtool commands.
So far we only test the basic use of "ethtool --show-phys", with : - A simple command to get a PHY we just added - A DUMP command listing PHYs on multiple netdevsim instances - A Filtered DUMP command listing all PHYs on a netdevsim
Introduce some helpers to create netdevsim PHYs, and a new test file.
Signed-off-by: Maxime Chevallier maxime.chevallier@bootlin.com ---
I couldn't get rid of some shellcheck messages, mostly becase ethtool-common.sh fails to be parsed by shellcheck :(
.../selftests/drivers/net/netdevsim/config | 1 + .../drivers/net/netdevsim/ethtool-common.sh | 15 +++++ .../drivers/net/netdevsim/ethtool-phy.sh | 64 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/netdevsim/ethtool-phy.sh
diff --git a/tools/testing/selftests/drivers/net/netdevsim/config b/tools/testing/selftests/drivers/net/netdevsim/config index 5117c78ddf0a..223e82cb7759 100644 --- a/tools/testing/selftests/drivers/net/netdevsim/config +++ b/tools/testing/selftests/drivers/net/netdevsim/config @@ -6,6 +6,7 @@ CONFIG_NETDEVSIM=m CONFIG_NET_SCH_MQPRIO=y CONFIG_NET_SCH_MULTIQ=y CONFIG_NET_SCH_PRIO=y +CONFIG_PHYLIB=m CONFIG_PSAMPLE=y CONFIG_PTP_1588_CLOCK_MOCK=y CONFIG_VXLAN=m diff --git a/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh b/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh index d9c7a3d397a9..1bd0ac5e7bba 100644 --- a/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh +++ b/tools/testing/selftests/drivers/net/netdevsim/ethtool-common.sh @@ -53,3 +53,18 @@ function make_netdev { # get new device name ls /sys/bus/netdevsim/devices/netdevsim${NSIM_ID}/net/ } + +function make_phydev_on_netdev { + local parent_ndev_nsim_id=$1 + local parent=$2 + + local ndev_dfs=/sys/kernel/debug/netdevsim/netdevsim$parent_ndev_nsim_id/ports/0 + + old_dev_dfs=$(find $ndev_dfs -type d) + echo $parent > $ndev_dfs/phy_add + new_dev_dfs=$(find $ndev_dfs -type d) + + # The new phydev name corresponds to the new file that was created. Its + # name isn't predictable. + echo $old_dev_dfs $new_dev_dfs | xargs -n1 | sort | uniq -u +} diff --git a/tools/testing/selftests/drivers/net/netdevsim/ethtool-phy.sh b/tools/testing/selftests/drivers/net/netdevsim/ethtool-phy.sh new file mode 100755 index 000000000000..b10440d108b2 --- /dev/null +++ b/tools/testing/selftests/drivers/net/netdevsim/ethtool-phy.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only + +source ethtool-common.sh + +# Bail if ethtool is too old +if ! ethtool -h | grep show-phys >/dev/null 2>&1; then + echo "SKIP: No --show-phys support in ethtool" + exit 4 +fi + +function make_netdev_from_id { + local new_nsim_id="$1" + # Make a netdevsim + echo "$new_nsim_id" > /sys/bus/netdevsim/new_device + udevadm settle + # get new device name + ls /sys/bus/netdevsim/devices/netdevsim"${new_nsim_id}"/net/ +} + +function cleanup_netdev_from_id { + local to_del_nsim_id="$1" + echo "$to_del_nsim_id" > /sys/bus/netdevsim/del_device +} + +NSIM_NETDEV=$(make_netdev) + +set -o pipefail + +# Check simple PHY addition and listing + +# Parent == 0 means that the PHY's parent is the netdev +PHY_DFS=$(make_phydev_on_netdev "$NSIM_ID" 0) + +# First PHY gets index 1 +index=$(ethtool --show-phys "$NSIM_NETDEV" | grep "PHY index" | cut -d ' ' -f 3) +check $? "$index" "1" + +# Insert a second PHY, same parent. It gets index 2. +PHY2_DFS=$(make_phydev_on_netdev "$NSIM_ID" 0) + +# Create another netdev +NSIM_ID2=$((RANDOM % 1024)) +NSIM_NETDEV_2=$(make_netdev_from_id "$NSIM_ID2") + +PHY3_DFS=$(make_phydev_on_netdev "$NSIM_ID2" 0); + +# Check unfiltered PHY Dump +n_phy=$(ethtool --show-phys '*' | grep -c "PHY index") +check $? "$n_phy" "3" + +# Check filtered Dump +n_phy=$(ethtool --show-phys "$NSIM_NETDEV" | grep -c "PHY index") +check $? "$n_phy" "2" + +cleanup_netdev_from_id "$NSIM_ID2" + +if [ "$num_errors" -eq 0 ]; then + echo "PASSED all $((num_passes)) checks" + exit 0 +else + echo "FAILED $num_errors/$((num_errors+num_passes)) checks" + exit 1 +fi
On Tue, 8 Jul 2025 13:55:27 +0200 Maxime Chevallier wrote:
Here's a V2 for the netdevsim PHY support, including a bugfix for NETDEVSIM=m as well as a round of shellcheck cleanups for ethtool-phy.sh.
The idea of this series is to allow attaching virtual PHY devices to netdevsim, so that we can test PHY-related ethtool commands. This can be extended in the future for phylib testing as well.
Appears to break the build for BPF CI:
https://github.com/kernel-patches/bpf/actions/runs/16150854854/job/455815077...
On Tue, 8 Jul 2025 13:55:53 -0700 Jakub Kicinski kuba@kernel.org wrote:
On Tue, 8 Jul 2025 13:55:27 +0200 Maxime Chevallier wrote:
Here's a V2 for the netdevsim PHY support, including a bugfix for NETDEVSIM=m as well as a round of shellcheck cleanups for ethtool-phy.sh.
The idea of this series is to allow attaching virtual PHY devices to netdevsim, so that we can test PHY-related ethtool commands. This can be extended in the future for phylib testing as well.
Appears to break the build for BPF CI:
https://github.com/kernel-patches/bpf/actions/runs/16150854854/job/455815077...
aw :( I'll follow-up on that then, sorry :(
Maxime
linux-kselftest-mirror@lists.linaro.org