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