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