1// SPDX-License-Identifier: GPL-2.0-only
2
3#include <linux/phy.h>
4#include <linux/ethtool_netlink.h>
5#include <net/netdev_lock.h>
6#include "netlink.h"
7#include "common.h"
8
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)
14
15const struct nla_policy ethnl_cable_test_act_policy[] = {
16 [ETHTOOL_A_CABLE_TEST_HEADER] =
17 NLA_POLICY_NESTED(ethnl_header_policy_phy),
18};
19
20static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
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
30 ehdr = ethnl_bcastmsg_put(skb, cmd);
31 if (!ehdr) {
32 err = -EMSGSIZE;
33 goto out;
34 }
35
36 err = ethnl_fill_reply_header(skb, dev: phydev->attached_dev,
37 attrtype: ETHTOOL_A_CABLE_TEST_NTF_HEADER);
38 if (err)
39 goto out;
40
41 err = nla_put_u8(skb, attrtype: ETHTOOL_A_CABLE_TEST_NTF_STATUS,
42 value: ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
43 if (err)
44 goto out;
45
46 genlmsg_end(skb, hdr: ehdr);
47
48 return ethnl_multicast(skb, dev: 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
57int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
58{
59 struct ethnl_req_info req_info = {};
60 const struct ethtool_phy_ops *ops;
61 struct nlattr **tb = info->attrs;
62 struct phy_device *phydev;
63 struct net_device *dev;
64 int ret;
65
66 ret = ethnl_parse_header_dev_get(req_info: &req_info,
67 nest: tb[ETHTOOL_A_CABLE_TEST_HEADER],
68 net: genl_info_net(info), extack: info->extack,
69 require_dev: true);
70 if (ret < 0)
71 return ret;
72
73 dev = req_info.dev;
74
75 rtnl_lock();
76 netdev_lock_ops(dev);
77 phydev = ethnl_req_get_phydev(req_info: &req_info, tb,
78 header: ETHTOOL_A_CABLE_TEST_HEADER,
79 extack: info->extack);
80 if (IS_ERR_OR_NULL(ptr: phydev)) {
81 ret = -EOPNOTSUPP;
82 goto out_unlock;
83 }
84
85 ops = ethtool_phy_ops;
86 if (!ops || !ops->start_cable_test) {
87 ret = -EOPNOTSUPP;
88 goto out_unlock;
89 }
90
91 ret = ethnl_ops_begin(dev);
92 if (ret < 0)
93 goto out_unlock;
94
95 ret = ops->start_cable_test(phydev, info->extack);
96
97 ethnl_ops_complete(dev);
98
99 if (!ret)
100 ethnl_cable_test_started(phydev, cmd: ETHTOOL_MSG_CABLE_TEST_NTF);
101
102out_unlock:
103 netdev_unlock_ops(dev);
104 rtnl_unlock();
105 ethnl_parse_header_dev_put(req_info: &req_info);
106 return ret;
107}
108
109int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
110{
111 int err = -ENOMEM;
112
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);
117 if (!phydev->skb)
118 goto out;
119
120 phydev->ehdr = ethnl_bcastmsg_put(skb: phydev->skb, cmd);
121 if (!phydev->ehdr) {
122 err = -EMSGSIZE;
123 goto out;
124 }
125
126 err = ethnl_fill_reply_header(skb: phydev->skb, dev: phydev->attached_dev,
127 attrtype: ETHTOOL_A_CABLE_TEST_NTF_HEADER);
128 if (err)
129 goto out;
130
131 err = nla_put_u8(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_TEST_NTF_STATUS,
132 value: ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
133 if (err)
134 goto out;
135
136 phydev->nest = nla_nest_start(skb: phydev->skb,
137 attrtype: ETHTOOL_A_CABLE_TEST_NTF_NEST);
138 if (!phydev->nest) {
139 err = -EMSGSIZE;
140 goto out;
141 }
142
143 return 0;
144
145out:
146 nlmsg_free(skb: phydev->skb);
147 phydev->skb = NULL;
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(skb: phydev->skb);
155 phydev->skb = NULL;
156}
157EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
158
159void ethnl_cable_test_finished(struct phy_device *phydev)
160{
161 nla_nest_end(skb: phydev->skb, start: phydev->nest);
162
163 genlmsg_end(skb: phydev->skb, hdr: phydev->ehdr);
164
165 ethnl_multicast(skb: phydev->skb, dev: phydev->attached_dev);
166}
167EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
168
169int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair,
170 u8 result, u32 src)
171{
172 struct nlattr *nest;
173 int ret = -EMSGSIZE;
174
175 nest = nla_nest_start(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_NEST_RESULT);
176 if (!nest)
177 return -EMSGSIZE;
178
179 if (nla_put_u8(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_RESULT_PAIR, value: pair))
180 goto err;
181 if (nla_put_u8(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_RESULT_CODE, value: result))
182 goto err;
183 if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
184 if (nla_put_u32(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_RESULT_SRC, value: src))
185 goto err;
186 }
187
188 nla_nest_end(skb: phydev->skb, start: nest);
189 return 0;
190
191err:
192 nla_nest_cancel(skb: phydev->skb, start: nest);
193 return ret;
194}
195EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
196
197int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair,
198 u32 cm, u32 src)
199{
200 struct nlattr *nest;
201 int ret = -EMSGSIZE;
202
203 nest = nla_nest_start(skb: phydev->skb,
204 attrtype: ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
205 if (!nest)
206 return -EMSGSIZE;
207
208 if (nla_put_u8(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, value: pair))
209 goto err;
210 if (nla_put_u32(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_FAULT_LENGTH_CM, value: cm))
211 goto err;
212 if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
213 if (nla_put_u32(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_FAULT_LENGTH_SRC,
214 value: src))
215 goto err;
216 }
217
218 nla_nest_end(skb: phydev->skb, start: nest);
219 return 0;
220
221err:
222 nla_nest_cancel(skb: phydev->skb, start: nest);
223 return ret;
224}
225EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
226
227static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
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
234const struct nla_policy ethnl_cable_test_tdr_act_policy[] = {
235 [ETHTOOL_A_CABLE_TEST_TDR_HEADER] =
236 NLA_POLICY_NESTED(ethnl_header_policy_phy),
237 [ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED },
238};
239
240/* CABLE_TEST_TDR_ACT */
241static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
242 struct genl_info *info,
243 struct phy_tdr_config *cfg)
244{
245 struct nlattr *tb[ARRAY_SIZE(cable_test_tdr_act_cfg_policy)];
246 int ret;
247
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
256 ret = nla_parse_nested(tb,
257 ARRAY_SIZE(cable_test_tdr_act_cfg_policy) - 1,
258 nla: nest, policy: cable_test_tdr_act_cfg_policy,
259 extack: info->extack);
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 nla: tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
266
267 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
268 cfg->last = nla_get_u32(nla: tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
269
270 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
271 cfg->step = nla_get_u32(nla: tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
272
273 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
274 cfg->pair = nla_get_u8(nla: 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 }
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
320int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
321{
322 struct ethnl_req_info req_info = {};
323 const struct ethtool_phy_ops *ops;
324 struct nlattr **tb = info->attrs;
325 struct phy_device *phydev;
326 struct phy_tdr_config cfg;
327 struct net_device *dev;
328 int ret;
329
330 ret = ethnl_parse_header_dev_get(req_info: &req_info,
331 nest: tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
332 net: genl_info_net(info), extack: info->extack,
333 require_dev: true);
334 if (ret < 0)
335 return ret;
336
337 dev = req_info.dev;
338
339 ret = ethnl_act_cable_test_tdr_cfg(nest: tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
340 info, cfg: &cfg);
341 if (ret)
342 goto out_dev_put;
343
344 rtnl_lock();
345 netdev_lock_ops(dev);
346 phydev = ethnl_req_get_phydev(req_info: &req_info, tb,
347 header: ETHTOOL_A_CABLE_TEST_TDR_HEADER,
348 extack: info->extack);
349 if (IS_ERR_OR_NULL(ptr: phydev)) {
350 ret = -EOPNOTSUPP;
351 goto out_unlock;
352 }
353
354 ops = ethtool_phy_ops;
355 if (!ops || !ops->start_cable_test_tdr) {
356 ret = -EOPNOTSUPP;
357 goto out_unlock;
358 }
359
360 ret = ethnl_ops_begin(dev);
361 if (ret < 0)
362 goto out_unlock;
363
364 ret = ops->start_cable_test_tdr(phydev, info->extack, &cfg);
365
366 ethnl_ops_complete(dev);
367
368 if (!ret)
369 ethnl_cable_test_started(phydev,
370 cmd: ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
371
372out_unlock:
373 netdev_unlock_ops(dev);
374 rtnl_unlock();
375out_dev_put:
376 ethnl_parse_header_dev_put(req_info: &req_info);
377 return ret;
378}
379
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(skb: phydev->skb,
387 attrtype: ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
388 if (!nest)
389 return -EMSGSIZE;
390
391 if (nla_put_u8(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_AMPLITUDE_PAIR, value: pair))
392 goto err;
393 if (nla_put_u16(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_AMPLITUDE_mV, value: mV))
394 goto err;
395
396 nla_nest_end(skb: phydev->skb, start: nest);
397 return 0;
398
399err:
400 nla_nest_cancel(skb: phydev->skb, start: 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(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_TDR_NEST_PULSE);
411 if (!nest)
412 return -EMSGSIZE;
413
414 if (nla_put_u16(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_PULSE_mV, value: mV))
415 goto err;
416
417 nla_nest_end(skb: phydev->skb, start: nest);
418 return 0;
419
420err:
421 nla_nest_cancel(skb: phydev->skb, start: 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(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_TDR_NEST_STEP);
433 if (!nest)
434 return -EMSGSIZE;
435
436 if (nla_put_u32(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
437 value: first))
438 goto err;
439
440 if (nla_put_u32(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, value: last))
441 goto err;
442
443 if (nla_put_u32(skb: phydev->skb, attrtype: ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, value: step))
444 goto err;
445
446 nla_nest_end(skb: phydev->skb, start: nest);
447 return 0;
448
449err:
450 nla_nest_cancel(skb: phydev->skb, start: nest);
451 return ret;
452}
453EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
454