Merge tag 'mm-hotfixes-stable-2025-07-11-16-16' of git://git.kernel.org/pub/scm/linux...
[linux-block.git] / net / ethtool / cabletest.c
CommitLineData
11ca3c42
AL
1// SPDX-License-Identifier: GPL-2.0-only
2
3#include <linux/phy.h>
1dd3f212 4#include <linux/ethtool_netlink.h>
8ef890df 5#include <net/netdev_lock.h>
11ca3c42
AL
6#include "netlink.h"
7#include "common.h"
8
f2bc8ad3
AL
9/* 802.3 standard allows 100 meters for BaseT cables. However longer
10 * cables might work, depending on the quality of the cables and the
11 * PHY. So allow testing for up to 150 meters.
12 */
13#define MAX_CABLE_LENGTH_CM (150 * 100)
11ca3c42 14
ff419afa 15const struct nla_policy ethnl_cable_test_act_policy[] = {
329d9c33 16 [ETHTOOL_A_CABLE_TEST_HEADER] =
3688ff30 17 NLA_POLICY_NESTED(ethnl_header_policy_phy),
11ca3c42
AL
18};
19
1a644de2 20static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
9896a457
AL
21{
22 struct sk_buff *skb;
23 int err = -ENOMEM;
24 void *ehdr;
25
26 skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
27 if (!skb)
28 goto out;
29
1a644de2 30 ehdr = ethnl_bcastmsg_put(skb, cmd);
9896a457
AL
31 if (!ehdr) {
32 err = -EMSGSIZE;
33 goto out;
34 }
35
36 err = ethnl_fill_reply_header(skb, phydev->attached_dev,
37 ETHTOOL_A_CABLE_TEST_NTF_HEADER);
38 if (err)
39 goto out;
40
41 err = nla_put_u8(skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
42 ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
43 if (err)
44 goto out;
45
46 genlmsg_end(skb, ehdr);
47
48 return ethnl_multicast(skb, phydev->attached_dev);
49
50out:
51 nlmsg_free(skb);
52 phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
53
54 return err;
55}
56
11ca3c42
AL
57int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
58{
11ca3c42 59 struct ethnl_req_info req_info = {};
f3631ab0 60 const struct ethtool_phy_ops *ops;
5028588b 61 struct nlattr **tb = info->attrs;
3688ff30 62 struct phy_device *phydev;
11ca3c42
AL
63 struct net_device *dev;
64 int ret;
65
11ca3c42
AL
66 ret = ethnl_parse_header_dev_get(&req_info,
67 tb[ETHTOOL_A_CABLE_TEST_HEADER],
68 genl_info_net(info), info->extack,
69 true);
70 if (ret < 0)
71 return ret;
72
73 dev = req_info.dev;
3688ff30
MC
74
75 rtnl_lock();
2bcf4772 76 netdev_lock_ops(dev);
637399bf
MC
77 phydev = ethnl_req_get_phydev(&req_info, tb,
78 ETHTOOL_A_CABLE_TEST_HEADER,
3688ff30
MC
79 info->extack);
80 if (IS_ERR_OR_NULL(phydev)) {
11ca3c42 81 ret = -EOPNOTSUPP;
2bcf4772 82 goto out_unlock;
11ca3c42
AL
83 }
84
f3631ab0
FF
85 ops = ethtool_phy_ops;
86 if (!ops || !ops->start_cable_test) {
87 ret = -EOPNOTSUPP;
2bcf4772 88 goto out_unlock;
f3631ab0
FF
89 }
90
11ca3c42
AL
91 ret = ethnl_ops_begin(dev);
92 if (ret < 0)
2bcf4772 93 goto out_unlock;
11ca3c42 94
3688ff30 95 ret = ops->start_cable_test(phydev, info->extack);
11ca3c42
AL
96
97 ethnl_ops_complete(dev);
9896a457
AL
98
99 if (!ret)
3688ff30 100 ethnl_cable_test_started(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);
9896a457 101
2bcf4772
JK
102out_unlock:
103 netdev_unlock_ops(dev);
11ca3c42 104 rtnl_unlock();
34ac17ec 105 ethnl_parse_header_dev_put(&req_info);
11ca3c42
AL
106 return ret;
107}
1dd3f212 108
1a644de2 109int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
1dd3f212
AL
110{
111 int err = -ENOMEM;
112
6b4a0fc1
AL
113 /* One TDR sample occupies 20 bytes. For a 150 meter cable,
114 * with four pairs, around 12K is needed.
115 */
116 phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
1dd3f212
AL
117 if (!phydev->skb)
118 goto out;
119
1a644de2 120 phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
1dd3f212
AL
121 if (!phydev->ehdr) {
122 err = -EMSGSIZE;
123 goto out;
124 }
125
126 err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
127 ETHTOOL_A_CABLE_TEST_NTF_HEADER);
128 if (err)
129 goto out;
130
131 err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
132 ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
133 if (err)
134 goto out;
135
136 phydev->nest = nla_nest_start(phydev->skb,
137 ETHTOOL_A_CABLE_TEST_NTF_NEST);
1e2dc145
AL
138 if (!phydev->nest) {
139 err = -EMSGSIZE;
1dd3f212 140 goto out;
1e2dc145 141 }
1dd3f212
AL
142
143 return 0;
144
145out:
146 nlmsg_free(phydev->skb);
1e2dc145 147 phydev->skb = NULL;
1dd3f212
AL
148 return err;
149}
150EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
151
152void ethnl_cable_test_free(struct phy_device *phydev)
153{
154 nlmsg_free(phydev->skb);
1e2dc145 155 phydev->skb = NULL;
1dd3f212
AL
156}
157EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
158
159void ethnl_cable_test_finished(struct phy_device *phydev)
160{
161 nla_nest_end(phydev->skb, phydev->nest);
162
163 genlmsg_end(phydev->skb, phydev->ehdr);
164
165 ethnl_multicast(phydev->skb, phydev->attached_dev);
166}
167EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
1e2dc145 168
4715d87e
OR
169int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair,
170 u8 result, u32 src)
1e2dc145
AL
171{
172 struct nlattr *nest;
173 int ret = -EMSGSIZE;
174
175 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_NEST_RESULT);
176 if (!nest)
177 return -EMSGSIZE;
178
179 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_PAIR, pair))
180 goto err;
181 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
182 goto err;
4715d87e
OR
183 if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
184 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_RESULT_SRC, src))
185 goto err;
186 }
1e2dc145
AL
187
188 nla_nest_end(phydev->skb, nest);
189 return 0;
190
191err:
192 nla_nest_cancel(phydev->skb, nest);
193 return ret;
194}
4715d87e 195EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
1e2dc145 196
4715d87e
OR
197int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair,
198 u32 cm, u32 src)
1e2dc145
AL
199{
200 struct nlattr *nest;
201 int ret = -EMSGSIZE;
202
203 nest = nla_nest_start(phydev->skb,
204 ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
205 if (!nest)
206 return -EMSGSIZE;
207
208 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
209 goto err;
210 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
211 goto err;
4715d87e
OR
212 if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
213 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_SRC,
214 src))
215 goto err;
216 }
1e2dc145
AL
217
218 nla_nest_end(phydev->skb, nest);
219 return 0;
220
221err:
222 nla_nest_cancel(phydev->skb, nest);
223 return ret;
224}
4715d87e 225EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
1a644de2 226
ff419afa 227static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
f2bc8ad3
AL
228 [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 },
229 [ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST] = { .type = NLA_U32 },
230 [ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP] = { .type = NLA_U32 },
231 [ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR] = { .type = NLA_U8 },
232};
233
ff419afa 234const struct nla_policy ethnl_cable_test_tdr_act_policy[] = {
329d9c33 235 [ETHTOOL_A_CABLE_TEST_TDR_HEADER] =
3688ff30 236 NLA_POLICY_NESTED(ethnl_header_policy_phy),
f2bc8ad3 237 [ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED },
1a644de2
AL
238};
239
f2bc8ad3 240/* CABLE_TEST_TDR_ACT */
fd55199d
AL
241static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
242 struct genl_info *info,
243 struct phy_tdr_config *cfg)
f2bc8ad3 244{
ff419afa 245 struct nlattr *tb[ARRAY_SIZE(cable_test_tdr_act_cfg_policy)];
f2bc8ad3
AL
246 int ret;
247
4b973f49
AL
248 cfg->first = 100;
249 cfg->step = 100;
250 cfg->last = MAX_CABLE_LENGTH_CM;
251 cfg->pair = PHY_PAIR_ALL;
252
253 if (!nest)
254 return 0;
255
ff419afa
JK
256 ret = nla_parse_nested(tb,
257 ARRAY_SIZE(cable_test_tdr_act_cfg_policy) - 1,
258 nest, cable_test_tdr_act_cfg_policy,
259 info->extack);
f2bc8ad3
AL
260 if (ret < 0)
261 return ret;
262
263 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
264 cfg->first = nla_get_u32(
265 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
4b973f49 266
f2bc8ad3
AL
267 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
268 cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
f2bc8ad3
AL
269
270 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
271 cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
f2bc8ad3
AL
272
273 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
274 cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
275 if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
276 NL_SET_ERR_MSG_ATTR(
277 info->extack,
278 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
279 "invalid pair parameter");
280 return -EINVAL;
281 }
f2bc8ad3
AL
282 }
283
284 if (cfg->first > MAX_CABLE_LENGTH_CM) {
285 NL_SET_ERR_MSG_ATTR(info->extack,
286 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
287 "invalid first parameter");
288 return -EINVAL;
289 }
290
291 if (cfg->last > MAX_CABLE_LENGTH_CM) {
292 NL_SET_ERR_MSG_ATTR(info->extack,
293 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
294 "invalid last parameter");
295 return -EINVAL;
296 }
297
298 if (cfg->first > cfg->last) {
299 NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
300 return -EINVAL;
301 }
302
303 if (!cfg->step) {
304 NL_SET_ERR_MSG_ATTR(info->extack,
305 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
306 "invalid step parameter");
307 return -EINVAL;
308 }
309
310 if (cfg->step > (cfg->last - cfg->first)) {
311 NL_SET_ERR_MSG_ATTR(info->extack,
312 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
313 "step parameter too big");
314 return -EINVAL;
315 }
316
317 return 0;
318}
319
1a644de2
AL
320int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
321{
1a644de2 322 struct ethnl_req_info req_info = {};
f3631ab0 323 const struct ethtool_phy_ops *ops;
5028588b 324 struct nlattr **tb = info->attrs;
3688ff30 325 struct phy_device *phydev;
f2bc8ad3 326 struct phy_tdr_config cfg;
1a644de2
AL
327 struct net_device *dev;
328 int ret;
329
1a644de2
AL
330 ret = ethnl_parse_header_dev_get(&req_info,
331 tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
332 genl_info_net(info), info->extack,
333 true);
334 if (ret < 0)
335 return ret;
336
337 dev = req_info.dev;
1a644de2 338
f2bc8ad3
AL
339 ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
340 info, &cfg);
341 if (ret)
342 goto out_dev_put;
343
1a644de2 344 rtnl_lock();
2bcf4772 345 netdev_lock_ops(dev);
637399bf
MC
346 phydev = ethnl_req_get_phydev(&req_info, tb,
347 ETHTOOL_A_CABLE_TEST_TDR_HEADER,
3688ff30 348 info->extack);
ad78337c 349 if (IS_ERR_OR_NULL(phydev)) {
3688ff30 350 ret = -EOPNOTSUPP;
2bcf4772 351 goto out_unlock;
3688ff30
MC
352 }
353
f3631ab0
FF
354 ops = ethtool_phy_ops;
355 if (!ops || !ops->start_cable_test_tdr) {
356 ret = -EOPNOTSUPP;
2bcf4772 357 goto out_unlock;
f3631ab0
FF
358 }
359
1a644de2
AL
360 ret = ethnl_ops_begin(dev);
361 if (ret < 0)
2bcf4772 362 goto out_unlock;
1a644de2 363
3688ff30 364 ret = ops->start_cable_test_tdr(phydev, info->extack, &cfg);
1a644de2
AL
365
366 ethnl_ops_complete(dev);
367
368 if (!ret)
3688ff30 369 ethnl_cable_test_started(phydev,
1a644de2
AL
370 ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
371
2bcf4772
JK
372out_unlock:
373 netdev_unlock_ops(dev);
1a644de2
AL
374 rtnl_unlock();
375out_dev_put:
34ac17ec 376 ethnl_parse_header_dev_put(&req_info);
1a644de2
AL
377 return ret;
378}
aa246499 379
6b4a0fc1
AL
380int ethnl_cable_test_amplitude(struct phy_device *phydev,
381 u8 pair, s16 mV)
382{
383 struct nlattr *nest;
384 int ret = -EMSGSIZE;
385
386 nest = nla_nest_start(phydev->skb,
387 ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
388 if (!nest)
389 return -EMSGSIZE;
390
391 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
392 goto err;
393 if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
394 goto err;
395
396 nla_nest_end(phydev->skb, nest);
397 return 0;
398
399err:
400 nla_nest_cancel(phydev->skb, nest);
401 return ret;
402}
403EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
404
405int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
406{
407 struct nlattr *nest;
408 int ret = -EMSGSIZE;
409
410 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
411 if (!nest)
412 return -EMSGSIZE;
413
414 if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
415 goto err;
416
417 nla_nest_end(phydev->skb, nest);
418 return 0;
419
420err:
421 nla_nest_cancel(phydev->skb, nest);
422 return ret;
423}
424EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
425
426int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
427 u32 step)
428{
429 struct nlattr *nest;
430 int ret = -EMSGSIZE;
431
432 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
433 if (!nest)
434 return -EMSGSIZE;
435
436 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
437 first))
438 goto err;
439
440 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
441 goto err;
442
443 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
444 goto err;
445
446 nla_nest_end(phydev->skb, nest);
447 return 0;
448
449err:
450 nla_nest_cancel(phydev->skb, nest);
451 return ret;
452}
453EXPORT_SYMBOL_GPL(ethnl_cable_test_step);