| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/02 10:29 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"ETHTOOL_NETLINK",
"PHYLIB",
"PHYLINK",
"SFP",
"I2C"
],
"FocusSymbols": [
"ethnl_port_dump_start",
"ethnl_port_dumpit",
"ethnl_port_dump_done",
"phy_link_topo_add_port",
"phy_link_topo_del_port",
"phy_caps_linkmode_filter_ifaces",
"phy_set_upstream_port"
],
"Reasoning": "The patch introduces a new ethtool netlink command `PORT_GET` to retrieve physical port information, and adds SFP port support to the PHY framework. This is a functional change that exposes new netlink APIs and modifies core PHY/phylink logic, which can be reached from userspace via ethtool and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/02 10:29 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 0abd5398c52ca9886061eb30a81202002ae62b4a\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Aug 2 10:29:17 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml\nindex 5dd4d1b5d94bb..67ac3400462bf 100644\n--- a/Documentation/netlink/specs/ethtool.yaml\n+++ b/Documentation/netlink/specs/ethtool.yaml\n@@ -210,6 +210,11 @@ definitions:\n -\n name: discard\n value: 31\n+ -\n+ name: port-type\n+ doc: type of physical connector\n+ type: enum\n+ entries: [mdi, sfp]\n \n attribute-sets:\n -\n@@ -1905,6 +1910,32 @@ attribute-sets:\n name: link\n type: nest\n nested-attributes: mse-snapshot\n+ -\n+ name: port\n+ attr-cnt-name: --ethtool-a-port-cnt\n+ attributes:\n+ -\n+ name: header\n+ type: nest\n+ nested-attributes: header\n+ -\n+ name: id\n+ type: u32\n+ -\n+ name: supported-modes\n+ type: nest\n+ nested-attributes: bitset\n+ -\n+ name: supported-interfaces\n+ type: nest\n+ nested-attributes: bitset\n+ -\n+ name: type\n+ type: u32\n+ enum: port-type\n+ -\n+ name: upstream-port\n+ type: u32\n \n operations:\n enum-model: directional\n@@ -2859,6 +2890,29 @@ operations:\n - worst-channel\n - link\n dump: *mse-get-op\n+ -\n+ name: port-get\n+ doc: Get ports attached to an interface\n+\n+ attribute-set: port\n+\n+ do: \u0026port-get-op\n+ request:\n+ attributes:\n+ - header\n+ - id\n+ reply:\n+ attributes:\n+ - header\n+ - id\n+ - supported-modes\n+ - supported-interfaces\n+ - type\n+ - upstream-port\n+ dump:\n+ request:\n+ attributes:\n+ - header\n \n mcast-groups:\n list:\ndiff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst\nindex e92abf45faf50..b4326c89b0751 100644\n--- a/Documentation/networking/ethtool-netlink.rst\n+++ b/Documentation/networking/ethtool-netlink.rst\n@@ -2537,6 +2537,39 @@ Within each channel nest, only the metrics supported by the PHY will be present.\n See ``struct phy_mse_snapshot`` kernel documentation in\n ``include/linux/phy.h``.\n \n+PORT_GET\n+========\n+\n+Retrieve information about the physical connection points of a network device,\n+referred to as \"ports\". User needs to specify a PORT_ID for the DO operation,\n+in which case the DO request returns information about that specific port.\n+\n+As there can be more than one port, the DUMP operation can be used to list the\n+ports present on a given interface, by passing an interface index or name in\n+the dump request.\n+\n+Request contents:\n+\n+ ===================================== ====== ===============================\n+ ``ETHTOOL_A_PORT_HEADER`` nested request header\n+ ``ETHTOOL_A_PORT_ID`` u32 port id\n+ ===================================== ====== ===============================\n+\n+Kernel response contents:\n+\n+ ======================================= ====== =============================\n+ ``ETHTOOL_A_PORT_HEADER`` nested request header\n+ ``ETHTOOL_A_PORT_ID`` u32 the port's unique identifier,\n+ per netdevice.\n+ ``ETHTOOL_A_PORT_SUPPORTED_MODES`` bitset bitset of supported linkmodes\n+ ``ETHTOOL_A_PORT_SUPPORTED_INTERFACES`` bitset bitset of supported MII\n+ interfaces\n+ ``ETHTOOL_A_PORT_TYPE`` u32 the port type\n+ ``ETHTOOL_A_PORT_UPSTREAM_PORT`` u32 If any, the index of the MII\n+ port that feeds into this\n+ port.\n+ ======================================= ====== =============================\n+\n Request translation\n ===================\n \n@@ -2647,4 +2680,5 @@ are netlink only.\n n/a ``ETHTOOL_MSG_PHY_GET``\n ``SIOCGHWTSTAMP`` ``ETHTOOL_MSG_TSCONFIG_GET``\n ``SIOCSHWTSTAMP`` ``ETHTOOL_MSG_TSCONFIG_SET``\n+ n/a ``ETHTOOL_MSG_PORT_GET``\n =================================== =====================================\ndiff --git a/Documentation/networking/phy-port.rst b/Documentation/networking/phy-port.rst\nindex 6e28d9094bce3..2a9b1ec93b70f 100644\n--- a/Documentation/networking/phy-port.rst\n+++ b/Documentation/networking/phy-port.rst\n@@ -99,13 +99,29 @@ will eventually be able to report its own ksettings::\n (_____)-----| Port |\n +------+\n \n+SFP ports\n+=========\n+\n+SFP interfaces involve 2 distinct components, each represented by\n+a :c:type:`struct phy_port \u003cphy_port\u003e` instance :\n+\n+ - The SFP cage itself is a :c:type:`struct phy_port \u003cphy_port\u003e`. It's special\n+ in that it's not an MDI interface, but rather a hot-pluggable MII.\n+ The :c:type:`struct phy_port \u003cphy_port\u003e` associated to it lists the different\n+ MII interfaces we can use on the cage.\n+\n+ - The SFP module, when inserted, will also be associated to a\n+ :c:type:`struct phy_port \u003cphy_port\u003e`, that represents the various linkmodes\n+ that it gives access to. The module's :c:type:`struct phy_port \u003cphy_port\u003e`\n+ doesn't supersede the cage's port, it references it through\n+ the :c:type:`struct phy_port \u003cphy_port\u003e` :c:member:`upstream_port` field.\n+\n Next steps\n ==========\n \n-As of writing this documentation, only ports controlled by PHY devices are\n-supported. The next steps will be to add the Netlink API to expose these\n-to userspace and add support for raw ports (controlled by some firmware, and directly\n-managed by the NIC driver).\n+As of writing this documentation, the port's presence and information can only\n+be queried, and it's not possible to change any of the port's settings or select\n+which one should be used.\n \n Another parallel task is the introduction of a MII muxing framework to allow the\n-control of non-PHY driver multi-port setups.\n+control of non-PHY driven multi-port setups.\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 932ea1db048e5..395f8b5d18495 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -18816,6 +18816,7 @@ F:\tDocumentation/devicetree/bindings/net/ethernet-connector.yaml\n F:\tDocumentation/networking/phy-port.rst\n F:\tdrivers/net/phy/phy_port.c\n F:\tinclude/linux/phy_port.h\n+F:\tnet/ethtool/port.c\n K:\tstruct\\s+phy_port|phy_port_\n \n NETWORKING [GENERAL]\ndiff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h\nindex 421088e6f6e8f..ec3d39a0ae06b 100644\n--- a/drivers/net/phy/phy-caps.h\n+++ b/drivers/net/phy/phy-caps.h\n@@ -66,5 +66,7 @@ void phy_caps_medium_get_supported(unsigned long *supported,\n \t\t\t\t enum ethtool_link_medium medium,\n \t\t\t\t int lanes);\n u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes);\n+void phy_caps_linkmode_filter_ifaces(unsigned long *to, const unsigned long *from,\n+\t\t\t\t const unsigned long *interfaces);\n \n #endif /* __PHY_CAPS_H */\ndiff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c\nindex 942d43191561e..558e4df4d63c0 100644\n--- a/drivers/net/phy/phy_caps.c\n+++ b/drivers/net/phy/phy_caps.c\n@@ -445,3 +445,29 @@ u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes)\n \treturn mediums;\n }\n EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes);\n+\n+/**\n+ * phy_caps_linkmode_filter_ifaces() - Filter linkmodes with an interface list\n+ * @to: Stores the filtered linkmodes\n+ * @from: Linkmodes to filter\n+ * @interfaces: Bitfield of phy_interface_t that we use for filtering\n+ *\n+ * Filter the provided linkmodes, only to keep the ones we can possibly achieve\n+ * when using any of the provided MII interfaces.\n+ */\n+void phy_caps_linkmode_filter_ifaces(unsigned long *to,\n+\t\t\t\t const unsigned long *from,\n+\t\t\t\t const unsigned long *interfaces)\n+{\n+\t__ETHTOOL_DECLARE_LINK_MODE_MASK(ifaces_supported) = {};\n+\tunsigned int ifaces_caps = 0;\n+\tphy_interface_t interface;\n+\n+\tfor_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)\n+\t\tifaces_caps |= phy_caps_from_interface(interface);\n+\n+\tphy_caps_linkmodes(ifaces_caps, ifaces_supported);\n+\n+\tlinkmode_and(to, from, ifaces_supported);\n+}\n+EXPORT_SYMBOL_GPL(phy_caps_linkmode_filter_ifaces);\ndiff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c\nindex 94b2e85e00a37..3da6872ebbba0 100644\n--- a/drivers/net/phy/phy_device.c\n+++ b/drivers/net/phy/phy_device.c\n@@ -1490,11 +1490,22 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)\n {\n \tstruct phy_device *phydev = upstream;\n \tstruct net_device *dev = phydev-\u003eattached_dev;\n+\tint ret;\n \n-\tif (dev)\n-\t\treturn phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev);\n+\tphydev-\u003ehas_sfp_mod_phy = true;\n+\tphy_set_upstream_port(phy, phydev-\u003esfp_cage_port);\n \n-\treturn 0;\n+\t/* If we aren't attached to a netdev, we can't add the SFP PHY to its\n+\t * topology.\n+\t */\n+\tif (!dev)\n+\t\treturn 0;\n+\n+\tret = phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev);\n+\tif (ret)\n+\t\tphydev-\u003ehas_sfp_mod_phy = false;\n+\n+\treturn ret;\n }\n \n /**\n@@ -1512,8 +1523,12 @@ static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)\n \tstruct phy_device *phydev = upstream;\n \tstruct net_device *dev = phydev-\u003eattached_dev;\n \n+\tphydev-\u003ehas_sfp_mod_phy = false;\n+\n \tif (dev)\n \t\tphy_link_topo_del_phy(dev, phy);\n+\n+\tphy_set_upstream_port(phy, NULL);\n }\n \n /**\n@@ -1617,6 +1632,77 @@ static void phy_sfp_link_down(void *upstream)\n \t\tport-\u003eops-\u003elink_down(port);\n }\n \n+static int phy_add_sfp_mod_port(struct phy_device *phydev)\n+{\n+\tconst struct sfp_module_caps *caps;\n+\tstruct phy_port *port;\n+\tint ret = 0;\n+\n+\t/* Create mod port */\n+\tport = phy_port_alloc();\n+\tif (!port)\n+\t\treturn -ENOMEM;\n+\n+\tport-\u003eactive = true;\n+\n+\tcaps = sfp_get_module_caps(phydev-\u003esfp_bus);\n+\n+\tphy_caps_linkmode_filter_ifaces(port-\u003esupported, caps-\u003elink_modes,\n+\t\t\t\t\tphydev-\u003esfp_cage_port-\u003einterfaces);\n+\n+\tif (phydev-\u003eattached_dev) {\n+\t\tret = phy_link_topo_add_port(phydev-\u003eattached_dev, port);\n+\t\tif (ret) {\n+\t\t\tphy_port_destroy(port);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\t/* we don't use phy_add_port() here as the module port isn't a direct\n+\t * interface from the PHY, but rather an extension to the sfp-bus, that\n+\t * is already represented by its own phy_port\n+\t */\n+\tphydev-\u003emod_port = port;\n+\n+\tport-\u003eupstream_port = phydev-\u003esfp_cage_port;\n+\n+\treturn 0;\n+}\n+\n+static void phy_del_sfp_mod_port(struct phy_device *phydev)\n+{\n+\tif (!phydev-\u003emod_port)\n+\t\treturn;\n+\n+\tif (phydev-\u003eattached_dev)\n+\t\tphy_link_topo_del_port(phydev-\u003eattached_dev, phydev-\u003emod_port);\n+\n+\tphy_port_destroy(phydev-\u003emod_port);\n+\tphydev-\u003emod_port = NULL;\n+}\n+\n+static int phy_sfp_module_start(void *upstream)\n+{\n+\tstruct phy_device *phydev = upstream;\n+\n+\t/* If there's a downstream SFP module, and it doesn't contain a PHY\n+\t * device, let's create a phy_port to represent that module.\n+\t */\n+\tif (!phydev-\u003ehas_sfp_mod_phy)\n+\t\treturn phy_add_sfp_mod_port(phydev);\n+\n+\treturn 0;\n+}\n+\n+static void phy_sfp_module_stop(void *upstream)\n+{\n+\tstruct phy_device *phydev = upstream;\n+\n+\t/* Called upon module removal or upstream removal */\n+\tif (!phydev-\u003ehas_sfp_mod_phy)\n+\t\tphy_del_sfp_mod_port(phydev);\n+}\n+\n static const struct sfp_upstream_ops sfp_phydev_ops = {\n \t.attach = phy_sfp_attach,\n \t.detach = phy_sfp_detach,\n@@ -1626,6 +1712,8 @@ static const struct sfp_upstream_ops sfp_phydev_ops = {\n \t.link_down = phy_sfp_link_down,\n \t.connect_phy = phy_sfp_connect_phy,\n \t.disconnect_phy = phy_sfp_disconnect_phy,\n+\t.module_start = phy_sfp_module_start,\n+\t.module_stop = phy_sfp_module_stop,\n };\n \n static int phy_add_port(struct phy_device *phydev, struct phy_port *port)\n@@ -1673,13 +1761,13 @@ static void phy_del_port(struct phy_device *phydev, struct phy_port *port)\n \tphydev-\u003en_ports--;\n }\n \n-static int phy_setup_sfp_port(struct phy_device *phydev)\n+static struct phy_port *phy_setup_sfp_port(struct phy_device *phydev)\n {\n \tstruct phy_port *port = phy_port_alloc();\n \tint ret;\n \n \tif (!port)\n-\t\treturn -ENOMEM;\n+\t\treturn ERR_PTR(-ENOMEM);\n \n \tport-\u003eparent_type = PHY_PORT_PHY;\n \tport-\u003ephy = phydev;\n@@ -1694,10 +1782,12 @@ static int phy_setup_sfp_port(struct phy_device *phydev)\n \t * when attaching the port to the phydev.\n \t */\n \tret = phy_add_port(phydev, port);\n-\tif (ret)\n+\tif (ret) {\n \t\tphy_port_destroy(port);\n+\t\treturn ERR_PTR(ret);\n+\t}\n \n-\treturn ret;\n+\treturn port;\n }\n \n /**\n@@ -1706,25 +1796,50 @@ static int phy_setup_sfp_port(struct phy_device *phydev)\n */\n static int phy_sfp_probe(struct phy_device *phydev)\n {\n+\tstruct phy_port *port = NULL;\n \tstruct sfp_bus *bus;\n-\tint ret = 0;\n+\tint ret;\n \n-\tif (phydev-\u003emdio.dev.fwnode) {\n-\t\tbus = sfp_bus_find_fwnode(phydev-\u003emdio.dev.fwnode);\n-\t\tif (IS_ERR(bus))\n-\t\t\treturn PTR_ERR(bus);\n+\tif (!phydev-\u003emdio.dev.fwnode)\n+\t\treturn 0;\n \n-\t\tphydev-\u003esfp_bus = bus;\n+\tbus = sfp_bus_find_fwnode(phydev-\u003emdio.dev.fwnode);\n+\tif (IS_ERR(bus))\n+\t\treturn PTR_ERR(bus);\n \n-\t\tret = sfp_bus_add_upstream(bus, phydev, \u0026sfp_phydev_ops);\n-\t\tsfp_bus_put(bus);\n+\tphydev-\u003esfp_bus = bus;\n \n-\t\tif (ret)\n-\t\t\tphydev-\u003esfp_bus = NULL;\n+\tif (bus) {\n+\t\tport = phy_setup_sfp_port(phydev);\n+\t\tif (IS_ERR(port)) {\n+\t\t\tret = PTR_ERR(port);\n+\t\t\tport = NULL;\n+\t\t\tgoto out_sfp;\n+\t\t}\n \t}\n \n-\tif (!ret \u0026\u0026 phydev-\u003esfp_bus)\n-\t\tret = phy_setup_sfp_port(phydev);\n+\tphydev-\u003esfp_cage_port = port;\n+\n+\tret = sfp_bus_add_upstream(bus, phydev, \u0026sfp_phydev_ops);\n+\tif (ret)\n+\t\tgoto out_port;\n+\n+\t/* sfp_bus_add_upstream() grabs a ref to the sfp bus on success, it's\n+\t * safe to release it now.\n+\t */\n+\tsfp_bus_put(bus);\n+\n+\treturn ret;\n+\n+out_port:\n+\tif (port) {\n+\t\tphy_del_port(phydev, port);\n+\t\tphy_port_destroy(port);\n+\t\tphydev-\u003esfp_cage_port = NULL;\n+\t}\n+out_sfp:\n+\tsfp_bus_put(bus);\n+\tphydev-\u003esfp_bus = NULL;\n \n \treturn ret;\n }\n@@ -1815,6 +1930,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,\n \t\terr = phy_link_topo_add_phy(dev, phydev, PHY_UPSTREAM_MAC, dev);\n \t\tif (err)\n \t\t\tgoto error;\n+\n+\t\tif (phydev-\u003emod_port) {\n+\t\t\terr = phy_link_topo_add_port(dev, phydev-\u003emod_port);\n+\t\t\tif (err)\n+\t\t\t\tgoto error;\n+\t\t}\n \t}\n \n \t/* Some Ethernet drivers try to connect to a PHY device before\n@@ -1951,6 +2072,8 @@ void phy_detach(struct phy_device *phydev)\n \t\tphydev-\u003eattached_dev-\u003ephydev = NULL;\n \t\tphydev-\u003eattached_dev = NULL;\n \t\tphy_link_topo_del_phy(dev, phydev);\n+\t\tif (phydev-\u003emod_port)\n+\t\t\tphy_link_topo_del_port(dev, phydev-\u003emod_port);\n \t}\n \n \tphydev-\u003ephy_link_change = NULL;\n@@ -3604,6 +3727,25 @@ struct phy_port *phy_get_sfp_port(struct phy_device *phydev)\n }\n EXPORT_SYMBOL_GPL(phy_get_sfp_port);\n \n+/**\n+ * phy_set_upstream_port() - Sets the phy_port controlling the MII this PHY is\n+ *\t\t\t attached to.\n+ * @phydev: pointer to the PHY device we set the upstream of.\n+ * @port: The phy_port upstream of this PHY, can be NULL.\n+ */\n+void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port)\n+{\n+\tstruct phy_port *local_port;\n+\n+\tASSERT_RTNL();\n+\n+\tphydev-\u003eupstream_port = port;\n+\n+\tphy_for_each_port(phydev, local_port)\n+\t\tlocal_port-\u003eupstream_port = port;\n+}\n+EXPORT_SYMBOL_GPL(phy_set_upstream_port);\n+\n /**\n * fwnode_mdio_find_device - Given a fwnode, find the mdio_device\n * @fwnode: pointer to the mdio_device's fwnode\n@@ -3842,6 +3984,7 @@ static int phy_remove(struct device *dev)\n \n \tsfp_bus_del_upstream(phydev-\u003esfp_bus);\n \tphydev-\u003esfp_bus = NULL;\n+\tphydev-\u003esfp_cage_port = NULL;\n \n \tphy_cleanup_ports(phydev);\n \ndiff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c\nindex 4134de7ae313d..6710a199967a1 100644\n--- a/drivers/net/phy/phy_link_topology.c\n+++ b/drivers/net/phy/phy_link_topology.c\n@@ -7,6 +7,7 @@\n */\n \n #include \u003clinux/phy_link_topology.h\u003e\n+#include \u003clinux/phy_port.h\u003e\n #include \u003clinux/phy.h\u003e\n #include \u003clinux/rtnetlink.h\u003e\n #include \u003clinux/xarray.h\u003e\n@@ -23,17 +24,89 @@ static int netdev_alloc_phy_link_topology(struct net_device *dev)\n \txa_init_flags(\u0026topo-\u003ephys, XA_FLAGS_ALLOC1);\n \ttopo-\u003enext_phy_index = 1;\n \n+\txa_init_flags(\u0026topo-\u003eports, XA_FLAGS_ALLOC1);\n+\ttopo-\u003enext_port_index = 1;\n+\n \tdev-\u003elink_topo = topo;\n \n \treturn 0;\n }\n \n+static struct phy_link_topology *phy_link_topo_get_or_alloc(struct net_device *dev)\n+{\n+\tint ret;\n+\n+\tif (dev-\u003elink_topo)\n+\t\treturn dev-\u003elink_topo;\n+\n+\t/* The topology is allocated the first time we add an object to it.\n+\t * It is freed alongside the netdev. It can be called on multiple\n+\t * contexts:\n+\t * - It can be called from .probe() : No rtnl, no netdev_lock\n+\t * - .ndo_open() : rtnl and possibly netdev_lock\n+\t * - SFP state machine : rtnl held or not\n+\t *\n+\t * However, we can't really have races :\n+\t * - If we have a PHY, phy_link_topo_add_phy() will always run first\n+\t * and trigger the alloc. Only then the ports can be added through\n+\t * phylib of sfp.\n+\t * - If we don't, the SFP port for the cage is registered first, and\n+\t * only then other ports/PHYs can be registered.\n+\t */\n+\tret = netdev_alloc_phy_link_topology(dev);\n+\tif (ret)\n+\t\treturn ERR_PTR(ret);\n+\n+\treturn dev-\u003elink_topo;\n+}\n+\n+int phy_link_topo_add_port(struct net_device *dev, struct phy_port *port)\n+{\n+\tstruct phy_link_topology *topo;\n+\tint ret;\n+\n+\t/* Ports can now be queried without rtnl for ops-locked devices, which\n+\t * we don't support now as port retrieval is done under rtnl.\n+\t * We don't have phy_port enabled devices yet, let's make sure\n+\t * we are loudly warned about that when it happens.\n+\t */\n+\tif (WARN_ON_ONCE(netdev_need_ops_lock(dev)))\n+\t\treturn -EOPNOTSUPP;\n+\n+\ttopo = phy_link_topo_get_or_alloc(dev);\n+\tif (IS_ERR(topo))\n+\t\treturn PTR_ERR(topo);\n+\n+\t/* Attempt to re-use a previously allocated port_id */\n+\tif (port-\u003eid)\n+\t\tret = xa_insert(\u0026topo-\u003eports, port-\u003eid, port, GFP_KERNEL);\n+\telse\n+\t\tret = xa_alloc_cyclic(\u0026topo-\u003eports, \u0026port-\u003eid, port,\n+\t\t\t\t xa_limit_32b, \u0026topo-\u003enext_port_index,\n+\t\t\t\t GFP_KERNEL);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(phy_link_topo_add_port);\n+\n+void phy_link_topo_del_port(struct net_device *dev, struct phy_port *port)\n+{\n+\tstruct phy_link_topology *topo = dev-\u003elink_topo;\n+\n+\tif (!topo)\n+\t\treturn;\n+\n+\txa_erase(\u0026topo-\u003eports, port-\u003eid);\n+}\n+EXPORT_SYMBOL_GPL(phy_link_topo_del_port);\n+\n int phy_link_topo_add_phy(struct net_device *dev,\n \t\t\t struct phy_device *phy,\n \t\t\t enum phy_upstream upt, void *upstream)\n {\n-\tstruct phy_link_topology *topo = dev-\u003elink_topo;\n+\tstruct phy_link_topology *topo;\n \tstruct phy_device_node *pdn;\n+\tstruct phy_port *port;\n \tint ret;\n \n \t/* ethtool ops may run without rtnl_lock, and rtnl_lock is what\n@@ -45,13 +118,9 @@ int phy_link_topo_add_phy(struct net_device *dev,\n \tif (WARN_ON_ONCE(netdev_need_ops_lock(dev)))\n \t\treturn -EOPNOTSUPP;\n \n-\tif (!topo) {\n-\t\tret = netdev_alloc_phy_link_topology(dev);\n-\t\tif (ret)\n-\t\t\treturn ret;\n-\n-\t\ttopo = dev-\u003elink_topo;\n-\t}\n+\ttopo = phy_link_topo_get_or_alloc(dev);\n+\tif (IS_ERR(topo))\n+\t\treturn PTR_ERR(topo);\n \n \tpdn = kzalloc_obj(*pdn);\n \tif (!pdn)\n@@ -86,8 +155,20 @@ int phy_link_topo_add_phy(struct net_device *dev,\n \tif (ret \u003c 0)\n \t\tgoto err;\n \n+\t/* Add all the PHY's ports to the topology */\n+\tlist_for_each_entry(port, \u0026phy-\u003eports, head) {\n+\t\tret = phy_link_topo_add_port(dev, port);\n+\t\tif (ret)\n+\t\t\tgoto del_ports;\n+\t}\n+\n \treturn 0;\n \n+del_ports:\n+\tlist_for_each_entry_continue_reverse(port, \u0026phy-\u003eports, head)\n+\t\tphy_link_topo_del_port(dev, port);\n+\n+\txa_erase(\u0026topo-\u003ephys, phy-\u003ephyindex);\n err:\n \tkfree(pdn);\n \treturn ret;\n@@ -99,10 +180,14 @@ void phy_link_topo_del_phy(struct net_device *dev,\n {\n \tstruct phy_link_topology *topo = dev-\u003elink_topo;\n \tstruct phy_device_node *pdn;\n+\tstruct phy_port *port;\n \n \tif (!topo)\n \t\treturn;\n \n+\tlist_for_each_entry(port, \u0026phy-\u003eports, head)\n+\t\tphy_link_topo_del_port(dev, port);\n+\n \tpdn = xa_erase(\u0026topo-\u003ephys, phy-\u003ephyindex);\n \n \t/* We delete the PHY from the topology, however we don't re-set the\ndiff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c\nindex f40acc0d41338..a5f025187afa2 100644\n--- a/drivers/net/phy/phylink.c\n+++ b/drivers/net/phy/phylink.c\n@@ -14,6 +14,8 @@\n #include \u003clinux/of_mdio.h\u003e\n #include \u003clinux/phy.h\u003e\n #include \u003clinux/phy_fixed.h\u003e\n+#include \u003clinux/phy_link_topology.h\u003e\n+#include \u003clinux/phy_port.h\u003e\n #include \u003clinux/phylink.h\u003e\n #include \u003clinux/rtnetlink.h\u003e\n #include \u003clinux/spinlock.h\u003e\n@@ -93,6 +95,8 @@ struct phylink {\n \tDECLARE_PHY_INTERFACE_MASK(sfp_interfaces);\n \t__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);\n \tu8 sfp_port;\n+\tstruct phy_port *sfp_cage_port;\n+\tstruct phy_port *mod_port;\n \n \tstruct eee_config eee_cfg;\n \n@@ -1764,6 +1768,51 @@ static void phylink_fixed_poll(struct timer_list *t)\n \n static const struct sfp_upstream_ops sfp_phylink_ops;\n \n+static int phylink_create_sfp_cage_port(struct phylink *pl)\n+{\n+\tstruct phy_port *port;\n+\tint ret = 0;\n+\n+\tif (!pl-\u003enetdev || !pl-\u003esfp_bus)\n+\t\treturn 0;\n+\n+\tport = phy_port_alloc();\n+\tif (!port)\n+\t\treturn -ENOMEM;\n+\n+\tport-\u003eis_sfp = true;\n+\tport-\u003eis_mii = true;\n+\tport-\u003eactive = true;\n+\n+\tphy_interface_and(port-\u003einterfaces, pl-\u003econfig-\u003esupported_interfaces,\n+\t\t\t phylink_sfp_interfaces);\n+\tphy_port_update_supported(port);\n+\n+\tret = phy_link_topo_add_port(pl-\u003enetdev, port);\n+\tif (ret)\n+\t\tgoto out_destroy_port;\n+\n+\tpl-\u003esfp_cage_port = port;\n+\n+\treturn 0;\n+\n+out_destroy_port:\n+\tphy_port_destroy(port);\n+\tpl-\u003esfp_cage_port = NULL;\n+\treturn ret;\n+}\n+\n+static void phylink_destroy_sfp_cage_port(struct phylink *pl)\n+{\n+\tif (pl-\u003enetdev \u0026\u0026 pl-\u003esfp_cage_port)\n+\t\tphy_link_topo_del_port(pl-\u003enetdev, pl-\u003esfp_cage_port);\n+\n+\tif (pl-\u003esfp_cage_port)\n+\t\tphy_port_destroy(pl-\u003esfp_cage_port);\n+\n+\tpl-\u003esfp_cage_port = NULL;\n+}\n+\n static int phylink_register_sfp(struct phylink *pl,\n \t\t\t\tconst struct fwnode_handle *fwnode)\n {\n@@ -1781,9 +1830,18 @@ static int phylink_register_sfp(struct phylink *pl,\n \n \tpl-\u003esfp_bus = bus;\n \n+\tret = phylink_create_sfp_cage_port(pl);\n+\tif (ret) {\n+\t\tsfp_bus_put(bus);\n+\t\treturn ret;\n+\t}\n+\n \tret = sfp_bus_add_upstream(bus, pl, \u0026sfp_phylink_ops);\n \tsfp_bus_put(bus);\n \n+\tif (ret)\n+\t\tphylink_destroy_sfp_cage_port(pl);\n+\n \treturn ret;\n }\n \n@@ -1946,6 +2004,7 @@ EXPORT_SYMBOL_GPL(phylink_create);\n void phylink_destroy(struct phylink *pl)\n {\n \tsfp_bus_del_upstream(pl-\u003esfp_bus);\n+\tphylink_destroy_sfp_cage_port(pl);\n \tif (pl-\u003elink_gpio)\n \t\tgpiod_put(pl-\u003elink_gpio);\n \n@@ -3871,14 +3930,67 @@ static void phylink_sfp_module_remove(void *upstream)\n \tphy_interface_zero(pl-\u003esfp_interfaces);\n }\n \n+static int phylink_add_sfp_mod_port(struct phylink *pl)\n+{\n+\tconst struct sfp_module_caps *caps;\n+\tstruct phy_port *port;\n+\tint ret = 0;\n+\n+\tif (!pl-\u003esfp_cage_port)\n+\t\treturn 0;\n+\n+\t/* Create mod port */\n+\tport = phy_port_alloc();\n+\tif (!port)\n+\t\treturn -ENOMEM;\n+\n+\tport-\u003eactive = true;\n+\n+\tcaps = sfp_get_module_caps(pl-\u003esfp_bus);\n+\n+\tphy_caps_linkmode_filter_ifaces(port-\u003esupported, caps-\u003elink_modes,\n+\t\t\t\t\tpl-\u003esfp_cage_port-\u003einterfaces);\n+\n+\tif (pl-\u003enetdev) {\n+\t\tret = phy_link_topo_add_port(pl-\u003enetdev, port);\n+\t\tif (ret) {\n+\t\t\tphy_port_destroy(port);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tport-\u003eupstream_port = pl-\u003esfp_cage_port;\n+\n+\tpl-\u003emod_port = port;\n+\n+\treturn 0;\n+}\n+\n+static void phylink_del_sfp_mod_port(struct phylink *pl)\n+{\n+\tif (!pl-\u003emod_port)\n+\t\treturn;\n+\n+\tif (pl-\u003enetdev)\n+\t\tphy_link_topo_del_port(pl-\u003enetdev, pl-\u003emod_port);\n+\n+\tphy_port_destroy(pl-\u003emod_port);\n+\tpl-\u003emod_port = NULL;\n+}\n+\n static int phylink_sfp_module_start(void *upstream)\n {\n \tstruct phylink *pl = upstream;\n+\tint ret;\n \n \t/* If this SFP module has a PHY, start the PHY now. */\n \tif (pl-\u003ephydev) {\n \t\tphy_start(pl-\u003ephydev);\n \t\treturn 0;\n+\t} else {\n+\t\tret = phylink_add_sfp_mod_port(pl);\n+\t\tif (ret)\n+\t\t\treturn ret;\n \t}\n \n \t/* If the module may have a PHY but we didn't detect one we\n@@ -3887,7 +3999,16 @@ static int phylink_sfp_module_start(void *upstream)\n \tif (!pl-\u003esfp_may_have_phy)\n \t\treturn 0;\n \n-\treturn phylink_sfp_config_optical(pl);\n+\tret = phylink_sfp_config_optical(pl);\n+\tif (ret)\n+\t\tgoto del_mod_port;\n+\n+\treturn 0;\n+\n+del_mod_port:\n+\tphylink_del_sfp_mod_port(pl);\n+\n+\treturn ret;\n }\n \n static void phylink_sfp_module_stop(void *upstream)\n@@ -3897,6 +4018,8 @@ static void phylink_sfp_module_stop(void *upstream)\n \t/* If this SFP module has a PHY, stop it. */\n \tif (pl-\u003ephydev)\n \t\tphy_stop(pl-\u003ephydev);\n+\telse\n+\t\tphylink_del_sfp_mod_port(pl);\n }\n \n static void phylink_sfp_link_down(void *upstream)\n@@ -3941,6 +4064,8 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)\n \tphy_interface_and(phy-\u003ehost_interfaces, phylink_sfp_interfaces,\n \t\t\t pl-\u003econfig-\u003esupported_interfaces);\n \n+\tphy_set_upstream_port(phy, pl-\u003esfp_cage_port);\n+\n \t/* Do the initial configuration */\n \treturn phylink_sfp_config_phy(pl, phy);\n }\n@@ -3949,6 +4074,7 @@ static void phylink_sfp_disconnect_phy(void *upstream,\n \t\t\t\t struct phy_device *phydev)\n {\n \tphylink_disconnect_phy(upstream);\n+\tphy_set_upstream_port(phydev, NULL);\n }\n \n static const struct sfp_upstream_ops sfp_phylink_ops = {\ndiff --git a/include/linux/phy.h b/include/linux/phy.h\nindex 11092c3175b3d..741ca7a0a2573 100644\n--- a/include/linux/phy.h\n+++ b/include/linux/phy.h\n@@ -582,6 +582,7 @@ struct phy_oatc14_sqi_capability {\n * @wol_enabled: Set to true if the PHY or the attached MAC have Wake-on-LAN\n * \t\t enabled.\n * @is_genphy_driven: PHY is driven by one of the generic PHY drivers\n+ * @has_sfp_mod_phy: Set true if downstream SFP bus's module contains a PHY\n * @state: State of the PHY for management purposes\n * @dev_flags: Device-specific flags used by the PHY driver.\n *\n@@ -594,6 +595,9 @@ struct phy_oatc14_sqi_capability {\n * @phylink: Pointer to phylink instance for this PHY\n * @sfp_bus_attached: Flag indicating whether the SFP bus has been attached\n * @sfp_bus: SFP bus attached to this PHY's fiber port\n+ * @sfp_cage_port: The phy_port connected to the downstream SFP cage\n+ * @mod_port: phy_port representing the SFP module, if it is phy-less\n+ * @upstream_port: phy_port this PHY's MII attaches to, if any\n * @attached_dev: The attached enet driver's device instance ptr\n * @adjust_link: Callback for the enet controller to respond to changes: in the\n * link state.\n@@ -706,6 +710,7 @@ struct phy_device {\n \tunsigned irq_rerun:1;\n \n \tunsigned default_timestamp:1;\n+\tunsigned has_sfp_mod_phy:1;\n \n \tint rate_matching;\n \n@@ -785,6 +790,9 @@ struct phy_device {\n \t/* This may be modified under the rtnl lock */\n \tbool sfp_bus_attached;\n \tstruct sfp_bus *sfp_bus;\n+\tstruct phy_port *sfp_cage_port;\n+\tstruct phy_port *mod_port;\n+\tstruct phy_port *upstream_port;\n \tstruct phylink *phylink;\n \tstruct net_device *attached_dev;\n \tstruct mii_timestamper *mii_ts;\n@@ -2468,6 +2476,8 @@ int __phy_hwtstamp_set(struct phy_device *phydev,\n \n struct phy_port *phy_get_sfp_port(struct phy_device *phydev);\n \n+void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port);\n+\n /**\n * phy_module_driver() - Helper macro for registering PHY drivers\n * @__phy_drivers: array of PHY drivers to register\ndiff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h\nindex 95575f68d5bc7..9585282cae3fc 100644\n--- a/include/linux/phy_link_topology.h\n+++ b/include/linux/phy_link_topology.h\n@@ -13,14 +13,19 @@\n \n #include \u003clinux/ethtool.h\u003e\n #include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/rtnetlink.h\u003e\n \n struct xarray;\n struct phy_device;\n+struct phy_port;\n struct sfp_bus;\n \n struct phy_link_topology {\n \tstruct xarray phys;\n \tu32 next_phy_index;\n+\n+\tstruct xarray ports;\n+\tu32 next_port_index;\n };\n \n struct phy_device_node {\n@@ -48,6 +53,9 @@ int phy_link_topo_add_phy(struct net_device *dev,\n \n void phy_link_topo_del_phy(struct net_device *dev, struct phy_device *phy);\n \n+int phy_link_topo_add_port(struct net_device *dev, struct phy_port *port);\n+void phy_link_topo_del_port(struct net_device *dev, struct phy_port *port);\n+\n static inline struct phy_device *\n phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)\n {\n@@ -64,6 +72,25 @@ phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)\n \treturn NULL;\n }\n \n+static inline struct phy_port *\n+phy_link_topo_get_port(struct net_device *dev, u32 port_id)\n+{\n+\tstruct phy_link_topology *topo = dev-\u003elink_topo;\n+\n+\tif (!topo)\n+\t\treturn NULL;\n+\n+\t/* ports in the topo are RTNL protected, but may be accessed under\n+\t * netdev_lock for ops-locked devices. For now keep them under rtnl\n+\t * protection, as no ops-locked devices have phy_port capabilities\n+\t * yet.\n+\t */\n+\tASSERT_RTNL();\n+\n+\t/* Caller must hold RTNL while handling the phy_port */\n+\treturn xa_load(\u0026topo-\u003eports, port_id);\n+}\n+\n #else\n static inline int phy_link_topo_add_phy(struct net_device *dev,\n \t\t\t\t\tstruct phy_device *phy,\n@@ -77,11 +104,28 @@ static inline void phy_link_topo_del_phy(struct net_device *dev,\n {\n }\n \n+static inline int phy_link_topo_add_port(struct net_device *dev,\n+\t\t\t\t\t struct phy_port *port)\n+{\n+\treturn 0;\n+}\n+\n+static inline void phy_link_topo_del_port(struct net_device *dev,\n+\t\t\t\t\t struct phy_port *port)\n+{\n+}\n+\n static inline struct phy_device *\n phy_link_topo_get_phy(struct net_device *dev, u32 phyindex)\n {\n \treturn NULL;\n }\n+\n+static inline struct phy_port *\n+phy_link_topo_get_port(struct net_device *dev, u32 port_id)\n+{\n+\treturn NULL;\n+}\n #endif\n \n #endif /* __PHY_LINK_TOPOLOGY_H */\ndiff --git a/include/linux/phy_port.h b/include/linux/phy_port.h\nindex 0ef0f5ce47092..8f45c031cbc10 100644\n--- a/include/linux/phy_port.h\n+++ b/include/linux/phy_port.h\n@@ -36,9 +36,12 @@ struct phy_port_ops {\n /**\n * struct phy_port - A representation of a network device physical interface\n *\n+ * @id: Unique identifier for the port within the topology\n * @head: Used by the port's parent to list ports\n * @parent_type: The type of device this port is directly connected to\n * @phy: If the parent is PHY_PORT_PHYDEV, the PHY controlling that port\n+ * @upstream_port: Indicates the MII port that feeds this port, if any,\n+ *\t\t e.g. the SFP cage port for a SFP module port.\n * @ops: Callback ops implemented by the port controller\n * @pairs: The number of pairs this port has, 0 if not applicable\n * @mediums: Bitmask of the physical mediums this port provides access to\n@@ -52,11 +55,13 @@ struct phy_port_ops {\n * @is_sfp: Indicates if this port drives an SFP cage.\n */\n struct phy_port {\n+\tu32 id;\n \tstruct list_head head;\n \tenum phy_port_parent parent_type;\n \tunion {\n \t\tstruct phy_device *phy;\n \t};\n+\tstruct phy_port *upstream_port;\n \n \tconst struct phy_port_ops *ops;\n \ndiff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h\nindex 8134baf7860f3..f9d8794eabc19 100644\n--- a/include/uapi/linux/ethtool_netlink_generated.h\n+++ b/include/uapi/linux/ethtool_netlink_generated.h\n@@ -78,6 +78,11 @@ enum ethtool_pse_event {\n \tETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64,\n };\n \n+enum ethtool_port_type {\n+\tETHTOOL_PORT_TYPE_MDI,\n+\tETHTOOL_PORT_TYPE_SFP,\n+};\n+\n enum {\n \tETHTOOL_A_HEADER_UNSPEC,\n \tETHTOOL_A_HEADER_DEV_INDEX,\n@@ -840,6 +845,18 @@ enum {\n \tETHTOOL_A_MSE_MAX = (__ETHTOOL_A_MSE_CNT - 1)\n };\n \n+enum {\n+\tETHTOOL_A_PORT_HEADER = 1,\n+\tETHTOOL_A_PORT_ID,\n+\tETHTOOL_A_PORT_SUPPORTED_MODES,\n+\tETHTOOL_A_PORT_SUPPORTED_INTERFACES,\n+\tETHTOOL_A_PORT_TYPE,\n+\tETHTOOL_A_PORT_UPSTREAM_PORT,\n+\n+\t__ETHTOOL_A_PORT_CNT,\n+\tETHTOOL_A_PORT_MAX = (__ETHTOOL_A_PORT_CNT - 1)\n+};\n+\n enum {\n \tETHTOOL_MSG_USER_NONE = 0,\n \tETHTOOL_MSG_STRSET_GET = 1,\n@@ -893,6 +910,7 @@ enum {\n \tETHTOOL_MSG_RSS_CREATE_ACT,\n \tETHTOOL_MSG_RSS_DELETE_ACT,\n \tETHTOOL_MSG_MSE_GET,\n+\tETHTOOL_MSG_PORT_GET,\n \n \t__ETHTOOL_MSG_USER_CNT,\n \tETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)\n@@ -954,6 +972,7 @@ enum {\n \tETHTOOL_MSG_RSS_CREATE_NTF,\n \tETHTOOL_MSG_RSS_DELETE_NTF,\n \tETHTOOL_MSG_MSE_GET_REPLY,\n+\tETHTOOL_MSG_PORT_GET_REPLY,\n \n \t__ETHTOOL_MSG_KERNEL_CNT,\n \tETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)\ndiff --git a/net/core/dev.c b/net/core/dev.c\nindex c1c1be1a6962f..b36cc5c47816a 100644\n--- a/net/core/dev.c\n+++ b/net/core/dev.c\n@@ -11315,6 +11315,7 @@ static void netdev_free_phy_link_topology(struct net_device *dev)\n \n \tif (IS_ENABLED(CONFIG_PHYLIB) \u0026\u0026 topo) {\n \t\txa_destroy(\u0026topo-\u003ephys);\n+\t\txa_destroy(\u0026topo-\u003eports);\n \t\tkfree(topo);\n \t\tdev-\u003elink_topo = NULL;\n \t}\ndiff --git a/net/ethtool/Makefile b/net/ethtool/Makefile\nindex 629c10916670e..9b5b096700081 100644\n--- a/net/ethtool/Makefile\n+++ b/net/ethtool/Makefile\n@@ -9,4 +9,4 @@ ethtool_nl-y\t:= netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \\\n \t\t channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \\\n \t\t tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \\\n \t\t module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o \\\n-\t\t phy.o tsconfig.o mse.o\n+\t\t phy.o tsconfig.o mse.o port.o\ndiff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c\nindex 1af395b54330e..c076c07d0a080 100644\n--- a/net/ethtool/netlink.c\n+++ b/net/ethtool/netlink.c\n@@ -26,6 +26,8 @@ static u32 ethnl_bcast_seq;\n \t\t\t ETHTOOL_FLAG_OMIT_REPLY)\n #define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS)\n \n+char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN] __ro_after_init;\n+\n const struct nla_policy ethnl_header_policy[] = {\n \t[ETHTOOL_A_HEADER_DEV_INDEX]\t= { .type = NLA_U32 },\n \t[ETHTOOL_A_HEADER_DEV_NAME]\t= { .type = NLA_NUL_STRING,\n@@ -431,6 +433,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {\n \t[ETHTOOL_MSG_TSCONFIG_SET]\t= \u0026ethnl_tsconfig_request_ops,\n \t[ETHTOOL_MSG_PHY_GET]\t\t= \u0026ethnl_phy_request_ops,\n \t[ETHTOOL_MSG_MSE_GET]\t\t= \u0026ethnl_mse_request_ops,\n+\t[ETHTOOL_MSG_PORT_GET]\t\t= \u0026ethnl_port_request_ops,\n };\n \n static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)\n@@ -1572,6 +1575,15 @@ static const struct genl_ops ethtool_genl_ops[] = {\n \t\t.policy = ethnl_mse_get_policy,\n \t\t.maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,\n \t},\n+\t{\n+\t\t.cmd\t= ETHTOOL_MSG_PORT_GET,\n+\t\t.doit\t= ethnl_default_doit,\n+\t\t.start\t= ethnl_port_dump_start,\n+\t\t.dumpit\t= ethnl_port_dumpit,\n+\t\t.done\t= ethnl_port_dump_done,\n+\t\t.policy = ethnl_port_get_policy,\n+\t\t.maxattr = ARRAY_SIZE(ethnl_port_get_policy) - 1,\n+\t},\n };\n \n static const struct genl_multicast_group ethtool_nl_mcgrps[] = {\n@@ -1594,10 +1606,23 @@ static struct genl_family ethtool_genl_family __ro_after_init = {\n \n /* module setup */\n \n+static void __init ethnl_phy_names_populate(void)\n+{\n+\tconst char *name;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c PHY_INTERFACE_MODE_MAX; i++) {\n+\t\tname = phy_modes(i);\n+\t\tstrscpy(phy_interface_names[i], name, ETH_GSTRING_LEN);\n+\t}\n+}\n+\n static int __init ethnl_init(void)\n {\n \tint ret;\n \n+\tethnl_phy_names_populate();\n+\n \tret = genl_register_family(\u0026ethtool_genl_family);\n \tif (WARN(ret \u003c 0, \"ethtool: genetlink family registration failed\"))\n \t\treturn ret;\ndiff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h\nindex 3e969a070f9fe..dc7211f67c4d5 100644\n--- a/net/ethtool/netlink.h\n+++ b/net/ethtool/netlink.h\n@@ -5,11 +5,14 @@\n \n #include \u003clinux/ethtool_netlink.h\u003e\n #include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/phy.h\u003e\n #include \u003cnet/genetlink.h\u003e\n #include \u003cnet/sock.h\u003e\n \n struct ethnl_req_info;\n \n+extern char phy_interface_names[PHY_INTERFACE_MODE_MAX][ETH_GSTRING_LEN];\n+\n int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,\n \t\t\t const struct nlattr *nest, struct net *net,\n \t\t\t struct netlink_ext_ack *extack,\n@@ -474,6 +477,7 @@ extern const struct ethnl_request_ops ethnl_mm_request_ops;\n extern const struct ethnl_request_ops ethnl_phy_request_ops;\n extern const struct ethnl_request_ops ethnl_tsconfig_request_ops;\n extern const struct ethnl_request_ops ethnl_mse_request_ops;\n+extern const struct ethnl_request_ops ethnl_port_request_ops;\n \n extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];\n extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];\n@@ -530,6 +534,7 @@ extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1];\n extern const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFIG_HEADER + 1];\n extern const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFIG_MAX + 1];\n extern const struct nla_policy ethnl_mse_get_policy[ETHTOOL_A_MSE_HEADER + 1];\n+extern const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 1];\n \n int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);\n int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);\n@@ -545,6 +550,9 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);\n int ethnl_tsinfo_done(struct netlink_callback *cb);\n int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);\n int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info);\n+int ethnl_port_dump_start(struct netlink_callback *cb);\n+int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb);\n+int ethnl_port_dump_done(struct netlink_callback *cb);\n \n extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];\n extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];\ndiff --git a/net/ethtool/port.c b/net/ethtool/port.c\nnew file mode 100644\nindex 0000000000000..e1bcadbfe40c0\n--- /dev/null\n+++ b/net/ethtool/port.c\n@@ -0,0 +1,375 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright 2026 Bootlin\n+ */\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/phy_link_topology.h\u003e\n+#include \u003clinux/phy_port.h\u003e\n+#include \u003cnet/netdev_lock.h\u003e\n+\n+#include \"bitset.h\"\n+#include \"common.h\"\n+#include \"netlink.h\"\n+\n+struct port_req_info {\n+\tstruct ethnl_req_info base;\n+\tu32 port_id;\n+};\n+\n+struct port_reply_data {\n+\tstruct ethnl_reply_data\tbase;\n+\t__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);\n+\tDECLARE_PHY_INTERFACE_MASK(interfaces);\n+\tu32 port_id;\n+\tbool mii;\n+\tbool sfp;\n+\tu32 upstream_port;\n+};\n+\n+#define PORT_REQINFO(__req_base) \\\n+\tcontainer_of(__req_base, struct port_req_info, base)\n+\n+#define PORT_REPDATA(__reply_base) \\\n+\tcontainer_of(__reply_base, struct port_reply_data, base)\n+\n+const struct nla_policy ethnl_port_get_policy[ETHTOOL_A_PORT_ID + 1] = {\n+\t[ETHTOOL_A_PORT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),\n+\t[ETHTOOL_A_PORT_ID] = NLA_POLICY_MIN(NLA_U32, 1),\n+};\n+\n+static int port_parse_request(struct ethnl_req_info *req_info,\n+\t\t\t const struct genl_info *info,\n+\t\t\t struct nlattr **tb,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct port_req_info *request = PORT_REQINFO(req_info);\n+\n+\tif (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_PORT_ID))\n+\t\treturn -EINVAL;\n+\n+\trequest-\u003eport_id = nla_get_u32(tb[ETHTOOL_A_PORT_ID]);\n+\n+\treturn 0;\n+}\n+\n+static int port_prepare_data(const struct ethnl_req_info *req_info,\n+\t\t\t struct ethnl_reply_data *reply_data,\n+\t\t\t const struct genl_info *info)\n+{\n+\tstruct port_reply_data *reply = PORT_REPDATA(reply_data);\n+\tstruct port_req_info *request = PORT_REQINFO(req_info);\n+\tstruct phy_port *port;\n+\n+\t/* RTNL must be held while holding a ref to the phy_port. Here, caller\n+\t * holds RTNL.\n+\t */\n+\tport = phy_link_topo_get_port(req_info-\u003edev, request-\u003eport_id);\n+\tif (!port)\n+\t\treturn -ENODEV;\n+\n+\tlinkmode_copy(reply-\u003esupported, port-\u003esupported);\n+\tphy_interface_copy(reply-\u003einterfaces, port-\u003einterfaces);\n+\treply-\u003eport_id = port-\u003eid;\n+\treply-\u003emii = port-\u003eis_mii;\n+\treply-\u003esfp = port-\u003eis_sfp;\n+\treply-\u003eupstream_port = port-\u003eupstream_port ? port-\u003eupstream_port-\u003eid : 0;\n+\n+\treturn 0;\n+}\n+\n+static int port_reply_size(const struct ethnl_req_info *req_info,\n+\t\t\t const struct ethnl_reply_data *reply_data)\n+{\n+\tbool compact = req_info-\u003eflags \u0026 ETHTOOL_FLAG_COMPACT_BITSETS;\n+\tstruct port_reply_data *reply = PORT_REPDATA(reply_data);\n+\tsize_t size = 0;\n+\tint ret;\n+\n+\t/* ETHTOOL_A_PORT_ID */\n+\tsize += nla_total_size(sizeof(u32));\n+\n+\tif (!reply-\u003emii) {\n+\t\t/* ETHTOOL_A_PORT_SUPPORTED_MODES */\n+\t\tret = ethnl_bitset_size(reply-\u003esupported, NULL,\n+\t\t\t\t\t__ETHTOOL_LINK_MODE_MASK_NBITS,\n+\t\t\t\t\tlink_mode_names, compact);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\tsize += ret;\n+\t} else {\n+\t\t/* ETHTOOL_A_PORT_SUPPORTED_INTERFACES */\n+\t\tret = ethnl_bitset_size(reply-\u003einterfaces, NULL,\n+\t\t\t\t\tPHY_INTERFACE_MODE_MAX,\n+\t\t\t\t\tphy_interface_names, compact);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\tsize += ret;\n+\t}\n+\n+\t/* ETHTOOL_A_PORT_TYPE */\n+\tsize += nla_total_size(sizeof(u32));\n+\n+\t/* ETHTOOL_A_PORT_UPSTREAM_PORT */\n+\tif (reply-\u003eupstream_port)\n+\t\tsize += nla_total_size(sizeof(u32));\n+\n+\treturn size;\n+}\n+\n+static int port_fill_reply(struct sk_buff *skb,\n+\t\t\t const struct ethnl_req_info *req_info,\n+\t\t\t const struct ethnl_reply_data *reply_data)\n+{\n+\tbool compact = req_info-\u003eflags \u0026 ETHTOOL_FLAG_COMPACT_BITSETS;\n+\tstruct port_reply_data *reply = PORT_REPDATA(reply_data);\n+\tint ret, port_type = ETHTOOL_PORT_TYPE_MDI;\n+\n+\tif (nla_put_u32(skb, ETHTOOL_A_PORT_ID, reply-\u003eport_id))\n+\t\treturn -EMSGSIZE;\n+\n+\tif (!reply-\u003emii) {\n+\t\tret = ethnl_put_bitset(skb, ETHTOOL_A_PORT_SUPPORTED_MODES,\n+\t\t\t\t reply-\u003esupported, NULL,\n+\t\t\t\t __ETHTOOL_LINK_MODE_MASK_NBITS,\n+\t\t\t\t link_mode_names, compact);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t} else {\n+\t\tret = ethnl_put_bitset(skb, ETHTOOL_A_PORT_SUPPORTED_INTERFACES,\n+\t\t\t\t reply-\u003einterfaces, NULL,\n+\t\t\t\t PHY_INTERFACE_MODE_MAX,\n+\t\t\t\t phy_interface_names, compact);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\tif (reply-\u003emii || reply-\u003esfp)\n+\t\tport_type = ETHTOOL_PORT_TYPE_SFP;\n+\n+\tif (nla_put_u32(skb, ETHTOOL_A_PORT_TYPE, port_type))\n+\t\treturn -EMSGSIZE;\n+\n+\tif (reply-\u003eupstream_port \u0026\u0026\n+\t nla_put_u32(skb, ETHTOOL_A_PORT_UPSTREAM_PORT,\n+\t\t\treply-\u003eupstream_port))\n+\t\treturn -EMSGSIZE;\n+\n+\treturn 0;\n+}\n+\n+struct port_dump_ctx {\n+\tstruct port_req_info\t*req_info;\n+\tstruct port_reply_data\t*reply_data;\n+\tunsigned long\t\tifindex;\n+\tunsigned long\t\tpos_ifindex;\n+\tunsigned long\t\tpos_portid;\n+};\n+\n+static struct port_dump_ctx *\n+port_dump_ctx_get(struct netlink_callback *cb)\n+{\n+\treturn (struct port_dump_ctx *)cb-\u003ectx;\n+}\n+\n+int ethnl_port_dump_start(struct netlink_callback *cb)\n+{\n+\tconst struct genl_dumpit_info *info = genl_dumpit_info(cb);\n+\tstruct port_dump_ctx *ctx = port_dump_ctx_get(cb);\n+\tstruct nlattr **tb = info-\u003einfo.attrs;\n+\tstruct port_reply_data *reply_data;\n+\tstruct port_req_info *req_info;\n+\tint ret;\n+\n+\tBUILD_BUG_ON(sizeof(*ctx) \u003e sizeof(cb-\u003ectx));\n+\n+\treq_info = kzalloc_obj(*req_info);\n+\tif (!req_info)\n+\t\treturn -ENOMEM;\n+\n+\treply_data = kmalloc_obj(*reply_data);\n+\tif (!reply_data) {\n+\t\tret = -ENOMEM;\n+\t\tgoto free_req_info;\n+\t}\n+\n+\tret = ethnl_parse_header_dev_get(\u0026req_info-\u003ebase, tb[ETHTOOL_A_PORT_HEADER],\n+\t\t\t\t\t genl_info_net(\u0026info-\u003einfo),\n+\t\t\t\t\t info-\u003einfo.extack, false);\n+\tif (ret \u003c 0)\n+\t\tgoto free_rep_data;\n+\n+\tctx-\u003eifindex = 0;\n+\tctx-\u003epos_ifindex = 0;\n+\n+\t/* For filtered DUMP requests, let's just store the ifindex. We'll check\n+\t * again if the netdev is still there when looping over the netdev list\n+\t * in the DUMP loop.\n+\t */\n+\tif (req_info-\u003ebase.dev) {\n+\t\tctx-\u003eifindex = req_info-\u003ebase.dev-\u003eifindex;\n+\t\tnetdev_put(req_info-\u003ebase.dev, \u0026req_info-\u003ebase.dev_tracker);\n+\t\treq_info-\u003ebase.dev = NULL;\n+\t}\n+\n+\tctx-\u003ereq_info = req_info;\n+\tctx-\u003ereply_data = reply_data;\n+\n+\treturn 0;\n+\n+free_rep_data:\n+\tkfree(reply_data);\n+free_req_info:\n+\tkfree(req_info);\n+\n+\treturn ret;\n+}\n+\n+static int port_dump_one(struct sk_buff *skb, struct net_device *dev,\n+\t\t\t struct netlink_callback *cb)\n+{\n+\tstruct port_dump_ctx *ctx = port_dump_ctx_get(cb);\n+\tvoid *ehdr;\n+\tint ret;\n+\n+\tehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_PORT_GET_REPLY);\n+\tif (!ehdr)\n+\t\treturn -EMSGSIZE;\n+\n+\tmemset(ctx-\u003ereply_data, 0, sizeof(struct port_reply_data));\n+\tctx-\u003ereply_data-\u003ebase.dev = dev;\n+\n+\trtnl_lock();\n+\tnetdev_lock_ops(dev);\n+\n+\tret = port_prepare_data(\u0026ctx-\u003ereq_info-\u003ebase, \u0026ctx-\u003ereply_data-\u003ebase,\n+\t\t\t\tgenl_info_dump(cb));\n+\n+\tnetdev_unlock_ops(dev);\n+\trtnl_unlock();\n+\n+\tif (ret \u003c 0)\n+\t\tgoto out;\n+\n+\tret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_PORT_HEADER);\n+\tif (ret \u003c 0)\n+\t\tgoto out;\n+\n+\tret = port_fill_reply(skb, \u0026ctx-\u003ereq_info-\u003ebase, \u0026ctx-\u003ereply_data-\u003ebase);\n+\n+out:\n+\tctx-\u003ereply_data-\u003ebase.dev = NULL;\n+\tif (ret \u003c 0)\n+\t\tgenlmsg_cancel(skb, ehdr);\n+\telse\n+\t\tgenlmsg_end(skb, ehdr);\n+\n+\treturn ret;\n+}\n+\n+static int port_dump_one_dev(struct sk_buff *skb, struct netlink_callback *cb)\n+{\n+\tstruct port_dump_ctx *ctx = port_dump_ctx_get(cb);\n+\tstruct net_device *dev;\n+\tstruct phy_port *port;\n+\tint ret;\n+\n+\tdev = ctx-\u003ereq_info-\u003ebase.dev;\n+\n+\tif (!dev-\u003elink_topo)\n+\t\treturn 0;\n+\n+\txa_for_each_start(\u0026dev-\u003elink_topo-\u003eports, ctx-\u003epos_portid, port,\n+\t\t\t ctx-\u003epos_portid) {\n+\t\tctx-\u003ereq_info-\u003eport_id = ctx-\u003epos_portid;\n+\n+\t\tret = port_dump_one(skb, dev, cb);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\tctx-\u003epos_portid = 0;\n+\n+\treturn 0;\n+}\n+\n+static int port_dump_all_dev(struct sk_buff *skb, struct netlink_callback *cb)\n+{\n+\tstruct port_dump_ctx *ctx = port_dump_ctx_get(cb);\n+\tstruct net *net = sock_net(skb-\u003esk);\n+\tnetdevice_tracker dev_tracker;\n+\tstruct net_device *dev;\n+\tint ret = 0;\n+\n+\trcu_read_lock();\n+\tfor_each_netdev_dump(net, dev, ctx-\u003epos_ifindex) {\n+\t\tnetdev_hold(dev, \u0026dev_tracker, GFP_ATOMIC);\n+\t\trcu_read_unlock();\n+\n+\t\tctx-\u003ereq_info-\u003ebase.dev = dev;\n+\t\tret = port_dump_one_dev(skb, cb);\n+\n+\t\trcu_read_lock();\n+\t\tnetdev_put(dev, \u0026dev_tracker);\n+\t\tctx-\u003ereq_info-\u003ebase.dev = NULL;\n+\n+\t\tif (ret)\n+\t\t\tbreak;\n+\n+\t\tret = 0;\n+\t}\n+\trcu_read_unlock();\n+\n+\treturn ret;\n+}\n+\n+int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb)\n+{\n+\tconst struct genl_dumpit_info *info = genl_dumpit_info(cb);\n+\tstruct port_dump_ctx *ctx = port_dump_ctx_get(cb);\n+\tint ret = 0;\n+\n+\tif (ctx-\u003eifindex) {\n+\t\tnetdevice_tracker dev_tracker;\n+\t\tstruct net_device *dev;\n+\n+\t\tdev = netdev_get_by_index(genl_info_net(\u0026info-\u003einfo),\n+\t\t\t\t\t ctx-\u003eifindex, \u0026dev_tracker,\n+\t\t\t\t\t GFP_KERNEL);\n+\t\tif (!dev)\n+\t\t\treturn -ENODEV;\n+\n+\t\tctx-\u003ereq_info-\u003ebase.dev = dev;\n+\t\tret = port_dump_one_dev(skb, cb);\n+\n+\t\tnetdev_put(dev, \u0026dev_tracker);\n+\t} else {\n+\t\tret = port_dump_all_dev(skb, cb);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+int ethnl_port_dump_done(struct netlink_callback *cb)\n+{\n+\tstruct port_dump_ctx *ctx = port_dump_ctx_get(cb);\n+\n+\tkfree(ctx-\u003ereq_info);\n+\tkfree(ctx-\u003ereply_data);\n+\n+\treturn 0;\n+}\n+\n+const struct ethnl_request_ops ethnl_port_request_ops = {\n+\t.request_cmd\t\t= ETHTOOL_MSG_PORT_GET,\n+\t.reply_cmd\t\t= ETHTOOL_MSG_PORT_GET_REPLY,\n+\t.hdr_attr\t\t= ETHTOOL_A_PORT_HEADER,\n+\t.req_info_size\t\t= sizeof(struct port_req_info),\n+\t.reply_data_size\t= sizeof(struct port_reply_data),\n+\n+\t.parse_request\t\t= port_parse_request,\n+\t.prepare_data\t\t= port_prepare_data,\n+\t.reply_size\t\t= port_reply_size,\n+\t.fill_reply\t\t= port_fill_reply,\n+};\n"
} |
| 2/1 | 2026/08/02 10:29 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"ETHTOOL_NETLINK",
"PHYLIB",
"PHYLINK",
"SFP",
"I2C"
],
"FocusSymbols": [
"ethnl_port_dump_start",
"ethnl_port_dumpit",
"ethnl_port_dump_done",
"phy_link_topo_add_port",
"phy_link_topo_del_port",
"phy_caps_linkmode_filter_ifaces",
"phy_set_upstream_port"
],
"Reasoning": "The patch introduces a new ethtool netlink command `PORT_GET` to retrieve physical port information, and adds SFP port support to the PHY framework. This is a functional change that exposes new netlink APIs and modifies core PHY/phylink logic, which can be reached from userspace via ethtool and should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 0abd5398c52ca9886061eb30a81202002ae62b4a
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Aug 2 10:29:17 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 5dd4d1b5d94bb..67ac3400462bf 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -210,6 +210,11 @@ definitions:
-
name: discard
value: 31
+ -
+ name: port-type
+ doc: type of physical connector
+ type: enum
+ entries: [mdi, sfp]
attribute-sets:
-
@@ -1905,6 +1910,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
@@ -2859,6 +2890,29 @@ 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
+ - upstream-port
+ dump:
+ request:
+ attributes:
+ - header
mcast-groups:
list:
diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index e92abf45faf50..b4326c89b0751 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -2537,6 +2537,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 +2680,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 6e28d9094bce3..2a9b1ec93b70f 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 <phy_port>` instance :
+
+ - The SFP cage itself is a :c:type:`struct phy_port <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 <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 <phy_port>`, that represents the various linkmodes
+ that it gives access to. The module's :c:type:`struct phy_port <phy_port>`
+ doesn't supersede the cage's port, it references it through
+ the :c:type:`struct phy_port <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 932ea1db048e5..395f8b5d18495 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18816,6 +18816,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 421088e6f6e8f..ec3d39a0ae06b 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 942d43191561e..558e4df4d63c0 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 94b2e85e00a37..3da6872ebbba0 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1490,11 +1490,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;
}
/**
@@ -1512,8 +1523,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);
}
/**
@@ -1617,6 +1632,77 @@ 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_cage_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;
+
+ 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,
@@ -1626,6 +1712,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)
@@ -1673,13 +1761,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;
@@ -1694,10 +1782,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;
}
/**
@@ -1706,25 +1796,50 @@ 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;
- 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 (bus) {
+ port = phy_setup_sfp_port(phydev);
+ if (IS_ERR(port)) {
+ ret = PTR_ERR(port);
+ port = NULL;
+ goto out_sfp;
+ }
}
- if (!ret && phydev->sfp_bus)
- ret = phy_setup_sfp_port(phydev);
+ phydev->sfp_cage_port = port;
+
+ ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops);
+ if (ret)
+ goto out_port;
+
+ /* 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 ret;
+
+out_port:
+ if (port) {
+ phy_del_port(phydev, port);
+ phy_port_destroy(port);
+ phydev->sfp_cage_port = NULL;
+ }
+out_sfp:
+ sfp_bus_put(bus);
+ phydev->sfp_bus = NULL;
return ret;
}
@@ -1815,6 +1930,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
@@ -1951,6 +2072,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;
@@ -3604,6 +3727,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
@@ -3842,6 +3984,7 @@ static int phy_remove(struct device *dev)
sfp_bus_del_upstream(phydev->sfp_bus);
phydev->sfp_bus = NULL;
+ phydev->sfp_cage_port = NULL;
phy_cleanup_ports(phydev);
diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c
index 4134de7ae313d..6710a199967a1 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>
@@ -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 of 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(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;
+}
+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 f40acc0d41338..a5f025187afa2 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,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;
@@ -1764,6 +1768,51 @@ 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);
+
+ ret = phy_link_topo_add_port(pl->netdev, port);
+ 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)
+ phy_link_topo_del_port(pl->netdev, pl->sfp_cage_port);
+
+ 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)
{
@@ -1781,9 +1830,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;
}
@@ -1946,6 +2004,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);
@@ -3871,14 +3930,67 @@ 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_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_caps_linkmode_filter_ifaces(port->supported, caps->link_modes,
+ pl->sfp_cage_port->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
@@ -3887,7 +3999,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)
@@ -3897,6 +4018,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)
@@ -3941,6 +4064,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);
}
@@ -3949,6 +4074,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 11092c3175b3d..741ca7a0a2573 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,9 @@ 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_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.
@@ -706,6 +710,7 @@ struct phy_device {
unsigned irq_rerun:1;
unsigned default_timestamp:1;
+ unsigned has_sfp_mod_phy:1;
int rate_matching;
@@ -785,6 +790,9 @@ struct phy_device {
/* This may be modified under the rtnl lock */
bool sfp_bus_attached;
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;
@@ -2468,6 +2476,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 95575f68d5bc7..9585282cae3fc 100644
--- a/include/linux/phy_link_topology.h
+++ b/include/linux/phy_link_topology.h
@@ -13,14 +13,19 @@
#include <linux/ethtool.h>
#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
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,25 @@ 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();
+
+ /* 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,
@@ -77,11 +104,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 0ef0f5ce47092..8f45c031cbc10 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_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h
index 8134baf7860f3..f9d8794eabc19 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,
@@ -840,6 +845,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 +910,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 +972,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 c1c1be1a6962f..b36cc5c47816a 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -11315,6 +11315,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 629c10916670e..9b5b096700081 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 1af395b54330e..c076c07d0a080 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -26,6 +26,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,
@@ -431,6 +433,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)
@@ -1572,6 +1575,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[] = {
@@ -1594,10 +1606,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 3e969a070f9fe..dc7211f67c4d5 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];
+
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 0000000000000..e1bcadbfe40c0
--- /dev/null
+++ b/net/ethtool/port.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2026 Bootlin
+ */
+#include <linux/phy.h>
+#include <linux/phy_link_topology.h>
+#include <linux/phy_port.h>
+#include <net/netdev_lock.h>
+
+#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 = 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 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;
+ }
+
+ 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));
+
+ 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;
+
+ 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->pos_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)
+ 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);
+
+ 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,
+};
|
| 3/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20723 output: 29 thoughts: 1167 Thoughts: **Analyzing the New ethtool PORT_GET Netlink API and SFP Support** Okay, so we have a new patch series here. It seems like we're introducing a `PORT_GET` netlink API command in ethtool to fetch information about the physical connection points of a network device, or "ports". We're also adding support for SFP ports within the PHY framework. Let's break this down from a security and functional perspective. First, the API itself. We have the usual documentation updates: `ethtool.yaml` to define the new enum and attributes, `ethtool-netlink.rst` and `phy-port.rst` to describe the new functionality and SFP ports, respectively. The PHY framework sees a lot of changes, with new functions in `phy_caps.c`, `phy_device.c`, `phy_link_topology.c`, and `phylink.c` related to SFP modules and port management, as well as updates to several header files. The core of the implementation will be in `net/ethtool/port.c`, which defines the netlink command `ETHTOOL_MSG_PORT_GET` in the user-space API. From a fuzzing perspective, this `PORT_GET` command is the main target. We're introducing a new netlink command, which means a new attack surface. I need to focus on these functions in `net/ethtool/port.c`: `port_parse_request`, `port_prepare_data`, `port_reply_size`, `port_fill_reply`, `ethnl_port_dump_start`, `ethnl_port_dumpit`, `ethnl_port_dump_done`, `port_dump_one`, `port_dump_one_dev`, and `port_dump_all_dev`. We'll need to see if any of these functions are static or exported. Let's check `net/ethtool/port.c`. Okay, `ethnl_port_dump_start`, `ethnl_port_dumpit`, and `ethnl_port_dump_done` are exported, which is good for direct fuzzing access. `port_parse_request`, `port_prepare_data`, `port_reply_size`, and `port_fill_reply` are static, but used within `ethnl_port_request_ops`, which *is* exported and then used by `ethnl_default_requests` in `net/ethtool/netlink.c`. So, `ethnl_port_dumpit`, `ethnl_port_dump_start`, and `ethnl_port_dump_done` are definitely good targets, and we'll need to figure out how to trigger the functions within `ethnl_port_request_ops`, which are called via function pointers. Now, are there any other entry points to consider? Let's see... we have `phy_link_topo_add_port` and `phy_link_topo_del_port` in `phy_link_topology.c`, `phy_caps_linkmode_filter_ifaces` in `phy_caps.c`, and `phy_set_upstream_port` in `phy_device.c`. These may require additional setup or trigger from events within the PHY subsystem, but are good targets to consider for completeness. In terms of build configuration, `CONFIG_ETHTOOL_NETLINK`, `CONFIG_PHYLIB`, and `CONFIG_PHYLINK` are the essentials. I expect `CONFIG_ETHTOOL_NETLINK` to be the key configuration to ensure this code is built. Let's see how `net/ethtool/Makefile` handles things... Yeah, it unconditionally builds `port.o` if the ethtool netlink code is built, so that's straightforward. Now I want to check for potential configuration dependencies or restrictions, I will search for `CONFIG_ETHTOOL_NETLINK` in `net/Kconfig` or `net/ethtool/Kconfig`. |
| 4/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "config ETHTOOL_NETLINK",
"PathPrefix": "net/"
}Results: {
"Output": "net/Kconfig=507=config FAILOVER\n--\nnet/Kconfig-519-\nnet/Kconfig:520:config ETHTOOL_NETLINK\nnet/Kconfig-521-\tbool \"Netlink interface for ethtool\"\n"
} |
| 5/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22873 output: 32 thoughts: 19 Thoughts: Here's my thought process, as requested: **Checking for ETHTOOL_NETLINK in net/Kconfig** Okay, so I need to figure out how `ETHTOOL_NETLINK` is handled in this configuration. The logical place to start is the `net/Kconfig` file. It's the standard location for network-related configuration options within the kernel. I'll need to look through its contents to see if `ETHTOOL_NETLINK` is defined or referenced anywhere. This will give me a better understanding of its dependencies, the way it's exposed to the user, and if it has any defaults or relationships with other features. Understanding this will be key to understanding the network configuration as a whole. |
| 6/2 | 2026/08/02 10:29 | tool | read-file |
0mArgs:{
"File": "net/Kconfig",
"FirstLine": 515,
"LineCount": 15
}Results: {
"Contents": " 515:\t\t failover netdev. This enables paravirtual drivers to use a\n 516:\t\t VF as an accelerated low latency datapath. It also allows live\n 517:\t\t migration of VMs with direct attached VFs by failing over to the\n 518:\t\t paravirtual datapath when the VF is unplugged.\n 519:\t\n 520:\tconfig ETHTOOL_NETLINK\n 521:\t\tbool \"Netlink interface for ethtool\"\n 522:\t\tselect DIMLIB\n 523:\t\tdefault y\n 524:\t\thelp\n 525:\t\t An alternative userspace interface for ethtool based on generic\n 526:\t\t netlink. It provides better extensibility and some new features,\n 527:\t\t e.g. notification messages.\n 528:\t\n 529:\tconfig NETDEV_ADDR_LIST_TEST\n"
} |
| 7/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23320 output: 29 thoughts: 17 Thoughts: Okay, here's my summarized thought process, presented as if I were having these thoughts: **Analyzing the Configuration** Alright, I'm looking at this...and it seems the system's behaving according to its default 'y' setting. This isn't unexpected, but it doesn't give me much context yet. Now, I need to dig deeper. What's the context? Let's see... what about the influence of `CONFIG_PHYLIB`? Is that active, and how might that be impacting this default behavior I'm observing? That's the key question now. I need to understand its relationship to the broader configuration I'm seeing. |
| 8/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "config PHYLIB",
"PathPrefix": "drivers/net/phy/"
}Results: {
"Output": "drivers/net/phy/Kconfig=6=config PHYLINK\n--\ndrivers/net/phy/Kconfig-14-\ndrivers/net/phy/Kconfig:15:menuconfig PHYLIB\ndrivers/net/phy/Kconfig-16-\ttristate \"PHY Device support and infrastructure\"\n--\ndrivers/net/phy/Kconfig=46=config OPEN_ALLIANCE_HELPERS\n--\ndrivers/net/phy/Kconfig-48-\ndrivers/net/phy/Kconfig:49:config PHYLIB_LEDS\ndrivers/net/phy/Kconfig-50-\tdef_bool OF\n"
} |
| 9/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23668 output: 70 thoughts: 230 Thoughts: **Analyzing Ethernet Port Dump Functions and PHYLIB Integration** Okay, so `PHYLIB` is also something to keep in mind, right? Let's zero in on the key functions we're looking at. I need to understand how these interact. I'm focusing on: * `ethnl_port_dump_start` * `ethnl_port_dumpit` * `ethnl_port_dump_done` * `phy_link_topo_add_port` * `phy_link_topo_del_port` * `phy_caps_linkmode_filter_ifaces` * `phy_set_upstream_port` Right, now the important thing is whether these are exported functions or just non-static within their respective files. Let me see... Aha, they're non-static. They are used within `ethtool_genl_ops` in `net/ethtool/netlink.c`. Hang on... `ethnl_port_dump_start` itself is actually in `net/ethtool/port.c`, but it's *declared* in `net/ethtool/netlink.h`. Yes, okay, they are indeed non-static and visible to the netlink interface. Now, let's take a quick look inside `net/ethtool/port.c` to see if there are any other non-static functions within that file that might be relevant to the bigger picture. |
| 10/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_][a-zA-Z0-9_]*\\s+\\**[a-zA-Z_][a-zA-Z0-9_]*\\(",
"PathPrefix": "net/ethtool/port.c"
}Results: {
"Output": "net/ethtool/port.c=171=port_dump_ctx_get(struct netlink_callback *cb)\n--\nnet/ethtool/port.c-175-\nnet/ethtool/port.c:176:int ethnl_port_dump_start(struct netlink_callback *cb)\nnet/ethtool/port.c-177-{\n--\nnet/ethtool/port.c=297=static int port_dump_all_dev(struct sk_buff *skb, struct netlink_callback *cb)\n--\nnet/ethtool/port.c-326-\nnet/ethtool/port.c:327:int ethnl_port_dumpit(struct sk_buff *skb, struct netlink_callback *cb)\nnet/ethtool/port.c-328-{\n--\nnet/ethtool/port.c-353-\nnet/ethtool/port.c:354:int ethnl_port_dump_done(struct netlink_callback *cb)\nnet/ethtool/port.c-355-{\n"
} |
| 11/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24583 output: 74 thoughts: 50 Thoughts: Okay, here we go. I'm seeing a sequence – `ethnl_port_dump_start`, then `ethnl_port_dumpit`, and finally `ethnl_port_dump_done`. That's a pretty clear indication of a dump process related to Ethernet port information, likely within the Netlink subsystem. I need to understand this whole cycle. My next step is to consider the broader context. Hmm, what about `drivers/net/phy/phy_link_topology.c`? That file's relevant, and I need to consider it within the scope of this dump process. It looks like the PHY layer link topology is something that is going to need to be considered. |
| 12/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_][a-zA-Z0-9_]*\\s+\\**[a-zA-Z_][a-zA-Z0-9_]*\\(",
"PathPrefix": "drivers/net/phy/phy_link_topology.c"
}Results: {
"Output": "drivers/net/phy/phy_link_topology.c=35=static struct phy_link_topology *phy_link_topo_get_or_alloc(struct net_device *dev)\n--\ndrivers/net/phy/phy_link_topology.c-62-\ndrivers/net/phy/phy_link_topology.c:63:int phy_link_topo_add_port(struct net_device *dev, struct phy_port *port)\ndrivers/net/phy/phy_link_topology.c-64-{\n--\ndrivers/net/phy/phy_link_topology.c=90=EXPORT_SYMBOL_GPL(phy_link_topo_add_port);\ndrivers/net/phy/phy_link_topology.c-91-\ndrivers/net/phy/phy_link_topology.c:92:void phy_link_topo_del_port(struct net_device *dev, struct phy_port *port)\ndrivers/net/phy/phy_link_topology.c-93-{\n--\ndrivers/net/phy/phy_link_topology.c=101=EXPORT_SYMBOL_GPL(phy_link_topo_del_port);\ndrivers/net/phy/phy_link_topology.c-102-\ndrivers/net/phy/phy_link_topology.c:103:int phy_link_topo_add_phy(struct net_device *dev,\ndrivers/net/phy/phy_link_topology.c-104-\t\t\t struct phy_device *phy,\n--\ndrivers/net/phy/phy_link_topology.c=176=EXPORT_SYMBOL_GPL(phy_link_topo_add_phy);\ndrivers/net/phy/phy_link_topology.c-177-\ndrivers/net/phy/phy_link_topology.c:178:void phy_link_topo_del_phy(struct net_device *dev,\ndrivers/net/phy/phy_link_topology.c-179-\t\t\t struct phy_device *phy)\n"
} |
| 13/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25353 output: 72 thoughts: 43 Thoughts: Here's my take on the thought process, as requested: **Assessing PHY Link Topology Management** Okay, so I see `phy_link_topo_add_port` and `phy_link_topo_del_port` are explicitly exported. That's good. They're clearly intended for external use, probably by some upper-level network stack or a system manager. My initial thought is about how these functions relate to the larger picture of PHY management. Now, my mind shifts slightly. Where are the core capabilities defined? I need to quickly establish if there is any interaction with these new functions in my areas of expertise. I immediately think of `drivers/net/phy/phy_caps.c`. It's a key file where PHY capabilities and their configurations are usually defined. I need to understand if these new topology functions have an effect here, and how, if so. A quick review of that file is in order. |
| 14/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_][a-zA-Z0-9_]*\\s+\\**[a-zA-Z_][a-zA-Z0-9_]*\\(",
"PathPrefix": "drivers/net/phy/phy_caps.c"
}Results: {
"Output": "drivers/net/phy/phy_caps.c=77=int __init phy_caps_init(void)\n--\ndrivers/net/phy/phy_caps.c-121- */\ndrivers/net/phy/phy_caps.c:122:size_t phy_caps_speeds(unsigned int *speeds, size_t size,\ndrivers/net/phy/phy_caps.c-123-\t\t unsigned long *linkmodes)\n--\ndrivers/net/phy/phy_caps.c=236=EXPORT_SYMBOL_GPL(phy_caps_lookup);\n--\ndrivers/net/phy/phy_caps.c-242- */\ndrivers/net/phy/phy_caps.c:243:void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes)\ndrivers/net/phy/phy_caps.c-244-{\n--\ndrivers/net/phy/phy_caps.c-262- */\ndrivers/net/phy/phy_caps.c:263:bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes)\ndrivers/net/phy/phy_caps.c-264-{\n--\ndrivers/net/phy/phy_caps.c-277- */\ndrivers/net/phy/phy_caps.c:278:void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes)\ndrivers/net/phy/phy_caps.c-279-{\n--\ndrivers/net/phy/phy_caps.c=390=EXPORT_SYMBOL_GPL(phy_caps_from_interface);\n--\ndrivers/net/phy/phy_caps.c-401- */\ndrivers/net/phy/phy_caps.c:402:void phy_caps_medium_get_supported(unsigned long *supported,\ndrivers/net/phy/phy_caps.c-403-\t\t\t\t enum ethtool_link_medium medium,\n--\ndrivers/net/phy/phy_caps.c=425=EXPORT_SYMBOL_GPL(phy_caps_medium_get_supported);\n--\ndrivers/net/phy/phy_caps.c-433- */\ndrivers/net/phy/phy_caps.c:434:u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes)\ndrivers/net/phy/phy_caps.c-435-{\n--\ndrivers/net/phy/phy_caps.c=447=EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes);\n--\ndrivers/net/phy/phy_caps.c-457- */\ndrivers/net/phy/phy_caps.c:458:void phy_caps_linkmode_filter_ifaces(unsigned long *to,\ndrivers/net/phy/phy_caps.c-459-\t\t\t\t const unsigned long *from,\n"
} |
| 15/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26388 output: 72 thoughts: 33 Thoughts: Okay, here's my interpretation of those thoughts, presented as a first-person thought process, as if I were considering these points as a network systems expert: **Investigating PHY Capability Filtering** Alright, so `phy_caps_linkmode_filter_ifaces` has been exposed. Hmm, that's interesting. I immediately wonder about the implications of this function being exported. It suggests a more controlled, perhaps fine-grained, approach to link mode filtering at the PHY level. I need to understand its relationship to the broader system. What's the context for it? My next thought is, where does this fit into the bigger picture of the PHY driver infrastructure? I need to consider all the places where PHY capabilities are manipulated. This triggers an association. What about that key area for PHY driver operations? Ah yes, `drivers/net/phy/phy_device.c`. That file is a critical piece, and likely where any interaction with this new function would be defined or used, or where its effects would be seen. This will be the first area I will check. |
| 16/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_][a-zA-Z0-9_]*\\s+\\**[a-zA-Z_][a-zA-Z0-9_]*\\(",
"PathPrefix": "drivers/net/phy/phy_device.c"
}Results: {
"Output": "drivers/net/phy/phy_device.c=145=static void __init features_init(void)\n--\ndrivers/net/phy/phy_device.c-209-\ndrivers/net/phy/phy_device.c:210:void phy_device_free(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-211-{\n--\ndrivers/net/phy/phy_device.c=238=static struct phy_driver genphy_driver;\ndrivers/net/phy/phy_device.c-239-\ndrivers/net/phy/phy_device.c:240:static LIST_HEAD(phy_fixup_list);\ndrivers/net/phy/phy_device.c:241:static DEFINE_MUTEX(phy_fixup_lock);\ndrivers/net/phy/phy_device.c-242-\ndrivers/net/phy/phy_device.c=243=static bool phy_drv_wol_enabled(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-251-\ndrivers/net/phy/phy_device.c:252:bool phy_may_wakeup(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-253-{\n--\ndrivers/net/phy/phy_device.c=378=static __maybe_unused int mdio_bus_phy_resume(struct device *dev)\n--\ndrivers/net/phy/phy_device.c-426-\ndrivers/net/phy/phy_device.c:427:static SIMPLE_DEV_PM_OPS(mdio_bus_phy_pm_ops, mdio_bus_phy_suspend,\ndrivers/net/phy/phy_device.c-428-\t\t\t mdio_bus_phy_resume);\n--\ndrivers/net/phy/phy_device.c=438=static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask,\n--\ndrivers/net/phy/phy_device.c-459-/* Registers a fixup to be run on any PHY with the UID in phy_uid */\ndrivers/net/phy/phy_device.c:460:int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask,\ndrivers/net/phy/phy_device.c-461-\t\t\t int (*run)(struct phy_device *))\n--\ndrivers/net/phy/phy_device.c=465=EXPORT_SYMBOL(phy_register_fixup_for_uid);\n--\ndrivers/net/phy/phy_device.c-467-/* Registers a fixup to be run on the PHY with id string bus_id */\ndrivers/net/phy/phy_device.c:468:int phy_register_fixup_for_id(const char *bus_id,\ndrivers/net/phy/phy_device.c-469-\t\t\t int (*run)(struct phy_device *))\n--\ndrivers/net/phy/phy_device.c=488=static int phy_scan_fixups(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-521- */\ndrivers/net/phy/phy_device.c:522:int genphy_match_phy_device(struct phy_device *phydev,\ndrivers/net/phy/phy_device.c-523-\t\t\t const struct phy_driver *phydrv)\n--\ndrivers/net/phy/phy_device.c=561=phy_id_show(struct device *dev, struct device_attribute *attr, char *buf)\n--\ndrivers/net/phy/phy_device.c-566-}\ndrivers/net/phy/phy_device.c:567:static DEVICE_ATTR_RO(phy_id);\ndrivers/net/phy/phy_device.c-568-\n--\ndrivers/net/phy/phy_device.c=570=phy_interface_show(struct device *dev, struct device_attribute *attr, char *buf)\n--\ndrivers/net/phy/phy_device.c-581-}\ndrivers/net/phy/phy_device.c:582:static DEVICE_ATTR_RO(phy_interface);\ndrivers/net/phy/phy_device.c-583-\n--\ndrivers/net/phy/phy_device.c=585=phy_has_fixups_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/net/phy/phy_device.c-591-}\ndrivers/net/phy/phy_device.c:592:static DEVICE_ATTR_RO(phy_has_fixups);\ndrivers/net/phy/phy_device.c-593-\ndrivers/net/phy/phy_device.c=594=static ssize_t phy_dev_flags_show(struct device *dev,\n--\ndrivers/net/phy/phy_device.c-601-}\ndrivers/net/phy/phy_device.c:602:static DEVICE_ATTR_RO(phy_dev_flags);\ndrivers/net/phy/phy_device.c-603-\n--\ndrivers/net/phy/phy_device.c=617=static ssize_t mmd##n##_device_id_show(struct device *dev, \\\n--\ndrivers/net/phy/phy_device.c-623-} \\\ndrivers/net/phy/phy_device.c:624:static DEVICE_ATTR_RO(mmd##n##_device_id)\ndrivers/net/phy/phy_device.c-625-\n--\ndrivers/net/phy/phy_device.c=1002=static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id)\n--\ndrivers/net/phy/phy_device.c-1033- */\ndrivers/net/phy/phy_device.c:1034:int fwnode_get_phy_id(struct fwnode_handle *fwnode, u32 *phy_id)\ndrivers/net/phy/phy_device.c-1035-{\n--\ndrivers/net/phy/phy_device.c=1103=EXPORT_SYMBOL(get_phy_device);\n--\ndrivers/net/phy/phy_device.c-1108- */\ndrivers/net/phy/phy_device.c:1109:int phy_device_register(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1110-{\n--\ndrivers/net/phy/phy_device.c=1142=EXPORT_SYMBOL(phy_device_register);\n--\ndrivers/net/phy/phy_device.c-1151- */\ndrivers/net/phy/phy_device.c:1152:void phy_device_remove(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1153-{\n--\ndrivers/net/phy/phy_device.c=1164=EXPORT_SYMBOL(phy_device_remove);\n--\ndrivers/net/phy/phy_device.c-1172- */\ndrivers/net/phy/phy_device.c:1173:int phy_get_c45_ids(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1174-{\n--\ndrivers/net/phy/phy_device.c=1212=static void phy_prepare_link(struct phy_device *phydev,\n--\ndrivers/net/phy/phy_device.c-1224- */\ndrivers/net/phy/phy_device.c:1225:int phy_connect_direct(struct net_device *dev, struct phy_device *phydev,\ndrivers/net/phy/phy_device.c-1226-\t\t void (*handler)(struct net_device *),\n--\ndrivers/net/phy/phy_device.c=1286=EXPORT_SYMBOL(phy_connect);\n--\ndrivers/net/phy/phy_device.c-1292- */\ndrivers/net/phy/phy_device.c:1293:void phy_disconnect(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1294-{\n--\ndrivers/net/phy/phy_device.c=1325=static int phy_poll_reset(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-1340-\ndrivers/net/phy/phy_device.c:1341:int phy_init_hw(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1342-{\n--\ndrivers/net/phy/phy_device.c=1388=EXPORT_SYMBOL(phy_init_hw);\ndrivers/net/phy/phy_device.c-1389-\ndrivers/net/phy/phy_device.c:1390:void phy_attached_info(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1391-{\n--\ndrivers/net/phy/phy_device.c=1394=EXPORT_SYMBOL(phy_attached_info);\n--\ndrivers/net/phy/phy_device.c-1396-#define ATTACHED_FMT \"attached PHY driver %s(mii_bus:phy_addr=%s, irq=%s)\"\ndrivers/net/phy/phy_device.c:1397:char *phy_attached_info_irq(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-1398-{\n--\ndrivers/net/phy/phy_device.c=1417=EXPORT_SYMBOL(phy_attached_info_irq);\ndrivers/net/phy/phy_device.c-1418-\ndrivers/net/phy/phy_device.c:1419:void phy_attached_print(struct phy_device *phydev, const char *fmt, ...)\ndrivers/net/phy/phy_device.c-1420-{\n--\ndrivers/net/phy/phy_device.c=1470=phy_standalone_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/net/phy/phy_device.c-1476-}\ndrivers/net/phy/phy_device.c:1477:static DEVICE_ATTR_RO(phy_standalone);\ndrivers/net/phy/phy_device.c-1478-\n--\ndrivers/net/phy/phy_device.c=1847=static bool phy_drv_supports_irq(const struct phy_driver *phydrv)\n--\ndrivers/net/phy/phy_device.c-1866- */\ndrivers/net/phy/phy_device.c:1867:int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,\ndrivers/net/phy/phy_device.c-1868-\t\t u32 flags, phy_interface_t interface)\n--\ndrivers/net/phy/phy_device.c=2028=EXPORT_SYMBOL(phy_attach_direct);\n--\ndrivers/net/phy/phy_device.c-2036- */\ndrivers/net/phy/phy_device.c:2037:void phy_detach(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2038-{\n--\ndrivers/net/phy/phy_device.c=2110=EXPORT_SYMBOL(phy_detach);\ndrivers/net/phy/phy_device.c-2111-\ndrivers/net/phy/phy_device.c:2112:int phy_suspend(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2113-{\n--\ndrivers/net/phy/phy_device.c=2136=EXPORT_SYMBOL(phy_suspend);\ndrivers/net/phy/phy_device.c-2137-\ndrivers/net/phy/phy_device.c:2138:int __phy_resume(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2139-{\n--\ndrivers/net/phy/phy_device.c=2154=EXPORT_SYMBOL(__phy_resume);\ndrivers/net/phy/phy_device.c-2155-\ndrivers/net/phy/phy_device.c:2156:int phy_resume(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2157-{\n--\ndrivers/net/phy/phy_device.c=2166=EXPORT_SYMBOL(phy_resume);\n--\ndrivers/net/phy/phy_device.c-2176- */\ndrivers/net/phy/phy_device.c:2177:int phy_reset_after_clk_enable(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2178-{\n--\ndrivers/net/phy/phy_device.c=2256=static int genphy_c37_config_advert(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-2287- */\ndrivers/net/phy/phy_device.c:2288:int genphy_setup_forced(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2289-{\n--\ndrivers/net/phy/phy_device.c=2302=static int genphy_setup_master_slave(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-2333-\ndrivers/net/phy/phy_device.c:2334:int genphy_read_master_slave(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2335-{\n--\ndrivers/net/phy/phy_device.c=2379=EXPORT_SYMBOL(genphy_read_master_slave);\n--\ndrivers/net/phy/phy_device.c-2384- */\ndrivers/net/phy/phy_device.c:2385:int genphy_restart_aneg(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2386-{\n--\ndrivers/net/phy/phy_device.c=2391=EXPORT_SYMBOL(genphy_restart_aneg);\n--\ndrivers/net/phy/phy_device.c-2399- */\ndrivers/net/phy/phy_device.c:2400:int genphy_check_and_restart_aneg(struct phy_device *phydev, bool restart)\ndrivers/net/phy/phy_device.c-2401-{\n--\ndrivers/net/phy/phy_device.c=2421=EXPORT_SYMBOL(genphy_check_and_restart_aneg);\n--\ndrivers/net/phy/phy_device.c-2431- */\ndrivers/net/phy/phy_device.c:2432:int __genphy_config_aneg(struct phy_device *phydev, bool changed)\ndrivers/net/phy/phy_device.c-2433-{\n--\ndrivers/net/phy/phy_device.c=2478=EXPORT_SYMBOL(__genphy_config_aneg);\n--\ndrivers/net/phy/phy_device.c-2488- */\ndrivers/net/phy/phy_device.c:2489:int genphy_c37_config_aneg(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2490-{\n--\ndrivers/net/phy/phy_device.c=2526=EXPORT_SYMBOL(genphy_c37_config_aneg);\n--\ndrivers/net/phy/phy_device.c-2535- */\ndrivers/net/phy/phy_device.c:2536:int genphy_aneg_done(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2537-{\n--\ndrivers/net/phy/phy_device.c=2542=EXPORT_SYMBOL(genphy_aneg_done);\n--\ndrivers/net/phy/phy_device.c-2551- */\ndrivers/net/phy/phy_device.c:2552:int genphy_update_link(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2553-{\n--\ndrivers/net/phy/phy_device.c=2595=EXPORT_SYMBOL(genphy_update_link);\ndrivers/net/phy/phy_device.c-2596-\ndrivers/net/phy/phy_device.c:2597:int genphy_read_lpa(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2598-{\n--\ndrivers/net/phy/phy_device.c=2642=EXPORT_SYMBOL(genphy_read_lpa);\n--\ndrivers/net/phy/phy_device.c-2650- */\ndrivers/net/phy/phy_device.c:2651:int genphy_read_status_fixed(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2652-{\n--\ndrivers/net/phy/phy_device.c=2672=EXPORT_SYMBOL(genphy_read_status_fixed);\n--\ndrivers/net/phy/phy_device.c-2682- */\ndrivers/net/phy/phy_device.c:2683:int genphy_read_status(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2684-{\n--\ndrivers/net/phy/phy_device.c=2723=EXPORT_SYMBOL(genphy_read_status);\n--\ndrivers/net/phy/phy_device.c-2735- */\ndrivers/net/phy/phy_device.c:2736:int genphy_c37_read_status(struct phy_device *phydev, bool *changed)\ndrivers/net/phy/phy_device.c-2737-{\n--\ndrivers/net/phy/phy_device.c=2787=EXPORT_SYMBOL(genphy_c37_read_status);\n--\ndrivers/net/phy/phy_device.c-2797- */\ndrivers/net/phy/phy_device.c:2798:int genphy_soft_reset(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2799-{\n--\ndrivers/net/phy/phy_device.c=2826=EXPORT_SYMBOL(genphy_soft_reset);\ndrivers/net/phy/phy_device.c-2827-\ndrivers/net/phy/phy_device.c:2828:irqreturn_t genphy_handle_interrupt_no_ack(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2829-{\n--\ndrivers/net/phy/phy_device.c=2839=EXPORT_SYMBOL(genphy_handle_interrupt_no_ack);\n--\ndrivers/net/phy/phy_device.c-2849- */\ndrivers/net/phy/phy_device.c:2850:int genphy_read_abilities(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2851-{\n--\ndrivers/net/phy/phy_device.c=2894=EXPORT_SYMBOL(genphy_read_abilities);\n--\ndrivers/net/phy/phy_device.c-2901- */\ndrivers/net/phy/phy_device.c:2902:int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum)\ndrivers/net/phy/phy_device.c-2903-{\n--\ndrivers/net/phy/phy_device.c=2909=EXPORT_SYMBOL(genphy_read_mmd_c45);\ndrivers/net/phy/phy_device.c-2910-\ndrivers/net/phy/phy_device.c:2911:int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum,\ndrivers/net/phy/phy_device.c-2912-\t\t\t u16 val)\n--\ndrivers/net/phy/phy_device.c=2919=EXPORT_SYMBOL(genphy_write_mmd_c45);\n--\ndrivers/net/phy/phy_device.c-2924- */\ndrivers/net/phy/phy_device.c:2925:int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad, u16 regnum)\ndrivers/net/phy/phy_device.c-2926-{\n--\ndrivers/net/phy/phy_device.c=2929=EXPORT_SYMBOL(genphy_read_mmd_unsupported);\ndrivers/net/phy/phy_device.c-2930-\ndrivers/net/phy/phy_device.c:2931:int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,\ndrivers/net/phy/phy_device.c-2932-\t\t\t\t u16 regnum, u16 val)\n--\ndrivers/net/phy/phy_device.c=2936=EXPORT_SYMBOL(genphy_write_mmd_unsupported);\ndrivers/net/phy/phy_device.c-2937-\ndrivers/net/phy/phy_device.c:2938:int genphy_suspend(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2939-{\n--\ndrivers/net/phy/phy_device.c=2942=EXPORT_SYMBOL(genphy_suspend);\ndrivers/net/phy/phy_device.c-2943-\ndrivers/net/phy/phy_device.c:2944:int genphy_resume(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-2945-{\n--\ndrivers/net/phy/phy_device.c=2948=EXPORT_SYMBOL(genphy_resume);\ndrivers/net/phy/phy_device.c-2949-\ndrivers/net/phy/phy_device.c:2950:int genphy_loopback(struct phy_device *phydev, bool enable, int speed)\ndrivers/net/phy/phy_device.c-2951-{\n--\ndrivers/net/phy/phy_device.c=2979=EXPORT_SYMBOL(genphy_loopback);\n--\ndrivers/net/phy/phy_device.c-2989- */\ndrivers/net/phy/phy_device.c:2990:void phy_remove_link_mode(struct phy_device *phydev, u32 link_mode)\ndrivers/net/phy/phy_device.c-2991-{\n--\ndrivers/net/phy/phy_device.c=2997=static void phy_copy_pause_bits(unsigned long *dst, unsigned long *src)\n--\ndrivers/net/phy/phy_device.c-3011- */\ndrivers/net/phy/phy_device.c:3012:void phy_advertise_supported(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-3013-{\n--\ndrivers/net/phy/phy_device.c=3020=EXPORT_SYMBOL(phy_advertise_supported);\n--\ndrivers/net/phy/phy_device.c-3031- */\ndrivers/net/phy/phy_device.c:3032:void phy_advertise_eee_all(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-3033-{\n--\ndrivers/net/phy/phy_device.c=3037=EXPORT_SYMBOL_GPL(phy_advertise_eee_all);\n--\ndrivers/net/phy/phy_device.c-3058- */\ndrivers/net/phy/phy_device.c:3059:void phy_support_eee(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-3060-{\n--\ndrivers/net/phy/phy_device.c=3080=EXPORT_SYMBOL(phy_support_eee);\n--\ndrivers/net/phy/phy_device.c-3088- */\ndrivers/net/phy/phy_device.c:3089:void phy_disable_eee(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-3090-{\n--\ndrivers/net/phy/phy_device.c=3097=EXPORT_SYMBOL_GPL(phy_disable_eee);\n--\ndrivers/net/phy/phy_device.c-3105- */\ndrivers/net/phy/phy_device.c:3106:void phy_support_sym_pause(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-3107-{\n--\ndrivers/net/phy/phy_device.c=3111=EXPORT_SYMBOL(phy_support_sym_pause);\n--\ndrivers/net/phy/phy_device.c-3118- */\ndrivers/net/phy/phy_device.c:3119:void phy_support_asym_pause(struct phy_device *phydev)\ndrivers/net/phy/phy_device.c-3120-{\n--\ndrivers/net/phy/phy_device.c=3123=EXPORT_SYMBOL(phy_support_asym_pause);\n--\ndrivers/net/phy/phy_device.c-3135- */\ndrivers/net/phy/phy_device.c:3136:void phy_set_sym_pause(struct phy_device *phydev, bool rx, bool tx,\ndrivers/net/phy/phy_device.c-3137-\t\t bool autoneg)\n--\ndrivers/net/phy/phy_device.c=3147=EXPORT_SYMBOL(phy_set_sym_pause);\n--\ndrivers/net/phy/phy_device.c-3159- */\ndrivers/net/phy/phy_device.c:3160:void phy_set_asym_pause(struct phy_device *phydev, bool rx, bool tx)\ndrivers/net/phy/phy_device.c-3161-{\n--\ndrivers/net/phy/phy_device.c=3171=EXPORT_SYMBOL(phy_set_asym_pause);\n--\ndrivers/net/phy/phy_device.c-3181- */\ndrivers/net/phy/phy_device.c:3182:bool phy_validate_pause(struct phy_device *phydev,\ndrivers/net/phy/phy_device.c-3183-\t\t\tstruct ethtool_pauseparam *pp)\n--\ndrivers/net/phy/phy_device.c=3196=EXPORT_SYMBOL(phy_validate_pause);\n--\ndrivers/net/phy/phy_device.c-3209- */\ndrivers/net/phy/phy_device.c:3210:void phy_get_pause(struct phy_device *phydev, bool *tx_pause, bool *rx_pause)\ndrivers/net/phy/phy_device.c-3211-{\n--\ndrivers/net/phy/phy_device.c=3230=static int phy_get_u32_property(struct device *dev, const char *name, u32 *val)\n--\ndrivers/net/phy/phy_device.c-3250- */\ndrivers/net/phy/phy_device.c:3251:s32 phy_get_internal_delay(struct phy_device *phydev, const int *delay_values,\ndrivers/net/phy/phy_device.c-3252-\t\t\t int size, bool is_rx)\n--\ndrivers/net/phy/phy_device.c=3313=EXPORT_SYMBOL(phy_get_internal_delay);\n--\ndrivers/net/phy/phy_device.c-3323- */\ndrivers/net/phy/phy_device.c:3324:int phy_get_tx_amplitude_gain(struct phy_device *phydev, struct device *dev,\ndrivers/net/phy/phy_device.c-3325-\t\t\t enum ethtool_link_mode_bit_indices linkmode,\n--\ndrivers/net/phy/phy_device.c=3337=EXPORT_SYMBOL_GPL(phy_get_tx_amplitude_gain);\n--\ndrivers/net/phy/phy_device.c-3346- */\ndrivers/net/phy/phy_device.c:3347:int phy_get_mac_termination(struct phy_device *phydev, struct device *dev,\ndrivers/net/phy/phy_device.c-3348-\t\t\t u32 *val)\n--\ndrivers/net/phy/phy_device.c=3728=EXPORT_SYMBOL_GPL(phy_get_sfp_port);\n--\ndrivers/net/phy/phy_device.c-3735- */\ndrivers/net/phy/phy_device.c:3736:void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port)\ndrivers/net/phy/phy_device.c-3737-{\n--\ndrivers/net/phy/phy_device.c=4052=static void phy_driver_unregister(struct phy_driver *drv)\n--\ndrivers/net/phy/phy_device.c-4056-\ndrivers/net/phy/phy_device.c:4057:int phy_drivers_register(struct phy_driver *new_driver, int n,\ndrivers/net/phy/phy_device.c-4058-\t\t\t struct module *owner)\n--\ndrivers/net/phy/phy_device.c=4072=EXPORT_SYMBOL(phy_drivers_register);\ndrivers/net/phy/phy_device.c-4073-\ndrivers/net/phy/phy_device.c:4074:void phy_drivers_unregister(struct phy_driver *drv, int n)\ndrivers/net/phy/phy_device.c-4075-{\n"
} |
| 17/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34363 output: 71 thoughts: 28 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Investigating Phy Driver Export Status** Alright, so `phy_set_upstream_port` is definitely exported, noted. I'm moving on to consider the broader implications. Specifically, I'm thinking about the interactions within the PHY subsystem. That leads me to immediately wonder about the role of `drivers/net/phy/phylink.c`. I need to assess how the export of that specific function interacts with the functionality managed by `phylink.c`. Does it impact link state handling, autonegotiation setup, or any other critical PHY operational areas that that file typically handles? I need to understand the architectural flow to determine if this export creates any opportunities or, potentially, unforeseen consequences within the PHY framework. My thought process is now centering on the interconnection. |
| 18/2 | 2026/08/02 10:29 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_][a-zA-Z0-9_]*\\s+\\**[a-zA-Z_][a-zA-Z0-9_]*\\(",
"PathPrefix": "drivers/net/phy/phylink.c"
}Results: {
"Output": "drivers/net/phy/phylink.c=140=static const phy_interface_t phylink_sfp_interface_preference[] = {\n--\ndrivers/net/phy/phylink.c-153-\ndrivers/net/phy/phylink.c:154:static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);\ndrivers/net/phy/phylink.c-155-\n--\ndrivers/net/phy/phylink.c-161- */\ndrivers/net/phy/phylink.c:162:void phylink_set_port_modes(unsigned long *mask)\ndrivers/net/phy/phylink.c-163-{\n--\ndrivers/net/phy/phylink.c=374=static void phylink_caps_to_linkmodes(unsigned long *linkmodes,\n--\ndrivers/net/phy/phylink.c-395- */\ndrivers/net/phy/phylink.c:396:void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed)\ndrivers/net/phy/phylink.c-397-{\n--\ndrivers/net/phy/phylink.c=983=static void phylink_pcs_poll_start(struct phylink *pl)\n--\ndrivers/net/phy/phylink.c-988-\ndrivers/net/phy/phylink.c:989:int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs)\ndrivers/net/phy/phylink.c-990-{\n--\ndrivers/net/phy/phylink.c=1816=static int phylink_register_sfp(struct phylink *pl,\n--\ndrivers/net/phy/phylink.c-1857- */\ndrivers/net/phy/phylink.c:1858:int phylink_set_fixed_link(struct phylink *pl,\ndrivers/net/phy/phylink.c-1859-\t\t\t const struct phylink_link_state *state)\n--\ndrivers/net/phy/phylink.c=1993=EXPORT_SYMBOL_GPL(phylink_create);\n--\ndrivers/net/phy/phylink.c-2003- */\ndrivers/net/phy/phylink.c:2004:void phylink_destroy(struct phylink *pl)\ndrivers/net/phy/phylink.c-2005-{\n--\ndrivers/net/phy/phylink.c=2014=EXPORT_SYMBOL_GPL(phylink_destroy);\n--\ndrivers/net/phy/phylink.c-2024- */\ndrivers/net/phy/phylink.c:2025:bool phylink_expects_phy(struct phylink *pl)\ndrivers/net/phy/phylink.c-2026-{\n--\ndrivers/net/phy/phylink.c=2263=static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,\n--\ndrivers/net/phy/phylink.c-2296- */\ndrivers/net/phy/phylink.c:2297:int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)\ndrivers/net/phy/phylink.c-2298-{\n--\ndrivers/net/phy/phylink.c=2317=EXPORT_SYMBOL_GPL(phylink_connect_phy);\n--\ndrivers/net/phy/phylink.c-2330- */\ndrivers/net/phy/phylink.c:2331:int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn,\ndrivers/net/phy/phylink.c-2332-\t\t\t u32 flags)\n--\ndrivers/net/phy/phylink.c=2336=EXPORT_SYMBOL_GPL(phylink_of_phy_connect);\n--\ndrivers/net/phy/phylink.c-2348- */\ndrivers/net/phy/phylink.c:2349:int phylink_fwnode_phy_connect(struct phylink *pl,\ndrivers/net/phy/phylink.c-2350-\t\t\t const struct fwnode_handle *fwnode,\n--\ndrivers/net/phy/phylink.c=2395=EXPORT_SYMBOL_GPL(phylink_fwnode_phy_connect);\n--\ndrivers/net/phy/phylink.c-2403- */\ndrivers/net/phy/phylink.c:2404:void phylink_disconnect_phy(struct phylink *pl)\ndrivers/net/phy/phylink.c-2405-{\n--\ndrivers/net/phy/phylink.c=2430=static void phylink_link_changed(struct phylink *pl, bool up, const char *what)\n--\ndrivers/net/phy/phylink.c-2445- */\ndrivers/net/phy/phylink.c:2446:void phylink_mac_change(struct phylink *pl, bool up)\ndrivers/net/phy/phylink.c-2447-{\n--\ndrivers/net/phy/phylink.c=2450=EXPORT_SYMBOL_GPL(phylink_mac_change);\n--\ndrivers/net/phy/phylink.c-2462- */\ndrivers/net/phy/phylink.c:2463:void phylink_pcs_change(struct phylink_pcs *pcs, bool up)\ndrivers/net/phy/phylink.c-2464-{\n--\ndrivers/net/phy/phylink.c=2472=static irqreturn_t phylink_link_handler(int irq, void *data)\n--\ndrivers/net/phy/phylink.c-2488- */\ndrivers/net/phy/phylink.c:2489:void phylink_start(struct phylink *pl)\ndrivers/net/phy/phylink.c-2490-{\n--\ndrivers/net/phy/phylink.c=2545=EXPORT_SYMBOL_GPL(phylink_start);\n--\ndrivers/net/phy/phylink.c-2558- */\ndrivers/net/phy/phylink.c:2559:void phylink_stop(struct phylink *pl)\ndrivers/net/phy/phylink.c-2560-{\n--\ndrivers/net/phy/phylink.c=2579=EXPORT_SYMBOL_GPL(phylink_stop);\n--\ndrivers/net/phy/phylink.c-2588- */\ndrivers/net/phy/phylink.c:2589:void phylink_rx_clk_stop_block(struct phylink *pl)\ndrivers/net/phy/phylink.c-2590-{\n--\ndrivers/net/phy/phylink.c=2608=EXPORT_SYMBOL_GPL(phylink_rx_clk_stop_block);\n--\ndrivers/net/phy/phylink.c-2617- */\ndrivers/net/phy/phylink.c:2618:void phylink_rx_clk_stop_unblock(struct phylink *pl)\ndrivers/net/phy/phylink.c-2619-{\n--\ndrivers/net/phy/phylink.c=2650=static bool phylink_phy_pm_speed_ctrl(struct phylink *pl)\n--\ndrivers/net/phy/phylink.c-2672- */\ndrivers/net/phy/phylink.c:2673:void phylink_suspend(struct phylink *pl, bool mac_wol)\ndrivers/net/phy/phylink.c-2674-{\n--\ndrivers/net/phy/phylink.c=2709=EXPORT_SYMBOL_GPL(phylink_suspend);\n--\ndrivers/net/phy/phylink.c-2718- */\ndrivers/net/phy/phylink.c:2719:void phylink_prepare_resume(struct phylink *pl)\ndrivers/net/phy/phylink.c-2720-{\n--\ndrivers/net/phy/phylink.c=2734=EXPORT_SYMBOL_GPL(phylink_prepare_resume);\n--\ndrivers/net/phy/phylink.c-2742- */\ndrivers/net/phy/phylink.c:2743:void phylink_resume(struct phylink *pl)\ndrivers/net/phy/phylink.c-2744-{\n--\ndrivers/net/phy/phylink.c=2777=EXPORT_SYMBOL_GPL(phylink_resume);\n--\ndrivers/net/phy/phylink.c-2787- */\ndrivers/net/phy/phylink.c:2788:void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol)\ndrivers/net/phy/phylink.c-2789-{\n--\ndrivers/net/phy/phylink.c=2814=EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol);\n--\ndrivers/net/phy/phylink.c-2826- */\ndrivers/net/phy/phylink.c:2827:int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol)\ndrivers/net/phy/phylink.c-2828-{\n--\ndrivers/net/phy/phylink.c=2946=static void phylink_get_ksettings(const struct phylink_link_state *state,\n--\ndrivers/net/phy/phylink.c-2968- */\ndrivers/net/phy/phylink.c:2969:int phylink_ethtool_ksettings_get(struct phylink *pl,\ndrivers/net/phy/phylink.c-2970-\t\t\t\t struct ethtool_link_ksettings *kset)\n--\ndrivers/net/phy/phylink.c=3014=static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,\n--\ndrivers/net/phy/phylink.c-3038- */\ndrivers/net/phy/phylink.c:3039:int phylink_ethtool_ksettings_set(struct phylink *pl,\ndrivers/net/phy/phylink.c-3040-\t\t\t\t const struct ethtool_link_ksettings *kset)\n--\ndrivers/net/phy/phylink.c=3202=EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set);\n--\ndrivers/net/phy/phylink.c-3214- */\ndrivers/net/phy/phylink.c:3215:int phylink_ethtool_nway_reset(struct phylink *pl)\ndrivers/net/phy/phylink.c-3216-{\n--\ndrivers/net/phy/phylink.c=3227=EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);\n--\ndrivers/net/phy/phylink.c-3233- */\ndrivers/net/phy/phylink.c:3234:void phylink_ethtool_get_pauseparam(struct phylink *pl,\ndrivers/net/phy/phylink.c-3235-\t\t\t\t struct ethtool_pauseparam *pause)\n--\ndrivers/net/phy/phylink.c=3243=EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);\n--\ndrivers/net/phy/phylink.c-3249- */\ndrivers/net/phy/phylink.c:3250:int phylink_ethtool_set_pauseparam(struct phylink *pl,\ndrivers/net/phy/phylink.c-3251-\t\t\t\t struct ethtool_pauseparam *pause)\n--\ndrivers/net/phy/phylink.c=3329=EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);\n--\ndrivers/net/phy/phylink.c-3340- */\ndrivers/net/phy/phylink.c:3341:int phylink_get_eee_err(struct phylink *pl)\ndrivers/net/phy/phylink.c-3342-{\n--\ndrivers/net/phy/phylink.c=3352=EXPORT_SYMBOL_GPL(phylink_get_eee_err);\n--\ndrivers/net/phy/phylink.c-3358- */\ndrivers/net/phy/phylink.c:3359:int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_keee *eee)\ndrivers/net/phy/phylink.c-3360-{\n--\ndrivers/net/phy/phylink.c=3378=EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);\n--\ndrivers/net/phy/phylink.c-3384- */\ndrivers/net/phy/phylink.c:3385:int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)\ndrivers/net/phy/phylink.c-3386-{\n--\ndrivers/net/phy/phylink.c=3552=static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,\n--\ndrivers/net/phy/phylink.c-3586- */\ndrivers/net/phy/phylink.c:3587:int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)\ndrivers/net/phy/phylink.c-3588-{\n--\ndrivers/net/phy/phylink.c=3645=EXPORT_SYMBOL_GPL(phylink_mii_ioctl);\n--\ndrivers/net/phy/phylink.c-3658- */\ndrivers/net/phy/phylink.c:3659:int phylink_speed_down(struct phylink *pl, bool sync)\ndrivers/net/phy/phylink.c-3660-{\n--\ndrivers/net/phy/phylink.c=3670=EXPORT_SYMBOL_GPL(phylink_speed_down);\n--\ndrivers/net/phy/phylink.c-3681- */\ndrivers/net/phy/phylink.c:3682:int phylink_speed_up(struct phylink *pl)\ndrivers/net/phy/phylink.c-3683-{\n--\ndrivers/net/phy/phylink.c=4095=static struct {\n--\ndrivers/net/phy/phylink.c-4110-\ndrivers/net/phy/phylink.c:4111:void phylink_resolve_c73(struct phylink_link_state *state)\ndrivers/net/phy/phylink.c-4112-{\n--\ndrivers/net/phy/phylink.c=4158=static void phylink_decode_sgmii_word(struct phylink_link_state *state,\n--\ndrivers/net/phy/phylink.c-4194- */\ndrivers/net/phy/phylink.c:4195:void phylink_decode_usxgmii_word(struct phylink_link_state *state,\ndrivers/net/phy/phylink.c-4196-\t\t\t\t uint16_t lpa)\n--\ndrivers/net/phy/phylink.c=4240=static void phylink_decode_usgmii_word(struct phylink_link_state *state,\n--\ndrivers/net/phy/phylink.c-4278- */\ndrivers/net/phy/phylink.c:4279:void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state,\ndrivers/net/phy/phylink.c-4280-\t\t\t\t unsigned int neg_mode, u16 bmsr, u16 lpa)\n--\ndrivers/net/phy/phylink.c=4326=EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_decode_state);\n--\ndrivers/net/phy/phylink.c-4342- */\ndrivers/net/phy/phylink.c:4343:void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs,\ndrivers/net/phy/phylink.c-4344-\t\t\t\t unsigned int neg_mode,\n--\ndrivers/net/phy/phylink.c=4358=EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_get_state);\n--\ndrivers/net/phy/phylink.c-4373- */\ndrivers/net/phy/phylink.c:4374:int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface,\ndrivers/net/phy/phylink.c-4375-\t\t\t\t\t const unsigned long *advertising)\n--\ndrivers/net/phy/phylink.c=4398=EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_encode_advertisement);\n--\ndrivers/net/phy/phylink.c-4411- */\ndrivers/net/phy/phylink.c:4412:int phylink_mii_c22_pcs_config(struct mdio_device *pcs,\ndrivers/net/phy/phylink.c-4413-\t\t\t phy_interface_t interface,\n--\ndrivers/net/phy/phylink.c=4442=EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_config);\n--\ndrivers/net/phy/phylink.c-4454- */\ndrivers/net/phy/phylink.c:4455:void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs)\ndrivers/net/phy/phylink.c-4456-{\n--\ndrivers/net/phy/phylink.c=4465=EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_an_restart);\ndrivers/net/phy/phylink.c-4466-\ndrivers/net/phy/phylink.c:4467:void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs,\ndrivers/net/phy/phylink.c-4468-\t\t\t\t struct phylink_link_state *state)\n--\ndrivers/net/phy/phylink.c=4494=EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state);\n--\ndrivers/net/phy/phylink.c-4508- */\ndrivers/net/phy/phylink.c:4509:void phylink_replay_link_begin(struct phylink *pl)\ndrivers/net/phy/phylink.c-4510-{\n--\ndrivers/net/phy/phylink.c=4515=EXPORT_SYMBOL_GPL(phylink_replay_link_begin);\n--\ndrivers/net/phy/phylink.c-4531- */\ndrivers/net/phy/phylink.c:4532:void phylink_replay_link_end(struct phylink *pl)\ndrivers/net/phy/phylink.c-4533-{\n"
} |
| 19/2 | 2026/08/02 10:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39480 output: 30 thoughts: 1012 Thoughts: **My Analysis of Relevant Functions** Okay, so I'm trying to identify the key functions here. My initial focus is on these: * `ethnl_port_dump_start` * `ethnl_port_dumpit` * `ethnl_port_dump_done` * `phy_link_topo_add_port` * `phy_link_topo_del_port` * `phy_caps_linkmode_filter_ifaces` * `phy_set_upstream_port` I quickly realized the first three are in `net/ethtool/port.c`. I checked for other potentially relevant functions in that file, but I confirmed that those are the only non-static functions. I was briefly concerned about the static helper functions, like `port_parse_request`, `port_prepare_data`, `port_reply_size`, and `port_fill_reply`, used within `net/ethtool/port.c`. Although they are static, I recognized they're used as function pointers within `ethnl_port_request_ops`, so they likely aren't inlined. However, the prompt emphasizes prioritizing non-static, non-inlined API entrypoints, so I'm sticking with the original list. These static functions will be the core of the `doit` command, so I'll keep them in mind, but won't include them in the final list for now. `ethnl_default_doit` seems to be the entry point to that logic. Then I considered other functions in `drivers/net/phy/phy_device.c` and `drivers/net/phy/phylink.c` like `phy_add_sfp_mod_port`, `phy_del_sfp_mod_port`, `phylink_create_sfp_cage_port`, etc. but they were all static, so I ignored them. So I am keeping my initial list. For the configs, I'll need `ETHTOOL_NETLINK`, `PHYLIB`, and possibly `PHYLINK`. Let me double-check `PHYLINK`. |
| 20/2 | 2026/08/02 10:30 | tool | grepper |
0mArgs:{
"Expression": "config PHYLINK",
"PathPrefix": "drivers/net/phy/"
}Results: {
"Output": "drivers/net/phy/Kconfig-5-\ndrivers/net/phy/Kconfig:6:config PHYLINK\ndrivers/net/phy/Kconfig-7-\ttristate\n"
} |
| 21/2 | 2026/08/02 10:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41049 output: 29 thoughts: 115 Thoughts: **Checking Configuration Options for SFP Port Support** Okay, so I'm going through the configuration options related to this SFP port support patch. First, `PHYLINK` is tristate, so that definitely gets included. Let's see... `ETHTOOL_NETLINK` is default yes, but I'm going to list it anyway, just to be thorough. Then, `PHYLIB` and `PHYLINK` are both tristate, so we add those as well. Now, I should make sure there's nothing else required; is there something like `MACB` or another driver that would need to be enabled for an SFP test? I need to figure out if this patch is completely self contained. Right, the patch adds SFP port support, so let's make sure we include the relevant configuration option. I think I will need to check if `CONFIG_SFP` is even required, and make sure that we include that information. |
| 22/2 | 2026/08/02 10:30 | tool | grepper |
0mArgs:{
"Expression": "config SFP",
"PathPrefix": "drivers/net/phy/"
}Results: {
"Output": "drivers/net/phy/Kconfig=65=config RUST_PHYLIB_ABSTRACTIONS\n--\ndrivers/net/phy/Kconfig-72-\ndrivers/net/phy/Kconfig:73:config SFP\ndrivers/net/phy/Kconfig-74-\ttristate \"SFP cage support\"\n"
} |
| 23/2 | 2026/08/02 10:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41486 output: 35 thoughts: 36 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **SFP Cage Enablement Investigation** Alright, so the task at hand is definitely related to the SFP cage support. I need to get this right. Let's start with a solid affirmation: yes, I need to make sure `CONFIG_SFP` is enabled. That seems like the obvious first step. Now, where do I find the details for this configuration option? Given the context, the logical place to start digging is in the network PHY driver configuration. I'll open up `drivers/net/phy/Kconfig` to see what I can uncover regarding the `SFP` Kconfig options. This should give me the information I need to confirm the underlying configuration. |
| 24/2 | 2026/08/02 10:30 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/Kconfig",
"FirstLine": 70,
"LineCount": 15
}Results: {
"Contents": " 70:\t Adds support needed for PHY drivers written in Rust. It provides\n 71:\t a wrapper around the C phylib core.\n 72:\t\n 73:\tconfig SFP\n 74:\t\ttristate \"SFP cage support\"\n 75:\t\tdepends on I2C \u0026\u0026 PHYLINK\n 76:\t\tdepends on HWMON || HWMON=n\n 77:\t\tselect MDIO_I2C\n 78:\t\n 79:\tcomment \"MII PHY device drivers\"\n 80:\t\n 81:\tconfig AS21XXX_PHY\n 82:\t\ttristate \"Aeonsemi AS21xxx PHYs\"\n 83:\t\thelp\n 84:\t\t Currently supports the Aeonsemi AS21xxx PHY.\n"
} |
| 25/2 | 2026/08/02 10:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41929 output: 211 thoughts: 404 Thoughts: **My Analysis of the SFP Integration Patch** Okay, so this patch introduces some pretty significant changes, specifically regarding SFP support within the PHY framework and exposes a new netlink API for getting physical port information. My initial assessment is that `CONFIG_SFP` *definitely* depends on `I2C` and `PHYLINK`. I should make sure to include those dependencies in my build. And I'll add `PHYLIB`, `ETHTOOL_NETLINK`, and of course `SFP` itself. The core of this is the new `PORT_GET` command in ethtool netlink. That immediately tells me this is a functional change, a pretty major one since we're interacting directly with the PHY/phylink logic that's accessible from user space. Translation: *This is definitely going to need fuzzing.* We're exposing new APIs, so we need to ensure the implementation is robust. Let me take a look at the specific symbols I should focus on for fuzzing and coverage. I'm thinking: * `ethnl_port_dump_start` * `ethnl_port_dumpit` * `ethnl_port_dump_done` * `phy_link_topo_add_port` * `phy_link_topo_del_port` * `phy_caps_linkmode_filter_ifaces` * `phy_set_upstream_port` Those seem like the crucial entry and exit points for the new functionality. And let's quickly double-check my configuration dependencies: * `ETHTOOL_NETLINK` - obviously a key one. * `PHYLIB` - fundamental, for the PHY framework itself. * `PHYLINK` - the link between the PHY and the network. * `SFP` - the feature that's driving the change. * `I2C` - for SFP/module interaction. Hmm, I'm going to quickly check if `port_parse_request` and the other static functions involved in processing requests are even worth considering. They're static, so if they are inlined, their symbols won't be distinct, but their addresses are being taken in `ethnl_port_request_ops`, so that suggests they are not inlined. Since syzkaller, and generally coverage tracking, uses exported or non-static symbols, I'll stick to listing those non-static symbols. Let me double check that `port_parse_request` is indeed in `net/ethtool/port.c`. *Yes, it is*. Okay, great, I can just provide the non-static ones. Alright, I think I've got a solid plan here. I'll flag this as needing coverage testing. And time to call `set-results`. |
| 26/2 | 2026/08/02 10:30 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"ETHTOOL_NETLINK",
"PHYLIB",
"PHYLINK",
"SFP",
"I2C"
],
"FocusSymbols": [
"ethnl_port_dump_start",
"ethnl_port_dumpit",
"ethnl_port_dump_done",
"phy_link_topo_add_port",
"phy_link_topo_del_port",
"phy_caps_linkmode_filter_ifaces",
"phy_set_upstream_port"
],
"Reasoning": "The patch introduces a new ethtool netlink command `PORT_GET` to retrieve physical port information, and adds SFP port support to the PHY framework. This is a functional change that exposes new netlink APIs and modifies core PHY/phylink logic, which can be reached from userspace via ethtool and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"ETHTOOL_NETLINK",
"PHYLIB",
"PHYLINK",
"SFP",
"I2C"
],
"FocusSymbols": [
"ethnl_port_dump_start",
"ethnl_port_dumpit",
"ethnl_port_dump_done",
"phy_link_topo_add_port",
"phy_link_topo_del_port",
"phy_caps_linkmode_filter_ifaces",
"phy_set_upstream_port"
],
"Reasoning": "The patch introduces a new ethtool netlink command `PORT_GET` to retrieve physical port information, and adds SFP port support to the PHY framework. This is a functional change that exposes new netlink APIs and modifies core PHY/phylink logic, which can be reached from userspace via ethtool and should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|