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