diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index a66594ad9e73..a9cb02538783 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -212,6 +212,11 @@ definitions: - name: discard value: 31 + - + name: port-type + doc: type of physical connector + type: enum + entries: [mdi, sfp] attribute-sets: - @@ -1907,6 +1912,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: u32 + enum: port-type + - + name: upstream-port + type: u32 operations: enum-model: directional @@ -2861,6 +2892,30 @@ operations: - worst-channel - link dump: *mse-get-op + - + name: port-get + doc: Get ports attached to an interface + + attribute-set: port + + do: + request: + attributes: + - header + - id + reply: &port-get-op-reply + attributes: + - header + - id + - supported-modes + - supported-interfaces + - type + - upstream-port + dump: + request: + attributes: + - header + reply: *port-get-op-reply mcast-groups: list: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 393f2b6d3863..eba226c79ec3 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -247,6 +247,7 @@ Userspace to kernel: ``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context ``ETHTOOL_MSG_RSS_DELETE_ACT`` delete an additional RSS context ``ETHTOOL_MSG_MSE_GET`` get MSE diagnostic data + ``ETHTOOL_MSG_PORT_GET`` get port information ===================================== ================================= Kernel to userspace: @@ -305,6 +306,7 @@ Kernel to userspace: ``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created ``ETHTOOL_MSG_RSS_DELETE_NTF`` additional RSS context deleted ``ETHTOOL_MSG_MSE_GET_REPLY`` MSE diagnostic data + ``ETHTOOL_MSG_PORT_GET_REPLY`` port information ======================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -2537,6 +2539,39 @@ 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`` u32 the port type + ``ETHTOOL_A_PORT_UPSTREAM_PORT`` u32 If any, the index of the MII + port that feeds into this + port. + ======================================= ====== ============================= + Request translation =================== @@ -2647,4 +2682,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/Documentation/networking/phy-port.rst b/Documentation/networking/phy-port.rst index 6e28d9094bce..2a9b1ec93b70 100644 --- a/Documentation/networking/phy-port.rst +++ b/Documentation/networking/phy-port.rst @@ -99,13 +99,29 @@ will eventually be able to report its own ksettings:: (_____)-----| Port | +------+ +SFP ports +========= + +SFP interfaces involve 2 distinct components, each represented by +a :c:type:`struct phy_port ` instance : + + - The SFP cage itself is a :c:type:`struct phy_port `. It's special + in that it's not an MDI interface, but rather a hot-pluggable MII. + The :c:type:`struct phy_port ` associated to it lists the different + MII interfaces we can use on the cage. + + - The SFP module, when inserted, will also be associated to a + :c:type:`struct phy_port `, that represents the various linkmodes + that it gives access to. The module's :c:type:`struct phy_port ` + doesn't supersede the cage's port, it references it through + the :c:type:`struct phy_port ` :c:member:`upstream_port` field. + Next steps ========== -As of writing this documentation, only ports controlled by PHY devices are -supported. The next steps will be to add the Netlink API to expose these -to userspace and add support for raw ports (controlled by some firmware, and directly -managed by the NIC driver). +As of writing this documentation, the port's presence and information can only +be queried, and it's not possible to change any of the port's settings or select +which one should be used. Another parallel task is the introduction of a MII muxing framework to allow the -control of non-PHY driver multi-port setups. +control of non-PHY driven multi-port setups. diff --git a/MAINTAINERS b/MAINTAINERS index 83ff18370ece..e7640995959c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19024,6 +19024,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/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 68691c487eee..5d4508bcfbe9 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1400,11 +1400,22 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) { struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; + int ret; - if (dev) - return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev); + phydev->has_sfp_mod_phy = true; + phy_set_upstream_port(phy, phydev->sfp_cage_port); - return 0; + /* If we aren't attached to a netdev, we can't add the SFP PHY to its + * topology. + */ + if (!dev) + return 0; + + ret = phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev); + if (ret) + phydev->has_sfp_mod_phy = false; + + return ret; } /** @@ -1422,8 +1433,12 @@ 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); + + phy_set_upstream_port(phy, NULL); } /** @@ -1527,6 +1542,80 @@ static void phy_sfp_link_down(void *upstream) port->ops->link_down(port); } +static int phy_add_sfp_mod_port(struct phy_device *phydev) +{ + DECLARE_PHY_INTERFACE_MASK(interfaces); + 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_interface_and(interfaces, phydev->sfp_cage_port->interfaces, + caps->interfaces); + phy_caps_linkmode_filter_ifaces(port->supported, caps->link_modes, + 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; + + port->upstream_port = phydev->sfp_cage_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, @@ -1536,6 +1625,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) @@ -1580,16 +1671,19 @@ static void phy_del_port(struct phy_device *phydev, struct phy_port *port) list_del(&port->head); + if (phydev->attached_dev) + phy_link_topo_del_port(phydev->attached_dev, 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; @@ -1604,10 +1698,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; } /** @@ -1616,32 +1712,51 @@ static int phy_setup_sfp_port(struct phy_device *phydev) */ static int phy_sfp_probe(struct phy_device *phydev) { + struct phy_port *port; struct sfp_bus *bus; - int ret = 0; - 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; - phydev->sfp_bus = bus; + bus = sfp_bus_find_fwnode(phydev->mdio.dev.fwnode); + if (IS_ERR(bus)) + return PTR_ERR(bus); - ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops); - sfp_bus_put(bus); + phydev->sfp_bus = bus; - if (ret) - phydev->sfp_bus = NULL; - } - - if (!ret && phydev->sfp_bus) { - ret = phy_setup_sfp_port(phydev); - if (ret) { - sfp_bus_del_upstream(phydev->sfp_bus); + if (bus) { + port = phy_setup_sfp_port(phydev); + if (IS_ERR(port)) { + sfp_bus_put(bus); phydev->sfp_bus = NULL; + return PTR_ERR(port); } + + phydev->sfp_cage_port = port; } - return ret; + return 0; +} + +static int phy_sfp_add_upstream(struct phy_device *phydev) +{ + struct sfp_bus *bus = phydev->sfp_bus; + int ret; + + ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops); + if (ret) { + sfp_bus_put(bus); + phydev->sfp_bus = NULL; + phydev->sfp_cage_port = NULL; + return ret; + } + + /* sfp_bus_add_upstream() grabs a ref to the sfp bus on success, it's + * safe to release it now. + */ + sfp_bus_put(bus); + + return 0; } /** @@ -1723,6 +1838,8 @@ static void phy_detach_internal(struct phy_device *phydev, bool notify_bus) 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; @@ -1859,6 +1976,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 @@ -3553,6 +3676,10 @@ static int phy_setup_ports(struct phy_device *phydev) break; } + ret = phy_sfp_add_upstream(phydev); + if (ret) + goto err_sfp; + return 0; err_sfp: @@ -3581,6 +3708,25 @@ struct phy_port *phy_get_sfp_port(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(phy_get_sfp_port); +/** + * phy_set_upstream_port() - Sets the phy_port controlling the MII this PHY is + * attached to. + * @phydev: pointer to the PHY device we set the upstream of. + * @port: The phy_port upstream of this PHY, can be NULL. + */ +void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port) +{ + struct phy_port *local_port; + + ASSERT_RTNL(); + + phydev->upstream_port = port; + + phy_for_each_port(phydev, local_port) + local_port->upstream_port = port; +} +EXPORT_SYMBOL_GPL(phy_set_upstream_port); + /** * fwnode_mdio_find_device - Given a fwnode, find the mdio_device * @fwnode: pointer to the mdio_device's fwnode diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c index 4134de7ae313..214ccce23cf6 100644 --- a/drivers/net/phy/phy_link_topology.c +++ b/drivers/net/phy/phy_link_topology.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -23,17 +24,89 @@ 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; } +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. It can be called on multiple + * contexts: + * - It can be called from .probe() : No rtnl, no netdev_lock + * - .ndo_open() : rtnl and possibly netdev_lock + * - SFP state machine : rtnl held or not + * + * However, we can't really have races : + * - If we have a PHY, phy_link_topo_add_phy() will always run first + * and trigger the alloc. Only then the ports can be added through + * phylib or sfp. + * - If we don't, the SFP port for the cage is registered first, and + * only then other ports/PHYs can be registered. + */ + ret = netdev_alloc_phy_link_topology(dev); + if (ret) + return ERR_PTR(ret); + + 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; + + /* Ports can now be queried without rtnl for ops-locked devices, which + * we don't support now as port retrieval is done under rtnl. + * We don't have phy_port enabled devices yet, let's make sure + * we are loudly warned about that when it happens. + */ + if (WARN_ON_ONCE(dev->netdev_ops && netdev_need_ops_lock(dev))) + return -EOPNOTSUPP; + + 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 < 0 ? ret : 0; +} +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 = dev->link_topo; + struct phy_link_topology *topo; struct phy_device_node *pdn; + struct phy_port *port; int ret; /* ethtool ops may run without rtnl_lock, and rtnl_lock is what @@ -45,13 +118,9 @@ int phy_link_topo_add_phy(struct net_device *dev, if (WARN_ON_ONCE(netdev_need_ops_lock(dev))) return -EOPNOTSUPP; - 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) @@ -86,8 +155,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; @@ -99,10 +180,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/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a7d086cdc9b2..e885da1c2831 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -93,6 +95,8 @@ struct phylink { DECLARE_PHY_INTERFACE_MASK(sfp_interfaces); __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support); u8 sfp_port; + struct phy_port *sfp_cage_port; + struct phy_port *mod_port; struct eee_config eee_cfg; @@ -1767,6 +1771,57 @@ static void phylink_fixed_poll(struct timer_list *t) static const struct sfp_upstream_ops sfp_phylink_ops; +static int phylink_create_sfp_cage_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); + + rtnl_lock(); + ret = phy_link_topo_add_port(pl->netdev, port); + rtnl_unlock(); + if (ret) + goto out_destroy_port; + + pl->sfp_cage_port = port; + + return 0; + +out_destroy_port: + phy_port_destroy(port); + pl->sfp_cage_port = NULL; + return ret; +} + +static void phylink_destroy_sfp_cage_port(struct phylink *pl) +{ + if (pl->netdev && pl->sfp_cage_port) { + /* The port may still be accessed under RTNL */ + rtnl_lock(); + phy_link_topo_del_port(pl->netdev, pl->sfp_cage_port); + rtnl_unlock(); + } + + if (pl->sfp_cage_port) + phy_port_destroy(pl->sfp_cage_port); + + pl->sfp_cage_port = NULL; +} + static int phylink_register_sfp(struct phylink *pl, const struct fwnode_handle *fwnode) { @@ -1784,9 +1839,18 @@ static int phylink_register_sfp(struct phylink *pl, pl->sfp_bus = bus; + ret = phylink_create_sfp_cage_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_cage_port(pl); + return ret; } @@ -2069,6 +2133,7 @@ EXPORT_SYMBOL_GPL(phylink_create); void phylink_destroy(struct phylink *pl) { sfp_bus_del_upstream(pl->sfp_bus); + phylink_destroy_sfp_cage_port(pl); if (pl->link_gpio) gpiod_put(pl->link_gpio); @@ -3955,14 +4020,70 @@ static void phylink_sfp_module_remove(void *upstream) phy_interface_zero(pl->sfp_interfaces); } +static int phylink_add_sfp_mod_port(struct phylink *pl) +{ + DECLARE_PHY_INTERFACE_MASK(interfaces); + const struct sfp_module_caps *caps; + struct phy_port *port; + int ret = 0; + + if (!pl->sfp_cage_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_interface_and(interfaces, pl->sfp_cage_port->interfaces, + caps->interfaces); + phy_caps_linkmode_filter_ifaces(port->supported, caps->link_modes, + interfaces); + + if (pl->netdev) { + ret = phy_link_topo_add_port(pl->netdev, port); + if (ret) { + phy_port_destroy(port); + return ret; + } + } + + port->upstream_port = pl->sfp_cage_port; + + 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 @@ -3971,7 +4092,16 @@ static int phylink_sfp_module_start(void *upstream) if (!pl->sfp_may_have_phy) return 0; - return phylink_sfp_config_optical(pl); + ret = phylink_sfp_config_optical(pl); + if (ret) + goto del_mod_port; + + return 0; + +del_mod_port: + phylink_del_sfp_mod_port(pl); + + return ret; } static void phylink_sfp_module_stop(void *upstream) @@ -3981,6 +4111,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) @@ -4025,6 +4157,8 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces, pl->config->supported_interfaces); + phy_set_upstream_port(phy, pl->sfp_cage_port); + /* Do the initial configuration */ return phylink_sfp_config_phy(pl, phy); } @@ -4033,6 +4167,7 @@ static void phylink_sfp_disconnect_phy(void *upstream, struct phy_device *phydev) { phylink_disconnect_phy(upstream); + phy_set_upstream_port(phydev, NULL); } static const struct sfp_upstream_ops sfp_phylink_ops = { diff --git a/include/linux/phy.h b/include/linux/phy.h index a5a419bc400e..21afc2082b17 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -612,7 +612,11 @@ struct phy_oatc14_sqi_capability { * @irq: IRQ number of the PHY's interrupt (-1 if none) * @phylink: Pointer to phylink instance for this PHY * @sfp_bus_attached: Flag indicating whether the SFP bus has been attached + * @has_sfp_mod_phy: Set true if downstream SFP bus's module contains a PHY * @sfp_bus: SFP bus attached to this PHY's fiber port + * @sfp_cage_port: The phy_port connected to the downstream SFP cage + * @mod_port: phy_port representing the SFP module, if it is phy-less + * @upstream_port: phy_port this PHY's MII attaches to, if any * @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. @@ -804,7 +808,11 @@ struct phy_device { /* This may be modified under the rtnl lock */ bool sfp_bus_attached; + bool has_sfp_mod_phy; struct sfp_bus *sfp_bus; + struct phy_port *sfp_cage_port; + struct phy_port *mod_port; + struct phy_port *upstream_port; struct phylink *phylink; struct net_device *attached_dev; struct mii_timestamper *mii_ts; @@ -2489,6 +2497,8 @@ int __phy_hwtstamp_set(struct phy_device *phydev, struct phy_port *phy_get_sfp_port(struct phy_device *phydev); +void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port); + /** * phy_module_driver() - Helper macro for registering PHY drivers * @__phy_drivers: array of PHY drivers to register diff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h index 95575f68d5bc..f2e493c433ff 100644 --- a/include/linux/phy_link_topology.h +++ b/include/linux/phy_link_topology.h @@ -13,14 +13,19 @@ #include #include +#include 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 { @@ -48,6 +53,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) { @@ -64,6 +72,24 @@ 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; + + if (!topo) + return NULL; + + /* ports in the topo are RTNL protected, but may be accessed under + * netdev_lock for ops-locked devices. For now keep them under rtnl + * protection, as no ops-locked devices have phy_port capabilities + * yet. + */ + ASSERT_RTNL(); + + return xa_load(&topo->ports, port_id); +} + #else static inline int phy_link_topo_add_phy(struct net_device *dev, struct phy_device *phy, @@ -77,11 +103,28 @@ 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) { 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 */ diff --git a/include/linux/phy_port.h b/include/linux/phy_port.h index 0ef0f5ce4709..8f45c031cbc1 100644 --- a/include/linux/phy_port.h +++ b/include/linux/phy_port.h @@ -36,9 +36,12 @@ 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 + * @upstream_port: Indicates the MII port that feeds this port, if any, + * e.g. the SFP cage port for a SFP module port. * @ops: Callback ops implemented by the port controller * @pairs: The number of pairs this port has, 0 if not applicable * @mediums: Bitmask of the physical mediums this port provides access to @@ -52,11 +55,13 @@ 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 { struct phy_device *phy; }; + struct phy_port *upstream_port; const struct phy_port_ops *ops; diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 986d70caec33..9e60c17b48c8 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -685,6 +685,7 @@ enum ethtool_link_ext_substate_module { * @ETH_SS_STATS_RMON: names of RMON statistics * @ETH_SS_STATS_PHY: names of PHY(dev) statistics * @ETH_SS_TS_FLAGS: hardware timestamping flags + * @ETH_SS_PHY_INTERFACES: names of MAC to PHY interfaces * * @ETH_SS_COUNT: number of defined string sets */ @@ -712,6 +713,7 @@ enum ethtool_stringset { ETH_SS_STATS_RMON, ETH_SS_STATS_PHY, ETH_SS_TS_FLAGS, + ETH_SS_PHY_INTERFACES, /* add new constants above here */ ETH_SS_COUNT diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 8134baf7860f..660e0c08a766 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -78,6 +78,14 @@ enum ethtool_pse_event { ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64, }; +/* + * type of physical connector + */ +enum ethtool_port_type { + ETHTOOL_PORT_TYPE_MDI, + ETHTOOL_PORT_TYPE_SFP, +}; + enum { ETHTOOL_A_HEADER_UNSPEC, ETHTOOL_A_HEADER_DEV_INDEX, @@ -840,6 +848,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_UPSTREAM_PORT, + + __ETHTOOL_A_PORT_CNT, + ETHTOOL_A_PORT_MAX = (__ETHTOOL_A_PORT_CNT - 1) +}; + enum { ETHTOOL_MSG_USER_NONE = 0, ETHTOOL_MSG_STRSET_GET = 1, @@ -893,6 +913,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) @@ -954,6 +975,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) diff --git a/net/core/dev.c b/net/core/dev.c index 8eb9bf1ff9eb..1a09e7869118 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -11521,6 +11521,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; } 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 383e911f50f7..dd8a53f89d54 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -28,6 +28,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, @@ -433,6 +435,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) @@ -1571,6 +1574,15 @@ 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[] = { @@ -1593,10 +1605,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 3e969a070f9f..dc7211f67c4d 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -5,11 +5,14 @@ #include #include +#include #include #include struct ethnl_req_info; +extern char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN]; + int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info, const struct nlattr *nest, struct net *net, struct netlink_ext_ack *extack, @@ -474,6 +477,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]; @@ -530,6 +534,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); @@ -545,6 +550,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..425e5bbdca45 --- /dev/null +++ b/net/ethtool/port.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2026 Bootlin + */ +#include +#include +#include +#include + +#include "bitset.h" +#include "common.h" +#include "netlink.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; + u32 upstream_port; +}; + +#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] = NLA_POLICY_MIN(NLA_U32, 1), +}; + +static int port_parse_request(struct ethnl_req_info *req_info, + const struct genl_info *info, + struct nlattr **tb, + struct netlink_ext_ack *extack) +{ + struct port_req_info *request = PORT_REQINFO(req_info); + + if (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_PORT_ID)) + return -EINVAL; + + request->port_id = nla_get_u32(tb[ETHTOOL_A_PORT_ID]); + + 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->upstream_port = port->upstream_port ? port->upstream_port->id : 0; + + 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(u32)); + + /* ETHTOOL_A_PORT_UPSTREAM_PORT */ + if (reply->upstream_port) + size += nla_total_size(sizeof(u32)); + + 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; + + 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 ret; + } 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 ret; + } + + port_type = ETHTOOL_PORT_TYPE_MDI; + if (reply->mii || reply->sfp) + port_type = ETHTOOL_PORT_TYPE_SFP; + + if (nla_put_u32(skb, ETHTOOL_A_PORT_TYPE, port_type)) + return -EMSGSIZE; + + if (reply->upstream_port && + nla_put_u32(skb, ETHTOOL_A_PORT_UPSTREAM_PORT, + reply->upstream_port)) + 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_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)); + + if (tb[ETHTOOL_A_PORT_ID]) { + NL_SET_BAD_ATTR(info->info.extack, tb[ETHTOOL_A_PORT_ID]); + return -EINVAL; + } + + 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; + ctx->pos_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; + + ret = port_prepare_data(&ctx->req_info->base, &ctx->reply_data->base, + genl_info_dump(cb)); + 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 = 0; + + dev = ctx->req_info->base.dev; + + rtnl_lock(); + netdev_lock_ops(dev); + + if (!dev->link_topo) + goto out; + + 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 == -ENODEV) + continue; + if (ret) + goto out; + } + + ret = 0; + +out: + netdev_unlock_ops(dev); + rtnl_unlock(); + + 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); + unsigned long start_ifindex = ctx->pos_ifindex; + 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->pos_ifindex) { + if (ctx->pos_ifindex != start_ifindex) + ctx->pos_portid = 0; + + 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) + break; + } + 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); + + 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, +}; diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c index 94c4718d31ae..0974714c34ec 100644 --- a/net/ethtool/strset.c +++ b/net/ethtool/strset.c @@ -116,6 +116,11 @@ static const struct strset_info info_template[] = { .count = __ETHTOOL_A_STATS_PHY_CNT, .strings = stats_phy_names, }, + [ETH_SS_PHY_INTERFACES] = { + .per_dev = false, + .count = PHY_INTERFACE_MODE_MAX, + .strings = phy_interface_names, + }, }; struct strset_req_info {