Linux Audio

Check our new training course

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