xref: /linux/net/ethtool/cabletest.c (revision 4ce06406958b67fdddcc2e6948237dd6ff6ba112)
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 
16 const struct nla_policy ethnl_cable_test_act_policy[] = {
17 	[ETHTOOL_A_CABLE_TEST_HEADER]		=
18 		NLA_POLICY_NESTED(ethnl_header_policy_phy),
19 };
20 
21 static 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 
51 out:
52 	nlmsg_free(skb);
53 	phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
54 
55 	return err;
56 }
57 
58 int 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 
103 out_unlock:
104 	netdev_unlock_ops(dev);
105 	rtnl_unlock();
106 	ethnl_parse_header_dev_put(&req_info);
107 	return ret;
108 }
109 
110 int 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 
146 out:
147 	nlmsg_free(phydev->skb);
148 	phydev->skb = NULL;
149 	return err;
150 }
151 EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
152 
153 void ethnl_cable_test_free(struct phy_device *phydev)
154 {
155 	nlmsg_free(phydev->skb);
156 	phydev->skb = NULL;
157 }
158 EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
159 
160 void 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 }
168 EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
169 
170 int 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 
192 err:
193 	nla_nest_cancel(phydev->skb, nest);
194 	return ret;
195 }
196 EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
197 
198 int 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 
222 err:
223 	nla_nest_cancel(phydev->skb, nest);
224 	return ret;
225 }
226 EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
227 
228 static 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 
235 const 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 */
242 static 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 
321 int 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 
373 out_unlock:
374 	netdev_unlock_ops(dev);
375 	rtnl_unlock();
376 out_dev_put:
377 	ethnl_parse_header_dev_put(&req_info);
378 	return ret;
379 }
380 
381 int 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 
400 err:
401 	nla_nest_cancel(phydev->skb, nest);
402 	return ret;
403 }
404 EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
405 
406 int 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 
421 err:
422 	nla_nest_cancel(phydev->skb, nest);
423 	return ret;
424 }
425 EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
426 
427 int 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 
450 err:
451 	nla_nest_cancel(phydev->skb, nest);
452 	return ret;
453 }
454 EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
455