xref: /openbmc/linux/net/ethtool/cabletest.c (revision fd55199d)
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 	ret = nla_parse_nested(tb, ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX, nest,
238 			       cable_test_tdr_act_cfg_policy, info->extack);
239 	if (ret < 0)
240 		return ret;
241 
242 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
243 		cfg->first = nla_get_u32(
244 			tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
245 	else
246 		cfg->first = 100;
247 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
248 		cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
249 	else
250 		cfg->last = MAX_CABLE_LENGTH_CM;
251 
252 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
253 		cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
254 	else
255 		cfg->step = 100;
256 
257 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
258 		cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
259 		if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
260 			NL_SET_ERR_MSG_ATTR(
261 				info->extack,
262 				tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
263 				"invalid pair parameter");
264 			return -EINVAL;
265 		}
266 	} else {
267 		cfg->pair = PHY_PAIR_ALL;
268 	}
269 
270 	if (cfg->first > MAX_CABLE_LENGTH_CM) {
271 		NL_SET_ERR_MSG_ATTR(info->extack,
272 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
273 				    "invalid first parameter");
274 		return -EINVAL;
275 	}
276 
277 	if (cfg->last > MAX_CABLE_LENGTH_CM) {
278 		NL_SET_ERR_MSG_ATTR(info->extack,
279 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
280 				    "invalid last parameter");
281 		return -EINVAL;
282 	}
283 
284 	if (cfg->first > cfg->last) {
285 		NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
286 		return -EINVAL;
287 	}
288 
289 	if (!cfg->step) {
290 		NL_SET_ERR_MSG_ATTR(info->extack,
291 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
292 				    "invalid step parameter");
293 		return -EINVAL;
294 	}
295 
296 	if (cfg->step > (cfg->last - cfg->first)) {
297 		NL_SET_ERR_MSG_ATTR(info->extack,
298 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
299 				    "step parameter too big");
300 		return -EINVAL;
301 	}
302 
303 	return 0;
304 }
305 
306 int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
307 {
308 	struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1];
309 	struct ethnl_req_info req_info = {};
310 	struct phy_tdr_config cfg;
311 	struct net_device *dev;
312 	int ret;
313 
314 	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
315 			  ETHTOOL_A_CABLE_TEST_TDR_MAX,
316 			  cable_test_tdr_act_policy, info->extack);
317 	if (ret < 0)
318 		return ret;
319 
320 	ret = ethnl_parse_header_dev_get(&req_info,
321 					 tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
322 					 genl_info_net(info), info->extack,
323 					 true);
324 	if (ret < 0)
325 		return ret;
326 
327 	dev = req_info.dev;
328 	if (!dev->phydev) {
329 		ret = -EOPNOTSUPP;
330 		goto out_dev_put;
331 	}
332 
333 	ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
334 					   info, &cfg);
335 	if (ret)
336 		goto out_dev_put;
337 
338 	rtnl_lock();
339 	ret = ethnl_ops_begin(dev);
340 	if (ret < 0)
341 		goto out_rtnl;
342 
343 	ret = phy_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