File Index
Files and directories
Sign in
FILE

cabletest.c

File preview with syntax highlighting.

linux-6.19.14/net/ethtool/cabletest.c C
  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
 15const struct nla_policy ethnl_cable_test_act_policy[] = {
 16	[ETHTOOL_A_CABLE_TEST_HEADER]		=
 17		NLA_POLICY_NESTED(ethnl_header_policy_phy),
 18};
 19
 20static 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
 50out:
 51	nlmsg_free(skb);
 52	phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
 53
 54	return err;
 55}
 56
 57int 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
102out_unlock:
103	netdev_unlock_ops(dev);
104	rtnl_unlock();
105	ethnl_parse_header_dev_put(&req_info);
106	return ret;
107}
108
109int 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
145out:
146	nlmsg_free(phydev->skb);
147	phydev->skb = NULL;
148	return err;
149}
150EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
151
152void ethnl_cable_test_free(struct phy_device *phydev)
153{
154	nlmsg_free(phydev->skb);
155	phydev->skb = NULL;
156}
157EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
158
159void 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}
167EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
168
169int 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
191err:
192	nla_nest_cancel(phydev->skb, nest);
193	return ret;
194}
195EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
196
197int 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
221err:
222	nla_nest_cancel(phydev->skb, nest);
223	return ret;
224}
225EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
226
227static 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
234const 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 */
241static 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
320int 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
372out_unlock:
373	netdev_unlock_ops(dev);
374	rtnl_unlock();
375out_dev_put:
376	ethnl_parse_header_dev_put(&req_info);
377	return ret;
378}
379
380int 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
399err:
400	nla_nest_cancel(phydev->skb, nest);
401	return ret;
402}
403EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
404
405int 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
420err:
421	nla_nest_cancel(phydev->skb, nest);
422	return ret;
423}
424EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
425
426int 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
449err:
450	nla_nest_cancel(phydev->skb, nest);
451	return ret;
452}
453EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
~/ Read-only file preview.