xref: /linux/net/ethtool/cabletest.c (revision 1a9239bb4253f9076b5b4b2a1a4e8d7defd77a95)
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