xref: /openbmc/linux/net/ethtool/cabletest.c (revision f3631ab0)
111ca3c42SAndrew Lunn // SPDX-License-Identifier: GPL-2.0-only
211ca3c42SAndrew Lunn 
311ca3c42SAndrew Lunn #include <linux/phy.h>
41dd3f212SAndrew Lunn #include <linux/ethtool_netlink.h>
511ca3c42SAndrew Lunn #include "netlink.h"
611ca3c42SAndrew Lunn #include "common.h"
711ca3c42SAndrew Lunn 
8f2bc8ad3SAndrew Lunn /* 802.3 standard allows 100 meters for BaseT cables. However longer
9f2bc8ad3SAndrew Lunn  * cables might work, depending on the quality of the cables and the
10f2bc8ad3SAndrew Lunn  * PHY. So allow testing for up to 150 meters.
11f2bc8ad3SAndrew Lunn  */
12f2bc8ad3SAndrew Lunn #define MAX_CABLE_LENGTH_CM (150 * 100)
1311ca3c42SAndrew Lunn 
1411ca3c42SAndrew Lunn static const struct nla_policy
1511ca3c42SAndrew Lunn cable_test_act_policy[ETHTOOL_A_CABLE_TEST_MAX + 1] = {
1611ca3c42SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_UNSPEC]		= { .type = NLA_REJECT },
1711ca3c42SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_HEADER]		= { .type = NLA_NESTED },
1811ca3c42SAndrew Lunn };
1911ca3c42SAndrew Lunn 
201a644de2SAndrew Lunn static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
219896a457SAndrew Lunn {
229896a457SAndrew Lunn 	struct sk_buff *skb;
239896a457SAndrew Lunn 	int err = -ENOMEM;
249896a457SAndrew Lunn 	void *ehdr;
259896a457SAndrew Lunn 
269896a457SAndrew Lunn 	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
279896a457SAndrew Lunn 	if (!skb)
289896a457SAndrew Lunn 		goto out;
299896a457SAndrew Lunn 
301a644de2SAndrew Lunn 	ehdr = ethnl_bcastmsg_put(skb, cmd);
319896a457SAndrew Lunn 	if (!ehdr) {
329896a457SAndrew Lunn 		err = -EMSGSIZE;
339896a457SAndrew Lunn 		goto out;
349896a457SAndrew Lunn 	}
359896a457SAndrew Lunn 
369896a457SAndrew Lunn 	err = ethnl_fill_reply_header(skb, phydev->attached_dev,
379896a457SAndrew Lunn 				      ETHTOOL_A_CABLE_TEST_NTF_HEADER);
389896a457SAndrew Lunn 	if (err)
399896a457SAndrew Lunn 		goto out;
409896a457SAndrew Lunn 
419896a457SAndrew Lunn 	err = nla_put_u8(skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
429896a457SAndrew Lunn 			 ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
439896a457SAndrew Lunn 	if (err)
449896a457SAndrew Lunn 		goto out;
459896a457SAndrew Lunn 
469896a457SAndrew Lunn 	genlmsg_end(skb, ehdr);
479896a457SAndrew Lunn 
489896a457SAndrew Lunn 	return ethnl_multicast(skb, phydev->attached_dev);
499896a457SAndrew Lunn 
509896a457SAndrew Lunn out:
519896a457SAndrew Lunn 	nlmsg_free(skb);
529896a457SAndrew Lunn 	phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
539896a457SAndrew Lunn 
549896a457SAndrew Lunn 	return err;
559896a457SAndrew Lunn }
569896a457SAndrew Lunn 
5711ca3c42SAndrew Lunn int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
5811ca3c42SAndrew Lunn {
5911ca3c42SAndrew Lunn 	struct nlattr *tb[ETHTOOL_A_CABLE_TEST_MAX + 1];
6011ca3c42SAndrew Lunn 	struct ethnl_req_info req_info = {};
61f3631ab0SFlorian Fainelli 	const struct ethtool_phy_ops *ops;
6211ca3c42SAndrew Lunn 	struct net_device *dev;
6311ca3c42SAndrew Lunn 	int ret;
6411ca3c42SAndrew Lunn 
6511ca3c42SAndrew Lunn 	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
6611ca3c42SAndrew Lunn 			  ETHTOOL_A_CABLE_TEST_MAX,
6711ca3c42SAndrew Lunn 			  cable_test_act_policy, info->extack);
6811ca3c42SAndrew Lunn 	if (ret < 0)
6911ca3c42SAndrew Lunn 		return ret;
7011ca3c42SAndrew Lunn 
7111ca3c42SAndrew Lunn 	ret = ethnl_parse_header_dev_get(&req_info,
7211ca3c42SAndrew Lunn 					 tb[ETHTOOL_A_CABLE_TEST_HEADER],
7311ca3c42SAndrew Lunn 					 genl_info_net(info), info->extack,
7411ca3c42SAndrew Lunn 					 true);
7511ca3c42SAndrew Lunn 	if (ret < 0)
7611ca3c42SAndrew Lunn 		return ret;
7711ca3c42SAndrew Lunn 
7811ca3c42SAndrew Lunn 	dev = req_info.dev;
7911ca3c42SAndrew Lunn 	if (!dev->phydev) {
8011ca3c42SAndrew Lunn 		ret = -EOPNOTSUPP;
8111ca3c42SAndrew Lunn 		goto out_dev_put;
8211ca3c42SAndrew Lunn 	}
8311ca3c42SAndrew Lunn 
8411ca3c42SAndrew Lunn 	rtnl_lock();
85f3631ab0SFlorian Fainelli 	ops = ethtool_phy_ops;
86f3631ab0SFlorian Fainelli 	if (!ops || !ops->start_cable_test) {
87f3631ab0SFlorian Fainelli 		ret = -EOPNOTSUPP;
88f3631ab0SFlorian Fainelli 		goto out_rtnl;
89f3631ab0SFlorian Fainelli 	}
90f3631ab0SFlorian Fainelli 
9111ca3c42SAndrew Lunn 	ret = ethnl_ops_begin(dev);
9211ca3c42SAndrew Lunn 	if (ret < 0)
9311ca3c42SAndrew Lunn 		goto out_rtnl;
9411ca3c42SAndrew Lunn 
95f3631ab0SFlorian Fainelli 	ret = ops->start_cable_test(dev->phydev, info->extack);
9611ca3c42SAndrew Lunn 
9711ca3c42SAndrew Lunn 	ethnl_ops_complete(dev);
989896a457SAndrew Lunn 
999896a457SAndrew Lunn 	if (!ret)
1001a644de2SAndrew Lunn 		ethnl_cable_test_started(dev->phydev,
1011a644de2SAndrew Lunn 					 ETHTOOL_MSG_CABLE_TEST_NTF);
1029896a457SAndrew Lunn 
10311ca3c42SAndrew Lunn out_rtnl:
10411ca3c42SAndrew Lunn 	rtnl_unlock();
10511ca3c42SAndrew Lunn out_dev_put:
10611ca3c42SAndrew Lunn 	dev_put(dev);
10711ca3c42SAndrew Lunn 	return ret;
10811ca3c42SAndrew Lunn }
1091dd3f212SAndrew Lunn 
1101a644de2SAndrew Lunn int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
1111dd3f212SAndrew Lunn {
1121dd3f212SAndrew Lunn 	int err = -ENOMEM;
1131dd3f212SAndrew Lunn 
1146b4a0fc1SAndrew Lunn 	/* One TDR sample occupies 20 bytes. For a 150 meter cable,
1156b4a0fc1SAndrew Lunn 	 * with four pairs, around 12K is needed.
1166b4a0fc1SAndrew Lunn 	 */
1176b4a0fc1SAndrew Lunn 	phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
1181dd3f212SAndrew Lunn 	if (!phydev->skb)
1191dd3f212SAndrew Lunn 		goto out;
1201dd3f212SAndrew Lunn 
1211a644de2SAndrew Lunn 	phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
1221dd3f212SAndrew Lunn 	if (!phydev->ehdr) {
1231dd3f212SAndrew Lunn 		err = -EMSGSIZE;
1241dd3f212SAndrew Lunn 		goto out;
1251dd3f212SAndrew Lunn 	}
1261dd3f212SAndrew Lunn 
1271dd3f212SAndrew Lunn 	err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
1281dd3f212SAndrew Lunn 				      ETHTOOL_A_CABLE_TEST_NTF_HEADER);
1291dd3f212SAndrew Lunn 	if (err)
1301dd3f212SAndrew Lunn 		goto out;
1311dd3f212SAndrew Lunn 
1321dd3f212SAndrew Lunn 	err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
1331dd3f212SAndrew Lunn 			 ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
1341dd3f212SAndrew Lunn 	if (err)
1351dd3f212SAndrew Lunn 		goto out;
1361dd3f212SAndrew Lunn 
1371dd3f212SAndrew Lunn 	phydev->nest = nla_nest_start(phydev->skb,
1381dd3f212SAndrew Lunn 				      ETHTOOL_A_CABLE_TEST_NTF_NEST);
1391e2dc145SAndrew Lunn 	if (!phydev->nest) {
1401e2dc145SAndrew Lunn 		err = -EMSGSIZE;
1411dd3f212SAndrew Lunn 		goto out;
1421e2dc145SAndrew Lunn 	}
1431dd3f212SAndrew Lunn 
1441dd3f212SAndrew Lunn 	return 0;
1451dd3f212SAndrew Lunn 
1461dd3f212SAndrew Lunn out:
1471dd3f212SAndrew Lunn 	nlmsg_free(phydev->skb);
1481e2dc145SAndrew Lunn 	phydev->skb = NULL;
1491dd3f212SAndrew Lunn 	return err;
1501dd3f212SAndrew Lunn }
1511dd3f212SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
1521dd3f212SAndrew Lunn 
1531dd3f212SAndrew Lunn void ethnl_cable_test_free(struct phy_device *phydev)
1541dd3f212SAndrew Lunn {
1551dd3f212SAndrew Lunn 	nlmsg_free(phydev->skb);
1561e2dc145SAndrew Lunn 	phydev->skb = NULL;
1571dd3f212SAndrew Lunn }
1581dd3f212SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
1591dd3f212SAndrew Lunn 
1601dd3f212SAndrew Lunn void ethnl_cable_test_finished(struct phy_device *phydev)
1611dd3f212SAndrew Lunn {
1621dd3f212SAndrew Lunn 	nla_nest_end(phydev->skb, phydev->nest);
1631dd3f212SAndrew Lunn 
1641dd3f212SAndrew Lunn 	genlmsg_end(phydev->skb, phydev->ehdr);
1651dd3f212SAndrew Lunn 
1661dd3f212SAndrew Lunn 	ethnl_multicast(phydev->skb, phydev->attached_dev);
1671dd3f212SAndrew Lunn }
1681dd3f212SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
1691e2dc145SAndrew Lunn 
1701e2dc145SAndrew Lunn int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
1711e2dc145SAndrew Lunn {
1721e2dc145SAndrew Lunn 	struct nlattr *nest;
1731e2dc145SAndrew Lunn 	int ret = -EMSGSIZE;
1741e2dc145SAndrew Lunn 
1751e2dc145SAndrew Lunn 	nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_NEST_RESULT);
1761e2dc145SAndrew Lunn 	if (!nest)
1771e2dc145SAndrew Lunn 		return -EMSGSIZE;
1781e2dc145SAndrew Lunn 
1791e2dc145SAndrew Lunn 	if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_PAIR, pair))
1801e2dc145SAndrew Lunn 		goto err;
1811e2dc145SAndrew Lunn 	if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
1821e2dc145SAndrew Lunn 		goto err;
1831e2dc145SAndrew Lunn 
1841e2dc145SAndrew Lunn 	nla_nest_end(phydev->skb, nest);
1851e2dc145SAndrew Lunn 	return 0;
1861e2dc145SAndrew Lunn 
1871e2dc145SAndrew Lunn err:
1881e2dc145SAndrew Lunn 	nla_nest_cancel(phydev->skb, nest);
1891e2dc145SAndrew Lunn 	return ret;
1901e2dc145SAndrew Lunn }
1911e2dc145SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_result);
1921e2dc145SAndrew Lunn 
1931e2dc145SAndrew Lunn int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
1941e2dc145SAndrew Lunn {
1951e2dc145SAndrew Lunn 	struct nlattr *nest;
1961e2dc145SAndrew Lunn 	int ret = -EMSGSIZE;
1971e2dc145SAndrew Lunn 
1981e2dc145SAndrew Lunn 	nest = nla_nest_start(phydev->skb,
1991e2dc145SAndrew Lunn 			      ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
2001e2dc145SAndrew Lunn 	if (!nest)
2011e2dc145SAndrew Lunn 		return -EMSGSIZE;
2021e2dc145SAndrew Lunn 
2031e2dc145SAndrew Lunn 	if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
2041e2dc145SAndrew Lunn 		goto err;
2051e2dc145SAndrew Lunn 	if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
2061e2dc145SAndrew Lunn 		goto err;
2071e2dc145SAndrew Lunn 
2081e2dc145SAndrew Lunn 	nla_nest_end(phydev->skb, nest);
2091e2dc145SAndrew Lunn 	return 0;
2101e2dc145SAndrew Lunn 
2111e2dc145SAndrew Lunn err:
2121e2dc145SAndrew Lunn 	nla_nest_cancel(phydev->skb, nest);
2131e2dc145SAndrew Lunn 	return ret;
2141e2dc145SAndrew Lunn }
2151e2dc145SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length);
2161a644de2SAndrew Lunn 
217f2bc8ad3SAndrew Lunn struct cable_test_tdr_req_info {
218f2bc8ad3SAndrew Lunn 	struct ethnl_req_info		base;
219f2bc8ad3SAndrew Lunn };
220f2bc8ad3SAndrew Lunn 
221f2bc8ad3SAndrew Lunn static const struct nla_policy
222f2bc8ad3SAndrew Lunn cable_test_tdr_act_cfg_policy[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1] = {
223f2bc8ad3SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]	= { .type = NLA_U32 },
224f2bc8ad3SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]	= { .type = NLA_U32 },
225f2bc8ad3SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]	= { .type = NLA_U32 },
226f2bc8ad3SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]	= { .type = NLA_U8 },
227f2bc8ad3SAndrew Lunn };
228f2bc8ad3SAndrew Lunn 
2291a644de2SAndrew Lunn static const struct nla_policy
2301a644de2SAndrew Lunn cable_test_tdr_act_policy[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1] = {
2311a644de2SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_UNSPEC]	= { .type = NLA_REJECT },
2321a644de2SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_HEADER]	= { .type = NLA_NESTED },
233f2bc8ad3SAndrew Lunn 	[ETHTOOL_A_CABLE_TEST_TDR_CFG]		= { .type = NLA_NESTED },
2341a644de2SAndrew Lunn };
2351a644de2SAndrew Lunn 
236f2bc8ad3SAndrew Lunn /* CABLE_TEST_TDR_ACT */
237fd55199dSAndrew Lunn static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
238f2bc8ad3SAndrew Lunn 					struct genl_info *info,
239f2bc8ad3SAndrew Lunn 					struct phy_tdr_config *cfg)
240f2bc8ad3SAndrew Lunn {
241f2bc8ad3SAndrew Lunn 	struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1];
242f2bc8ad3SAndrew Lunn 	int ret;
243f2bc8ad3SAndrew Lunn 
2444b973f49SAndrew Lunn 	cfg->first = 100;
2454b973f49SAndrew Lunn 	cfg->step = 100;
2464b973f49SAndrew Lunn 	cfg->last = MAX_CABLE_LENGTH_CM;
2474b973f49SAndrew Lunn 	cfg->pair = PHY_PAIR_ALL;
2484b973f49SAndrew Lunn 
2494b973f49SAndrew Lunn 	if (!nest)
2504b973f49SAndrew Lunn 		return 0;
2514b973f49SAndrew Lunn 
252f2bc8ad3SAndrew Lunn 	ret = nla_parse_nested(tb, ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX, nest,
253f2bc8ad3SAndrew Lunn 			       cable_test_tdr_act_cfg_policy, info->extack);
254f2bc8ad3SAndrew Lunn 	if (ret < 0)
255f2bc8ad3SAndrew Lunn 		return ret;
256f2bc8ad3SAndrew Lunn 
257f2bc8ad3SAndrew Lunn 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
258f2bc8ad3SAndrew Lunn 		cfg->first = nla_get_u32(
259f2bc8ad3SAndrew Lunn 			tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
2604b973f49SAndrew Lunn 
261f2bc8ad3SAndrew Lunn 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
262f2bc8ad3SAndrew Lunn 		cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
263f2bc8ad3SAndrew Lunn 
264f2bc8ad3SAndrew Lunn 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
265f2bc8ad3SAndrew Lunn 		cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
266f2bc8ad3SAndrew Lunn 
267f2bc8ad3SAndrew Lunn 	if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
268f2bc8ad3SAndrew Lunn 		cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
269f2bc8ad3SAndrew Lunn 		if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
270f2bc8ad3SAndrew Lunn 			NL_SET_ERR_MSG_ATTR(
271f2bc8ad3SAndrew Lunn 				info->extack,
272f2bc8ad3SAndrew Lunn 				tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
273f2bc8ad3SAndrew Lunn 				"invalid pair parameter");
274f2bc8ad3SAndrew Lunn 			return -EINVAL;
275f2bc8ad3SAndrew Lunn 		}
276f2bc8ad3SAndrew Lunn 	}
277f2bc8ad3SAndrew Lunn 
278f2bc8ad3SAndrew Lunn 	if (cfg->first > MAX_CABLE_LENGTH_CM) {
279f2bc8ad3SAndrew Lunn 		NL_SET_ERR_MSG_ATTR(info->extack,
280f2bc8ad3SAndrew Lunn 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
281f2bc8ad3SAndrew Lunn 				    "invalid first parameter");
282f2bc8ad3SAndrew Lunn 		return -EINVAL;
283f2bc8ad3SAndrew Lunn 	}
284f2bc8ad3SAndrew Lunn 
285f2bc8ad3SAndrew Lunn 	if (cfg->last > MAX_CABLE_LENGTH_CM) {
286f2bc8ad3SAndrew Lunn 		NL_SET_ERR_MSG_ATTR(info->extack,
287f2bc8ad3SAndrew Lunn 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
288f2bc8ad3SAndrew Lunn 				    "invalid last parameter");
289f2bc8ad3SAndrew Lunn 		return -EINVAL;
290f2bc8ad3SAndrew Lunn 	}
291f2bc8ad3SAndrew Lunn 
292f2bc8ad3SAndrew Lunn 	if (cfg->first > cfg->last) {
293f2bc8ad3SAndrew Lunn 		NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
294f2bc8ad3SAndrew Lunn 		return -EINVAL;
295f2bc8ad3SAndrew Lunn 	}
296f2bc8ad3SAndrew Lunn 
297f2bc8ad3SAndrew Lunn 	if (!cfg->step) {
298f2bc8ad3SAndrew Lunn 		NL_SET_ERR_MSG_ATTR(info->extack,
299f2bc8ad3SAndrew Lunn 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
300f2bc8ad3SAndrew Lunn 				    "invalid step parameter");
301f2bc8ad3SAndrew Lunn 		return -EINVAL;
302f2bc8ad3SAndrew Lunn 	}
303f2bc8ad3SAndrew Lunn 
304f2bc8ad3SAndrew Lunn 	if (cfg->step > (cfg->last - cfg->first)) {
305f2bc8ad3SAndrew Lunn 		NL_SET_ERR_MSG_ATTR(info->extack,
306f2bc8ad3SAndrew Lunn 				    tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
307f2bc8ad3SAndrew Lunn 				    "step parameter too big");
308f2bc8ad3SAndrew Lunn 		return -EINVAL;
309f2bc8ad3SAndrew Lunn 	}
310f2bc8ad3SAndrew Lunn 
311f2bc8ad3SAndrew Lunn 	return 0;
312f2bc8ad3SAndrew Lunn }
313f2bc8ad3SAndrew Lunn 
3141a644de2SAndrew Lunn int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
3151a644de2SAndrew Lunn {
3161a644de2SAndrew Lunn 	struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1];
3171a644de2SAndrew Lunn 	struct ethnl_req_info req_info = {};
318f3631ab0SFlorian Fainelli 	const struct ethtool_phy_ops *ops;
319f2bc8ad3SAndrew Lunn 	struct phy_tdr_config cfg;
3201a644de2SAndrew Lunn 	struct net_device *dev;
3211a644de2SAndrew Lunn 	int ret;
3221a644de2SAndrew Lunn 
3231a644de2SAndrew Lunn 	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
3241a644de2SAndrew Lunn 			  ETHTOOL_A_CABLE_TEST_TDR_MAX,
3251a644de2SAndrew Lunn 			  cable_test_tdr_act_policy, info->extack);
3261a644de2SAndrew Lunn 	if (ret < 0)
3271a644de2SAndrew Lunn 		return ret;
3281a644de2SAndrew Lunn 
3291a644de2SAndrew Lunn 	ret = ethnl_parse_header_dev_get(&req_info,
3301a644de2SAndrew Lunn 					 tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
3311a644de2SAndrew Lunn 					 genl_info_net(info), info->extack,
3321a644de2SAndrew Lunn 					 true);
3331a644de2SAndrew Lunn 	if (ret < 0)
3341a644de2SAndrew Lunn 		return ret;
3351a644de2SAndrew Lunn 
3361a644de2SAndrew Lunn 	dev = req_info.dev;
3371a644de2SAndrew Lunn 	if (!dev->phydev) {
3381a644de2SAndrew Lunn 		ret = -EOPNOTSUPP;
3391a644de2SAndrew Lunn 		goto out_dev_put;
3401a644de2SAndrew Lunn 	}
3411a644de2SAndrew Lunn 
342f2bc8ad3SAndrew Lunn 	ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
343f2bc8ad3SAndrew Lunn 					   info, &cfg);
344f2bc8ad3SAndrew Lunn 	if (ret)
345f2bc8ad3SAndrew Lunn 		goto out_dev_put;
346f2bc8ad3SAndrew Lunn 
3471a644de2SAndrew Lunn 	rtnl_lock();
348f3631ab0SFlorian Fainelli 	ops = ethtool_phy_ops;
349f3631ab0SFlorian Fainelli 	if (!ops || !ops->start_cable_test_tdr) {
350f3631ab0SFlorian Fainelli 		ret = -EOPNOTSUPP;
351f3631ab0SFlorian Fainelli 		goto out_rtnl;
352f3631ab0SFlorian Fainelli 	}
353f3631ab0SFlorian Fainelli 
3541a644de2SAndrew Lunn 	ret = ethnl_ops_begin(dev);
3551a644de2SAndrew Lunn 	if (ret < 0)
3561a644de2SAndrew Lunn 		goto out_rtnl;
3571a644de2SAndrew Lunn 
358f3631ab0SFlorian Fainelli 	ret = ops->start_cable_test_tdr(dev->phydev, info->extack, &cfg);
3591a644de2SAndrew Lunn 
3601a644de2SAndrew Lunn 	ethnl_ops_complete(dev);
3611a644de2SAndrew Lunn 
3621a644de2SAndrew Lunn 	if (!ret)
3631a644de2SAndrew Lunn 		ethnl_cable_test_started(dev->phydev,
3641a644de2SAndrew Lunn 					 ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
3651a644de2SAndrew Lunn 
3661a644de2SAndrew Lunn out_rtnl:
3671a644de2SAndrew Lunn 	rtnl_unlock();
3681a644de2SAndrew Lunn out_dev_put:
3691a644de2SAndrew Lunn 	dev_put(dev);
3701a644de2SAndrew Lunn 	return ret;
3711a644de2SAndrew Lunn }
3726b4a0fc1SAndrew Lunn 
3736b4a0fc1SAndrew Lunn int ethnl_cable_test_amplitude(struct phy_device *phydev,
3746b4a0fc1SAndrew Lunn 			       u8 pair, s16 mV)
3756b4a0fc1SAndrew Lunn {
3766b4a0fc1SAndrew Lunn 	struct nlattr *nest;
3776b4a0fc1SAndrew Lunn 	int ret = -EMSGSIZE;
3786b4a0fc1SAndrew Lunn 
3796b4a0fc1SAndrew Lunn 	nest = nla_nest_start(phydev->skb,
3806b4a0fc1SAndrew Lunn 			      ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
3816b4a0fc1SAndrew Lunn 	if (!nest)
3826b4a0fc1SAndrew Lunn 		return -EMSGSIZE;
3836b4a0fc1SAndrew Lunn 
3846b4a0fc1SAndrew Lunn 	if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
3856b4a0fc1SAndrew Lunn 		goto err;
3866b4a0fc1SAndrew Lunn 	if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
3876b4a0fc1SAndrew Lunn 		goto err;
3886b4a0fc1SAndrew Lunn 
3896b4a0fc1SAndrew Lunn 	nla_nest_end(phydev->skb, nest);
3906b4a0fc1SAndrew Lunn 	return 0;
3916b4a0fc1SAndrew Lunn 
3926b4a0fc1SAndrew Lunn err:
3936b4a0fc1SAndrew Lunn 	nla_nest_cancel(phydev->skb, nest);
3946b4a0fc1SAndrew Lunn 	return ret;
3956b4a0fc1SAndrew Lunn }
3966b4a0fc1SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
3976b4a0fc1SAndrew Lunn 
3986b4a0fc1SAndrew Lunn int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
3996b4a0fc1SAndrew Lunn {
4006b4a0fc1SAndrew Lunn 	struct nlattr *nest;
4016b4a0fc1SAndrew Lunn 	int ret = -EMSGSIZE;
4026b4a0fc1SAndrew Lunn 
4036b4a0fc1SAndrew Lunn 	nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
4046b4a0fc1SAndrew Lunn 	if (!nest)
4056b4a0fc1SAndrew Lunn 		return -EMSGSIZE;
4066b4a0fc1SAndrew Lunn 
4076b4a0fc1SAndrew Lunn 	if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
4086b4a0fc1SAndrew Lunn 		goto err;
4096b4a0fc1SAndrew Lunn 
4106b4a0fc1SAndrew Lunn 	nla_nest_end(phydev->skb, nest);
4116b4a0fc1SAndrew Lunn 	return 0;
4126b4a0fc1SAndrew Lunn 
4136b4a0fc1SAndrew Lunn err:
4146b4a0fc1SAndrew Lunn 	nla_nest_cancel(phydev->skb, nest);
4156b4a0fc1SAndrew Lunn 	return ret;
4166b4a0fc1SAndrew Lunn }
4176b4a0fc1SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
4186b4a0fc1SAndrew Lunn 
4196b4a0fc1SAndrew Lunn int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
4206b4a0fc1SAndrew Lunn 			  u32 step)
4216b4a0fc1SAndrew Lunn {
4226b4a0fc1SAndrew Lunn 	struct nlattr *nest;
4236b4a0fc1SAndrew Lunn 	int ret = -EMSGSIZE;
4246b4a0fc1SAndrew Lunn 
4256b4a0fc1SAndrew Lunn 	nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
4266b4a0fc1SAndrew Lunn 	if (!nest)
4276b4a0fc1SAndrew Lunn 		return -EMSGSIZE;
4286b4a0fc1SAndrew Lunn 
4296b4a0fc1SAndrew Lunn 	if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
4306b4a0fc1SAndrew Lunn 			first))
4316b4a0fc1SAndrew Lunn 		goto err;
4326b4a0fc1SAndrew Lunn 
4336b4a0fc1SAndrew Lunn 	if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
4346b4a0fc1SAndrew Lunn 		goto err;
4356b4a0fc1SAndrew Lunn 
4366b4a0fc1SAndrew Lunn 	if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
4376b4a0fc1SAndrew Lunn 		goto err;
4386b4a0fc1SAndrew Lunn 
4396b4a0fc1SAndrew Lunn 	nla_nest_end(phydev->skb, nest);
4406b4a0fc1SAndrew Lunn 	return 0;
4416b4a0fc1SAndrew Lunn 
4426b4a0fc1SAndrew Lunn err:
4436b4a0fc1SAndrew Lunn 	nla_nest_cancel(phydev->skb, nest);
4446b4a0fc1SAndrew Lunn 	return ret;
4456b4a0fc1SAndrew Lunn }
4466b4a0fc1SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
447