Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: ethtool: Introduce a command to list PHYs on an interface

As we have the ability to track the PHYs connected to a net_device
through the link_topology, we can expose this list to userspace. This
allows userspace to use these identifiers for phy-specific commands and
take the decision of which PHY to target by knowing the link topology.

Add PHY_GET and PHY_DUMP, which can be a filtered DUMP operation to list
devices on only one interface.

Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Reviewed-by: Christophe Leroy <christophe.leroy@csgroup.eu>
Tested-by: Christophe Leroy <christophe.leroy@csgroup.eu>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Maxime Chevallier and committed by
David S. Miller
17194be4 9af0e89d

+384 -1
+41
Documentation/networking/ethtool-netlink.rst
··· 2191 2191 The ``ETHTOOL_A_MODULE_FW_FLASH_DONE`` and ``ETHTOOL_A_MODULE_FW_FLASH_TOTAL`` 2192 2192 attributes encode the completed and total amount of work, respectively. 2193 2193 2194 + PHY_GET 2195 + ======= 2196 + 2197 + Retrieve information about a given Ethernet PHY sitting on the link. The DO 2198 + operation returns all available information about dev->phydev. User can also 2199 + specify a PHY_INDEX, in which case the DO request returns information about that 2200 + specific PHY. 2201 + As there can be more than one PHY, the DUMP operation can be used to list the PHYs 2202 + present on a given interface, by passing an interface index or name in 2203 + the dump request. 2204 + 2205 + Request contents: 2206 + 2207 + ==================================== ====== ========================== 2208 + ``ETHTOOL_A_PHY_HEADER`` nested request header 2209 + ==================================== ====== ========================== 2210 + 2211 + Kernel response contents: 2212 + 2213 + ===================================== ====== =============================== 2214 + ``ETHTOOL_A_PHY_HEADER`` nested request header 2215 + ``ETHTOOL_A_PHY_INDEX`` u32 the phy's unique index, that can 2216 + be used for phy-specific 2217 + requests 2218 + ``ETHTOOL_A_PHY_DRVNAME`` string the phy driver name 2219 + ``ETHTOOL_A_PHY_NAME`` string the phy device name 2220 + ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` u32 the type of device this phy is 2221 + connected to 2222 + ``ETHTOOL_A_PHY_UPSTREAM_INDEX`` u32 the PHY index of the upstream 2223 + PHY 2224 + ``ETHTOOL_A_PHY_UPSTREAM_SFP_NAME`` string if this PHY is connected to 2225 + its parent PHY through an SFP 2226 + bus, the name of this sfp bus 2227 + ``ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME`` string if the phy controls an sfp bus, 2228 + the name of the sfp bus 2229 + ===================================== ====== =============================== 2230 + 2231 + When ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` is PHY_UPSTREAM_PHY, the PHY's parent is 2232 + another PHY. 2233 + 2194 2234 Request translation 2195 2235 =================== 2196 2236 ··· 2338 2298 n/a ``ETHTOOL_MSG_MM_GET`` 2339 2299 n/a ``ETHTOOL_MSG_MM_SET`` 2340 2300 n/a ``ETHTOOL_MSG_MODULE_FW_FLASH_ACT`` 2301 + n/a ``ETHTOOL_MSG_PHY_GET`` 2341 2302 =================================== =====================================
+19
include/uapi/linux/ethtool_netlink.h
··· 58 58 ETHTOOL_MSG_MM_GET, 59 59 ETHTOOL_MSG_MM_SET, 60 60 ETHTOOL_MSG_MODULE_FW_FLASH_ACT, 61 + ETHTOOL_MSG_PHY_GET, 61 62 62 63 /* add new constants above here */ 63 64 __ETHTOOL_MSG_USER_CNT, ··· 112 111 ETHTOOL_MSG_MM_GET_REPLY, 113 112 ETHTOOL_MSG_MM_NTF, 114 113 ETHTOOL_MSG_MODULE_FW_FLASH_NTF, 114 + ETHTOOL_MSG_PHY_GET_REPLY, 115 + ETHTOOL_MSG_PHY_NTF, 115 116 116 117 /* add new constants above here */ 117 118 __ETHTOOL_MSG_KERNEL_CNT, ··· 1056 1053 /* add new constants above here */ 1057 1054 __ETHTOOL_A_MODULE_FW_FLASH_CNT, 1058 1055 ETHTOOL_A_MODULE_FW_FLASH_MAX = (__ETHTOOL_A_MODULE_FW_FLASH_CNT - 1) 1056 + }; 1057 + 1058 + enum { 1059 + ETHTOOL_A_PHY_UNSPEC, 1060 + ETHTOOL_A_PHY_HEADER, /* nest - _A_HEADER_* */ 1061 + ETHTOOL_A_PHY_INDEX, /* u32 */ 1062 + ETHTOOL_A_PHY_DRVNAME, /* string */ 1063 + ETHTOOL_A_PHY_NAME, /* string */ 1064 + ETHTOOL_A_PHY_UPSTREAM_TYPE, /* u32 */ 1065 + ETHTOOL_A_PHY_UPSTREAM_INDEX, /* u32 */ 1066 + ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, /* string */ 1067 + ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, /* string */ 1068 + 1069 + /* add new constants above here */ 1070 + __ETHTOOL_A_PHY_CNT, 1071 + ETHTOOL_A_PHY_MAX = (__ETHTOOL_A_PHY_CNT - 1) 1059 1072 }; 1060 1073 1061 1074 /* generic netlink info */
+2 -1
net/ethtool/Makefile
··· 8 8 linkstate.o debug.o wol.o features.o privflags.o rings.o \ 9 9 channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \ 10 10 tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \ 11 - module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o 11 + module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o \ 12 + phy.o
+9
net/ethtool/netlink.c
··· 1234 1234 .policy = ethnl_module_fw_flash_act_policy, 1235 1235 .maxattr = ARRAY_SIZE(ethnl_module_fw_flash_act_policy) - 1, 1236 1236 }, 1237 + { 1238 + .cmd = ETHTOOL_MSG_PHY_GET, 1239 + .doit = ethnl_phy_doit, 1240 + .start = ethnl_phy_start, 1241 + .dumpit = ethnl_phy_dumpit, 1242 + .done = ethnl_phy_done, 1243 + .policy = ethnl_phy_get_policy, 1244 + .maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1, 1245 + }, 1237 1246 }; 1238 1247 1239 1248 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
+5
net/ethtool/netlink.h
··· 484 484 extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1]; 485 485 extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1]; 486 486 extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1]; 487 + extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1]; 487 488 488 489 int ethnl_set_features(struct sk_buff *skb, struct genl_info *info); 489 490 int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info); ··· 495 494 int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info); 496 495 int ethnl_rss_dump_start(struct netlink_callback *cb); 497 496 int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb); 497 + int ethnl_phy_start(struct netlink_callback *cb); 498 + int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info); 499 + int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb); 500 + int ethnl_phy_done(struct netlink_callback *cb); 498 501 499 502 extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN]; 500 503 extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
+308
net/ethtool/phy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright 2023 Bootlin 4 + * 5 + */ 6 + #include "common.h" 7 + #include "netlink.h" 8 + 9 + #include <linux/phy.h> 10 + #include <linux/phy_link_topology.h> 11 + #include <linux/sfp.h> 12 + 13 + struct phy_req_info { 14 + struct ethnl_req_info base; 15 + struct phy_device_node *pdn; 16 + }; 17 + 18 + #define PHY_REQINFO(__req_base) \ 19 + container_of(__req_base, struct phy_req_info, base) 20 + 21 + const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1] = { 22 + [ETHTOOL_A_PHY_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), 23 + }; 24 + 25 + /* Caller holds rtnl */ 26 + static ssize_t 27 + ethnl_phy_reply_size(const struct ethnl_req_info *req_base, 28 + struct netlink_ext_ack *extack) 29 + { 30 + struct phy_req_info *req_info = PHY_REQINFO(req_base); 31 + struct phy_device_node *pdn = req_info->pdn; 32 + struct phy_device *phydev = pdn->phy; 33 + size_t size = 0; 34 + 35 + ASSERT_RTNL(); 36 + 37 + /* ETHTOOL_A_PHY_INDEX */ 38 + size += nla_total_size(sizeof(u32)); 39 + 40 + /* ETHTOOL_A_DRVNAME */ 41 + if (phydev->drv) 42 + size += nla_total_size(strlen(phydev->drv->name) + 1); 43 + 44 + /* ETHTOOL_A_NAME */ 45 + size += nla_total_size(strlen(dev_name(&phydev->mdio.dev)) + 1); 46 + 47 + /* ETHTOOL_A_PHY_UPSTREAM_TYPE */ 48 + size += nla_total_size(sizeof(u32)); 49 + 50 + if (phy_on_sfp(phydev)) { 51 + const char *upstream_sfp_name = sfp_get_name(pdn->parent_sfp_bus); 52 + 53 + /* ETHTOOL_A_PHY_UPSTREAM_SFP_NAME */ 54 + if (upstream_sfp_name) 55 + size += nla_total_size(strlen(upstream_sfp_name) + 1); 56 + 57 + /* ETHTOOL_A_PHY_UPSTREAM_INDEX */ 58 + size += nla_total_size(sizeof(u32)); 59 + } 60 + 61 + /* ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME */ 62 + if (phydev->sfp_bus) { 63 + const char *sfp_name = sfp_get_name(phydev->sfp_bus); 64 + 65 + if (sfp_name) 66 + size += nla_total_size(strlen(sfp_name) + 1); 67 + } 68 + 69 + return size; 70 + } 71 + 72 + static int 73 + ethnl_phy_fill_reply(const struct ethnl_req_info *req_base, struct sk_buff *skb) 74 + { 75 + struct phy_req_info *req_info = PHY_REQINFO(req_base); 76 + struct phy_device_node *pdn = req_info->pdn; 77 + struct phy_device *phydev = pdn->phy; 78 + enum phy_upstream ptype; 79 + 80 + ptype = pdn->upstream_type; 81 + 82 + if (nla_put_u32(skb, ETHTOOL_A_PHY_INDEX, phydev->phyindex) || 83 + nla_put_string(skb, ETHTOOL_A_PHY_NAME, dev_name(&phydev->mdio.dev)) || 84 + nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_TYPE, ptype)) 85 + return -EMSGSIZE; 86 + 87 + if (phydev->drv && 88 + nla_put_string(skb, ETHTOOL_A_PHY_DRVNAME, phydev->drv->name)) 89 + return -EMSGSIZE; 90 + 91 + if (ptype == PHY_UPSTREAM_PHY) { 92 + struct phy_device *upstream = pdn->upstream.phydev; 93 + const char *sfp_upstream_name; 94 + 95 + /* Parent index */ 96 + if (nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_INDEX, upstream->phyindex)) 97 + return -EMSGSIZE; 98 + 99 + if (pdn->parent_sfp_bus) { 100 + sfp_upstream_name = sfp_get_name(pdn->parent_sfp_bus); 101 + if (sfp_upstream_name && 102 + nla_put_string(skb, ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, 103 + sfp_upstream_name)) 104 + return -EMSGSIZE; 105 + } 106 + } 107 + 108 + if (phydev->sfp_bus) { 109 + const char *sfp_name = sfp_get_name(phydev->sfp_bus); 110 + 111 + if (sfp_name && 112 + nla_put_string(skb, ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, 113 + sfp_name)) 114 + return -EMSGSIZE; 115 + } 116 + 117 + return 0; 118 + } 119 + 120 + static int ethnl_phy_parse_request(struct ethnl_req_info *req_base, 121 + struct nlattr **tb, 122 + struct netlink_ext_ack *extack) 123 + { 124 + struct phy_link_topology *topo = req_base->dev->link_topo; 125 + struct phy_req_info *req_info = PHY_REQINFO(req_base); 126 + struct phy_device *phydev; 127 + 128 + phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_PHY_HEADER], 129 + extack); 130 + if (!phydev) 131 + return 0; 132 + 133 + if (IS_ERR(phydev)) 134 + return PTR_ERR(phydev); 135 + 136 + if (!topo) 137 + return 0; 138 + 139 + req_info->pdn = xa_load(&topo->phys, phydev->phyindex); 140 + 141 + return 0; 142 + } 143 + 144 + int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info) 145 + { 146 + struct phy_req_info req_info = {}; 147 + struct nlattr **tb = info->attrs; 148 + struct sk_buff *rskb; 149 + void *reply_payload; 150 + int reply_len; 151 + int ret; 152 + 153 + ret = ethnl_parse_header_dev_get(&req_info.base, 154 + tb[ETHTOOL_A_PHY_HEADER], 155 + genl_info_net(info), info->extack, 156 + true); 157 + if (ret < 0) 158 + return ret; 159 + 160 + rtnl_lock(); 161 + 162 + ret = ethnl_phy_parse_request(&req_info.base, tb, info->extack); 163 + if (ret < 0) 164 + goto err_unlock_rtnl; 165 + 166 + /* No PHY, return early */ 167 + if (!req_info.pdn->phy) 168 + goto err_unlock_rtnl; 169 + 170 + ret = ethnl_phy_reply_size(&req_info.base, info->extack); 171 + if (ret < 0) 172 + goto err_unlock_rtnl; 173 + reply_len = ret + ethnl_reply_header_size(); 174 + 175 + rskb = ethnl_reply_init(reply_len, req_info.base.dev, 176 + ETHTOOL_MSG_PHY_GET_REPLY, 177 + ETHTOOL_A_PHY_HEADER, 178 + info, &reply_payload); 179 + if (!rskb) { 180 + ret = -ENOMEM; 181 + goto err_unlock_rtnl; 182 + } 183 + 184 + ret = ethnl_phy_fill_reply(&req_info.base, rskb); 185 + if (ret) 186 + goto err_free_msg; 187 + 188 + rtnl_unlock(); 189 + ethnl_parse_header_dev_put(&req_info.base); 190 + genlmsg_end(rskb, reply_payload); 191 + 192 + return genlmsg_reply(rskb, info); 193 + 194 + err_free_msg: 195 + nlmsg_free(rskb); 196 + err_unlock_rtnl: 197 + rtnl_unlock(); 198 + ethnl_parse_header_dev_put(&req_info.base); 199 + return ret; 200 + } 201 + 202 + struct ethnl_phy_dump_ctx { 203 + struct phy_req_info *phy_req_info; 204 + unsigned long ifindex; 205 + unsigned long phy_index; 206 + }; 207 + 208 + int ethnl_phy_start(struct netlink_callback *cb) 209 + { 210 + const struct genl_info *info = genl_info_dump(cb); 211 + struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; 212 + int ret; 213 + 214 + BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); 215 + 216 + ctx->phy_req_info = kzalloc(sizeof(*ctx->phy_req_info), GFP_KERNEL); 217 + if (!ctx->phy_req_info) 218 + return -ENOMEM; 219 + 220 + ret = ethnl_parse_header_dev_get(&ctx->phy_req_info->base, 221 + info->attrs[ETHTOOL_A_PHY_HEADER], 222 + sock_net(cb->skb->sk), cb->extack, 223 + false); 224 + ctx->ifindex = 0; 225 + ctx->phy_index = 0; 226 + 227 + if (ret) 228 + kfree(ctx->phy_req_info); 229 + 230 + return ret; 231 + } 232 + 233 + int ethnl_phy_done(struct netlink_callback *cb) 234 + { 235 + struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; 236 + 237 + if (ctx->phy_req_info->base.dev) 238 + ethnl_parse_header_dev_put(&ctx->phy_req_info->base); 239 + 240 + kfree(ctx->phy_req_info); 241 + 242 + return 0; 243 + } 244 + 245 + static int ethnl_phy_dump_one_dev(struct sk_buff *skb, struct net_device *dev, 246 + struct netlink_callback *cb) 247 + { 248 + struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; 249 + struct phy_req_info *pri = ctx->phy_req_info; 250 + struct phy_device_node *pdn; 251 + int ret = 0; 252 + void *ehdr; 253 + 254 + pri->base.dev = dev; 255 + 256 + if (!dev->link_topo) 257 + return 0; 258 + 259 + xa_for_each_start(&dev->link_topo->phys, ctx->phy_index, pdn, ctx->phy_index) { 260 + ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_PHY_GET_REPLY); 261 + if (!ehdr) { 262 + ret = -EMSGSIZE; 263 + break; 264 + } 265 + 266 + ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_PHY_HEADER); 267 + if (ret < 0) { 268 + genlmsg_cancel(skb, ehdr); 269 + break; 270 + } 271 + 272 + pri->pdn = pdn; 273 + ret = ethnl_phy_fill_reply(&pri->base, skb); 274 + if (ret < 0) { 275 + genlmsg_cancel(skb, ehdr); 276 + break; 277 + } 278 + 279 + genlmsg_end(skb, ehdr); 280 + } 281 + 282 + return ret; 283 + } 284 + 285 + int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb) 286 + { 287 + struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; 288 + struct net *net = sock_net(skb->sk); 289 + struct net_device *dev; 290 + int ret = 0; 291 + 292 + rtnl_lock(); 293 + 294 + if (ctx->phy_req_info->base.dev) { 295 + ret = ethnl_phy_dump_one_dev(skb, ctx->phy_req_info->base.dev, cb); 296 + } else { 297 + for_each_netdev_dump(net, dev, ctx->ifindex) { 298 + ret = ethnl_phy_dump_one_dev(skb, dev, cb); 299 + if (ret) 300 + break; 301 + 302 + ctx->phy_index = 0; 303 + } 304 + } 305 + rtnl_unlock(); 306 + 307 + return ret; 308 + }