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
15 const struct nla_policy ethnl_cable_test_act_policy[] = {
16 [ETHTOOL_A_CABLE_TEST_HEADER] =
17 NLA_POLICY_NESTED(ethnl_header_policy_phy),
18 };
19
ethnl_cable_test_started(struct phy_device * phydev,u8 cmd)20 static 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, 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
50 out:
51 nlmsg_free(skb);
52 phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
53
54 return err;
55 }
56
ethnl_act_cable_test(struct sk_buff * skb,struct genl_info * info)57 int 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,
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;
74
75 rtnl_lock();
76 netdev_lock_ops(dev);
77 phydev = ethnl_req_get_phydev(&req_info, tb,
78 ETHTOOL_A_CABLE_TEST_HEADER,
79 info->extack);
80 if (IS_ERR_OR_NULL(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, ETHTOOL_MSG_CABLE_TEST_NTF);
101
102 out_unlock:
103 netdev_unlock_ops(dev);
104 rtnl_unlock();
105 ethnl_parse_header_dev_put(&req_info);
106 return ret;
107 }
108
ethnl_cable_test_alloc(struct phy_device * phydev,u8 cmd)109 int 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(phydev->skb, cmd);
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);
138 if (!phydev->nest) {
139 err = -EMSGSIZE;
140 goto out;
141 }
142
143 return 0;
144
145 out:
146 nlmsg_free(phydev->skb);
147 phydev->skb = NULL;
148 return err;
149 }
150 EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
151
ethnl_cable_test_free(struct phy_device * phydev)152 void ethnl_cable_test_free(struct phy_device *phydev)
153 {
154 nlmsg_free(phydev->skb);
155 phydev->skb = NULL;
156 }
157 EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
158
ethnl_cable_test_finished(struct phy_device * phydev)159 void 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 }
167 EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
168
ethnl_cable_test_result_with_src(struct phy_device * phydev,u8 pair,u8 result,u32 src)169 int 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(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;
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 }
187
188 nla_nest_end(phydev->skb, nest);
189 return 0;
190
191 err:
192 nla_nest_cancel(phydev->skb, nest);
193 return ret;
194 }
195 EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
196
ethnl_cable_test_fault_length_with_src(struct phy_device * phydev,u8 pair,u32 cm,u32 src)197 int 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(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;
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 }
217
218 nla_nest_end(phydev->skb, nest);
219 return 0;
220
221 err:
222 nla_nest_cancel(phydev->skb, nest);
223 return ret;
224 }
225 EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
226
227 static 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
234 const 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 */
ethnl_act_cable_test_tdr_cfg(const struct nlattr * nest,struct genl_info * info,struct phy_tdr_config * cfg)241 static 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 nest, cable_test_tdr_act_cfg_policy,
259 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 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(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(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(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
ethnl_act_cable_test_tdr(struct sk_buff * skb,struct genl_info * info)320 int 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,
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;
338
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
344 rtnl_lock();
345 netdev_lock_ops(dev);
346 phydev = ethnl_req_get_phydev(&req_info, tb,
347 ETHTOOL_A_CABLE_TEST_TDR_HEADER,
348 info->extack);
349 if (IS_ERR_OR_NULL(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 ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
371
372 out_unlock:
373 netdev_unlock_ops(dev);
374 rtnl_unlock();
375 out_dev_put:
376 ethnl_parse_header_dev_put(&req_info);
377 return ret;
378 }
379
ethnl_cable_test_amplitude(struct phy_device * phydev,u8 pair,s16 mV)380 int 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
399 err:
400 nla_nest_cancel(phydev->skb, nest);
401 return ret;
402 }
403 EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
404
ethnl_cable_test_pulse(struct phy_device * phydev,u16 mV)405 int 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
420 err:
421 nla_nest_cancel(phydev->skb, nest);
422 return ret;
423 }
424 EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
425
ethnl_cable_test_step(struct phy_device * phydev,u32 first,u32 last,u32 step)426 int 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
449 err:
450 nla_nest_cancel(phydev->skb, nest);
451 return ret;
452 }
453 EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
454