History

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.

Maxime Chevallier committed 2 years ago

Commit 17194be1 parent 9af0e896 files · +384 −1Browse files

Unified · Split

6 files
  1. Documentation/networking/ethtool-netlink.rst +41 −0
  2. include/uapi/linux/ethtool_netlink.h +19 −0
  3. net/ethtool/Makefile +2 −1
  4. net/ethtool/netlink.c +9 −0
  5. net/ethtool/netlink.h +5 −0
  6. net/ethtool/phy.c +308 −0
Documentation/networking/ethtool-netlink.rst+41−0
@@ -2191,6 +2191,46 @@ string. · 2190 unmodified lines
2191The ``ETHTOOL_A_MODULE_FW_FLASH_DONE`` and ``ETHTOOL_A_MODULE_FW_FLASH_TOTAL``2191The ``ETHTOOL_A_MODULE_FW_FLASH_DONE`` and ``ETHTOOL_A_MODULE_FW_FLASH_TOTAL``
2192attributes encode the completed and total amount of work, respectively.2192attributes encode the completed and total amount of work, respectively.
2193 2193
2194PHY_GET
2195=======
2196
2197Retrieve information about a given Ethernet PHY sitting on the link. The DO
2198operation returns all available information about dev->phydev. User can also
2199specify a PHY_INDEX, in which case the DO request returns information about that
2200specific PHY.
2201As there can be more than one PHY, the DUMP operation can be used to list the PHYs
2202present on a given interface, by passing an interface index or name in
2203the dump request.
2204
2205Request contents:
2206
2207 ==================================== ====== ==========================
2208 ``ETHTOOL_A_PHY_HEADER`` nested request header
2209 ==================================== ====== ==========================
2210
2211Kernel 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
2231When ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` is PHY_UPSTREAM_PHY, the PHY's parent is
2232another PHY.
2233
2194Request translation2234Request translation
2195===================2235===================
2196 2236
@@ -2298,4 +2338,5 @@ are netlink only. · 101 unmodified lines
2298 n/a ``ETHTOOL_MSG_MM_GET``2338 n/a ``ETHTOOL_MSG_MM_GET``
2299 n/a ``ETHTOOL_MSG_MM_SET``2339 n/a ``ETHTOOL_MSG_MM_SET``
2300 n/a ``ETHTOOL_MSG_MODULE_FW_FLASH_ACT``2340 n/a ``ETHTOOL_MSG_MODULE_FW_FLASH_ACT``
2341 n/a ``ETHTOOL_MSG_PHY_GET``
2301 =================================== =====================================2342 =================================== =====================================
include/uapi/linux/ethtool_netlink.h+19−0
@@ -58,6 +58,7 @@ enum { · 57 unmodified lines
58 ETHTOOL_MSG_MM_GET,58 ETHTOOL_MSG_MM_GET,
59 ETHTOOL_MSG_MM_SET,59 ETHTOOL_MSG_MM_SET,
60 ETHTOOL_MSG_MODULE_FW_FLASH_ACT,60 ETHTOOL_MSG_MODULE_FW_FLASH_ACT,
61 ETHTOOL_MSG_PHY_GET,
61 62
62 /* add new constants above here */63 /* add new constants above here */
63 __ETHTOOL_MSG_USER_CNT,64 __ETHTOOL_MSG_USER_CNT,
@@ -111,6 +112,8 @@ enum { · 47 unmodified lines
111 ETHTOOL_MSG_MM_GET_REPLY,112 ETHTOOL_MSG_MM_GET_REPLY,
112 ETHTOOL_MSG_MM_NTF,113 ETHTOOL_MSG_MM_NTF,
113 ETHTOOL_MSG_MODULE_FW_FLASH_NTF,114 ETHTOOL_MSG_MODULE_FW_FLASH_NTF,
115 ETHTOOL_MSG_PHY_GET_REPLY,
116 ETHTOOL_MSG_PHY_NTF,
114 117
115 /* add new constants above here */118 /* add new constants above here */
116 __ETHTOOL_MSG_KERNEL_CNT,119 __ETHTOOL_MSG_KERNEL_CNT,
@@ -1055,6 +1058,22 @@ enum { · 938 unmodified lines
1055 ETHTOOL_A_MODULE_FW_FLASH_MAX = (__ETHTOOL_A_MODULE_FW_FLASH_CNT - 1)1058 ETHTOOL_A_MODULE_FW_FLASH_MAX = (__ETHTOOL_A_MODULE_FW_FLASH_CNT - 1)
1056};1059};
1057 1060
1061enum {
1062 ETHTOOL_A_PHY_UNSPEC,
1063 ETHTOOL_A_PHY_HEADER, /* nest - _A_HEADER_* */
1064 ETHTOOL_A_PHY_INDEX, /* u32 */
1065 ETHTOOL_A_PHY_DRVNAME, /* string */
1066 ETHTOOL_A_PHY_NAME, /* string */
1067 ETHTOOL_A_PHY_UPSTREAM_TYPE, /* u32 */
1068 ETHTOOL_A_PHY_UPSTREAM_INDEX, /* u32 */
1069 ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, /* string */
1070 ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, /* string */
1071
1072 /* add new constants above here */
1073 __ETHTOOL_A_PHY_CNT,
1074 ETHTOOL_A_PHY_MAX = (__ETHTOOL_A_PHY_CNT - 1)
1075};
1076
1058/* generic netlink info */1077/* generic netlink info */
1059#define ETHTOOL_GENL_NAME "ethtool"1078#define ETHTOOL_GENL_NAME "ethtool"
1060#define ETHTOOL_GENL_VERSION 11079#define ETHTOOL_GENL_VERSION 1
net/ethtool/Makefile+2−1
@@ -8,4 +8,5 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \ · 7 unmodified lines
8 linkstate.o debug.o wol.o features.o privflags.o rings.o \8 linkstate.o debug.o wol.o features.o privflags.o rings.o \
9 channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \9 channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
10 tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \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.o11 module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o \
12 phy.o
net/ethtool/netlink.c+9−0
@@ -1234,6 +1234,15 @@ static const struct genl_ops ethtool_genl_ops[] = { · 1233 unmodified lines
1234 .policy = ethnl_module_fw_flash_act_policy,1234 .policy = ethnl_module_fw_flash_act_policy,
1235 .maxattr = ARRAY_SIZE(ethnl_module_fw_flash_act_policy) - 1,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
1239static const struct genl_multicast_group ethtool_nl_mcgrps[] = {1248static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
net/ethtool/netlink.h+5−0
@@ -484,6 +484,7 @@ extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADE · 483 unmodified lines
484extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1];484extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1];
485extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1];485extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1];
486extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1];486extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1];
487extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1];
487 488
488int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);489int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
489int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);490int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);
@@ -494,6 +495,10 @@ int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb); · 4 unmodified lines
494int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info);495int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info);
495int ethnl_rss_dump_start(struct netlink_callback *cb);496int ethnl_rss_dump_start(struct netlink_callback *cb);
496int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);497int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
498int ethnl_phy_start(struct netlink_callback *cb);
499int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info);
500int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
501int ethnl_phy_done(struct netlink_callback *cb);
497 502
498extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];503extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
499extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];504extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
net/ethtool/phy.c+308−0
@@ -0,0 +1,308 @@
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
13struct 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
21const 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 */
26static ssize_t
27ethnl_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
72static int
73ethnl_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
120static 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
144int 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
194err_free_msg:
195 nlmsg_free(rskb);
196err_unlock_rtnl:
197 rtnl_unlock();
198 ethnl_parse_header_dev_put(&req_info.base);
199 return ret;
200}
201
202struct ethnl_phy_dump_ctx {
203 struct phy_req_info *phy_req_info;
204 unsigned long ifindex;
205 unsigned long phy_index;
206};
207
208int 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
233int 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
245static 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
285int 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}