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