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
14ff419afaSJakub Kicinski const struct nla_policy ethnl_cable_test_act_policy[] = {
15329d9c33SJakub Kicinski [ETHTOOL_A_CABLE_TEST_HEADER] =
16329d9c33SJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy),
1711ca3c42SAndrew Lunn };
1811ca3c42SAndrew Lunn
ethnl_cable_test_started(struct phy_device * phydev,u8 cmd)191a644de2SAndrew Lunn static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
209896a457SAndrew Lunn {
219896a457SAndrew Lunn struct sk_buff *skb;
229896a457SAndrew Lunn int err = -ENOMEM;
239896a457SAndrew Lunn void *ehdr;
249896a457SAndrew Lunn
259896a457SAndrew Lunn skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
269896a457SAndrew Lunn if (!skb)
279896a457SAndrew Lunn goto out;
289896a457SAndrew Lunn
291a644de2SAndrew Lunn ehdr = ethnl_bcastmsg_put(skb, cmd);
309896a457SAndrew Lunn if (!ehdr) {
319896a457SAndrew Lunn err = -EMSGSIZE;
329896a457SAndrew Lunn goto out;
339896a457SAndrew Lunn }
349896a457SAndrew Lunn
359896a457SAndrew Lunn err = ethnl_fill_reply_header(skb, phydev->attached_dev,
369896a457SAndrew Lunn ETHTOOL_A_CABLE_TEST_NTF_HEADER);
379896a457SAndrew Lunn if (err)
389896a457SAndrew Lunn goto out;
399896a457SAndrew Lunn
409896a457SAndrew Lunn err = nla_put_u8(skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
419896a457SAndrew Lunn ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
429896a457SAndrew Lunn if (err)
439896a457SAndrew Lunn goto out;
449896a457SAndrew Lunn
459896a457SAndrew Lunn genlmsg_end(skb, ehdr);
469896a457SAndrew Lunn
479896a457SAndrew Lunn return ethnl_multicast(skb, phydev->attached_dev);
489896a457SAndrew Lunn
499896a457SAndrew Lunn out:
509896a457SAndrew Lunn nlmsg_free(skb);
519896a457SAndrew Lunn phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
529896a457SAndrew Lunn
539896a457SAndrew Lunn return err;
549896a457SAndrew Lunn }
559896a457SAndrew Lunn
ethnl_act_cable_test(struct sk_buff * skb,struct genl_info * info)5611ca3c42SAndrew Lunn int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
5711ca3c42SAndrew Lunn {
5811ca3c42SAndrew Lunn struct ethnl_req_info req_info = {};
59f3631ab0SFlorian Fainelli const struct ethtool_phy_ops *ops;
605028588bSJakub Kicinski struct nlattr **tb = info->attrs;
6111ca3c42SAndrew Lunn struct net_device *dev;
6211ca3c42SAndrew Lunn int ret;
6311ca3c42SAndrew Lunn
6411ca3c42SAndrew Lunn ret = ethnl_parse_header_dev_get(&req_info,
6511ca3c42SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_HEADER],
6611ca3c42SAndrew Lunn genl_info_net(info), info->extack,
6711ca3c42SAndrew Lunn true);
6811ca3c42SAndrew Lunn if (ret < 0)
6911ca3c42SAndrew Lunn return ret;
7011ca3c42SAndrew Lunn
7111ca3c42SAndrew Lunn dev = req_info.dev;
7211ca3c42SAndrew Lunn if (!dev->phydev) {
7311ca3c42SAndrew Lunn ret = -EOPNOTSUPP;
7411ca3c42SAndrew Lunn goto out_dev_put;
7511ca3c42SAndrew Lunn }
7611ca3c42SAndrew Lunn
7711ca3c42SAndrew Lunn rtnl_lock();
78f3631ab0SFlorian Fainelli ops = ethtool_phy_ops;
79f3631ab0SFlorian Fainelli if (!ops || !ops->start_cable_test) {
80f3631ab0SFlorian Fainelli ret = -EOPNOTSUPP;
81f3631ab0SFlorian Fainelli goto out_rtnl;
82f3631ab0SFlorian Fainelli }
83f3631ab0SFlorian Fainelli
8411ca3c42SAndrew Lunn ret = ethnl_ops_begin(dev);
8511ca3c42SAndrew Lunn if (ret < 0)
8611ca3c42SAndrew Lunn goto out_rtnl;
8711ca3c42SAndrew Lunn
88f3631ab0SFlorian Fainelli ret = ops->start_cable_test(dev->phydev, info->extack);
8911ca3c42SAndrew Lunn
9011ca3c42SAndrew Lunn ethnl_ops_complete(dev);
919896a457SAndrew Lunn
929896a457SAndrew Lunn if (!ret)
931a644de2SAndrew Lunn ethnl_cable_test_started(dev->phydev,
941a644de2SAndrew Lunn ETHTOOL_MSG_CABLE_TEST_NTF);
959896a457SAndrew Lunn
9611ca3c42SAndrew Lunn out_rtnl:
9711ca3c42SAndrew Lunn rtnl_unlock();
9811ca3c42SAndrew Lunn out_dev_put:
99*34ac17ecSEric Dumazet ethnl_parse_header_dev_put(&req_info);
10011ca3c42SAndrew Lunn return ret;
10111ca3c42SAndrew Lunn }
1021dd3f212SAndrew Lunn
ethnl_cable_test_alloc(struct phy_device * phydev,u8 cmd)1031a644de2SAndrew Lunn int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
1041dd3f212SAndrew Lunn {
1051dd3f212SAndrew Lunn int err = -ENOMEM;
1061dd3f212SAndrew Lunn
1076b4a0fc1SAndrew Lunn /* One TDR sample occupies 20 bytes. For a 150 meter cable,
1086b4a0fc1SAndrew Lunn * with four pairs, around 12K is needed.
1096b4a0fc1SAndrew Lunn */
1106b4a0fc1SAndrew Lunn phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
1111dd3f212SAndrew Lunn if (!phydev->skb)
1121dd3f212SAndrew Lunn goto out;
1131dd3f212SAndrew Lunn
1141a644de2SAndrew Lunn phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
1151dd3f212SAndrew Lunn if (!phydev->ehdr) {
1161dd3f212SAndrew Lunn err = -EMSGSIZE;
1171dd3f212SAndrew Lunn goto out;
1181dd3f212SAndrew Lunn }
1191dd3f212SAndrew Lunn
1201dd3f212SAndrew Lunn err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
1211dd3f212SAndrew Lunn ETHTOOL_A_CABLE_TEST_NTF_HEADER);
1221dd3f212SAndrew Lunn if (err)
1231dd3f212SAndrew Lunn goto out;
1241dd3f212SAndrew Lunn
1251dd3f212SAndrew Lunn err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
1261dd3f212SAndrew Lunn ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
1271dd3f212SAndrew Lunn if (err)
1281dd3f212SAndrew Lunn goto out;
1291dd3f212SAndrew Lunn
1301dd3f212SAndrew Lunn phydev->nest = nla_nest_start(phydev->skb,
1311dd3f212SAndrew Lunn ETHTOOL_A_CABLE_TEST_NTF_NEST);
1321e2dc145SAndrew Lunn if (!phydev->nest) {
1331e2dc145SAndrew Lunn err = -EMSGSIZE;
1341dd3f212SAndrew Lunn goto out;
1351e2dc145SAndrew Lunn }
1361dd3f212SAndrew Lunn
1371dd3f212SAndrew Lunn return 0;
1381dd3f212SAndrew Lunn
1391dd3f212SAndrew Lunn out:
1401dd3f212SAndrew Lunn nlmsg_free(phydev->skb);
1411e2dc145SAndrew Lunn phydev->skb = NULL;
1421dd3f212SAndrew Lunn return err;
1431dd3f212SAndrew Lunn }
1441dd3f212SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
1451dd3f212SAndrew Lunn
ethnl_cable_test_free(struct phy_device * phydev)1461dd3f212SAndrew Lunn void ethnl_cable_test_free(struct phy_device *phydev)
1471dd3f212SAndrew Lunn {
1481dd3f212SAndrew Lunn nlmsg_free(phydev->skb);
1491e2dc145SAndrew Lunn phydev->skb = NULL;
1501dd3f212SAndrew Lunn }
1511dd3f212SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
1521dd3f212SAndrew Lunn
ethnl_cable_test_finished(struct phy_device * phydev)1531dd3f212SAndrew Lunn void ethnl_cable_test_finished(struct phy_device *phydev)
1541dd3f212SAndrew Lunn {
1551dd3f212SAndrew Lunn nla_nest_end(phydev->skb, phydev->nest);
1561dd3f212SAndrew Lunn
1571dd3f212SAndrew Lunn genlmsg_end(phydev->skb, phydev->ehdr);
1581dd3f212SAndrew Lunn
1591dd3f212SAndrew Lunn ethnl_multicast(phydev->skb, phydev->attached_dev);
1601dd3f212SAndrew Lunn }
1611dd3f212SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
1621e2dc145SAndrew Lunn
ethnl_cable_test_result(struct phy_device * phydev,u8 pair,u8 result)1631e2dc145SAndrew Lunn int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
1641e2dc145SAndrew Lunn {
1651e2dc145SAndrew Lunn struct nlattr *nest;
1661e2dc145SAndrew Lunn int ret = -EMSGSIZE;
1671e2dc145SAndrew Lunn
1681e2dc145SAndrew Lunn nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_NEST_RESULT);
1691e2dc145SAndrew Lunn if (!nest)
1701e2dc145SAndrew Lunn return -EMSGSIZE;
1711e2dc145SAndrew Lunn
1721e2dc145SAndrew Lunn if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_PAIR, pair))
1731e2dc145SAndrew Lunn goto err;
1741e2dc145SAndrew Lunn if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
1751e2dc145SAndrew Lunn goto err;
1761e2dc145SAndrew Lunn
1771e2dc145SAndrew Lunn nla_nest_end(phydev->skb, nest);
1781e2dc145SAndrew Lunn return 0;
1791e2dc145SAndrew Lunn
1801e2dc145SAndrew Lunn err:
1811e2dc145SAndrew Lunn nla_nest_cancel(phydev->skb, nest);
1821e2dc145SAndrew Lunn return ret;
1831e2dc145SAndrew Lunn }
1841e2dc145SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_result);
1851e2dc145SAndrew Lunn
ethnl_cable_test_fault_length(struct phy_device * phydev,u8 pair,u32 cm)1861e2dc145SAndrew Lunn int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
1871e2dc145SAndrew Lunn {
1881e2dc145SAndrew Lunn struct nlattr *nest;
1891e2dc145SAndrew Lunn int ret = -EMSGSIZE;
1901e2dc145SAndrew Lunn
1911e2dc145SAndrew Lunn nest = nla_nest_start(phydev->skb,
1921e2dc145SAndrew Lunn ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
1931e2dc145SAndrew Lunn if (!nest)
1941e2dc145SAndrew Lunn return -EMSGSIZE;
1951e2dc145SAndrew Lunn
1961e2dc145SAndrew Lunn if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
1971e2dc145SAndrew Lunn goto err;
1981e2dc145SAndrew Lunn if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
1991e2dc145SAndrew Lunn goto err;
2001e2dc145SAndrew Lunn
2011e2dc145SAndrew Lunn nla_nest_end(phydev->skb, nest);
2021e2dc145SAndrew Lunn return 0;
2031e2dc145SAndrew Lunn
2041e2dc145SAndrew Lunn err:
2051e2dc145SAndrew Lunn nla_nest_cancel(phydev->skb, nest);
2061e2dc145SAndrew Lunn return ret;
2071e2dc145SAndrew Lunn }
2081e2dc145SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length);
2091a644de2SAndrew Lunn
210f2bc8ad3SAndrew Lunn struct cable_test_tdr_req_info {
211f2bc8ad3SAndrew Lunn struct ethnl_req_info base;
212f2bc8ad3SAndrew Lunn };
213f2bc8ad3SAndrew Lunn
214ff419afaSJakub Kicinski static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
215f2bc8ad3SAndrew Lunn [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 },
216f2bc8ad3SAndrew Lunn [ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST] = { .type = NLA_U32 },
217f2bc8ad3SAndrew Lunn [ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP] = { .type = NLA_U32 },
218f2bc8ad3SAndrew Lunn [ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR] = { .type = NLA_U8 },
219f2bc8ad3SAndrew Lunn };
220f2bc8ad3SAndrew Lunn
221ff419afaSJakub Kicinski const struct nla_policy ethnl_cable_test_tdr_act_policy[] = {
222329d9c33SJakub Kicinski [ETHTOOL_A_CABLE_TEST_TDR_HEADER] =
223329d9c33SJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy),
224f2bc8ad3SAndrew Lunn [ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED },
2251a644de2SAndrew Lunn };
2261a644de2SAndrew Lunn
227f2bc8ad3SAndrew Lunn /* CABLE_TEST_TDR_ACT */
ethnl_act_cable_test_tdr_cfg(const struct nlattr * nest,struct genl_info * info,struct phy_tdr_config * cfg)228fd55199dSAndrew Lunn static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
229f2bc8ad3SAndrew Lunn struct genl_info *info,
230f2bc8ad3SAndrew Lunn struct phy_tdr_config *cfg)
231f2bc8ad3SAndrew Lunn {
232ff419afaSJakub Kicinski struct nlattr *tb[ARRAY_SIZE(cable_test_tdr_act_cfg_policy)];
233f2bc8ad3SAndrew Lunn int ret;
234f2bc8ad3SAndrew Lunn
2354b973f49SAndrew Lunn cfg->first = 100;
2364b973f49SAndrew Lunn cfg->step = 100;
2374b973f49SAndrew Lunn cfg->last = MAX_CABLE_LENGTH_CM;
2384b973f49SAndrew Lunn cfg->pair = PHY_PAIR_ALL;
2394b973f49SAndrew Lunn
2404b973f49SAndrew Lunn if (!nest)
2414b973f49SAndrew Lunn return 0;
2424b973f49SAndrew Lunn
243ff419afaSJakub Kicinski ret = nla_parse_nested(tb,
244ff419afaSJakub Kicinski ARRAY_SIZE(cable_test_tdr_act_cfg_policy) - 1,
245ff419afaSJakub Kicinski nest, cable_test_tdr_act_cfg_policy,
246ff419afaSJakub Kicinski info->extack);
247f2bc8ad3SAndrew Lunn if (ret < 0)
248f2bc8ad3SAndrew Lunn return ret;
249f2bc8ad3SAndrew Lunn
250f2bc8ad3SAndrew Lunn if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
251f2bc8ad3SAndrew Lunn cfg->first = nla_get_u32(
252f2bc8ad3SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
2534b973f49SAndrew Lunn
254f2bc8ad3SAndrew Lunn if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
255f2bc8ad3SAndrew Lunn cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
256f2bc8ad3SAndrew Lunn
257f2bc8ad3SAndrew Lunn if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
258f2bc8ad3SAndrew Lunn cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
259f2bc8ad3SAndrew Lunn
260f2bc8ad3SAndrew Lunn if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
261f2bc8ad3SAndrew Lunn cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
262f2bc8ad3SAndrew Lunn if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
263f2bc8ad3SAndrew Lunn NL_SET_ERR_MSG_ATTR(
264f2bc8ad3SAndrew Lunn info->extack,
265f2bc8ad3SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
266f2bc8ad3SAndrew Lunn "invalid pair parameter");
267f2bc8ad3SAndrew Lunn return -EINVAL;
268f2bc8ad3SAndrew Lunn }
269f2bc8ad3SAndrew Lunn }
270f2bc8ad3SAndrew Lunn
271f2bc8ad3SAndrew Lunn if (cfg->first > MAX_CABLE_LENGTH_CM) {
272f2bc8ad3SAndrew Lunn NL_SET_ERR_MSG_ATTR(info->extack,
273f2bc8ad3SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
274f2bc8ad3SAndrew Lunn "invalid first parameter");
275f2bc8ad3SAndrew Lunn return -EINVAL;
276f2bc8ad3SAndrew Lunn }
277f2bc8ad3SAndrew Lunn
278f2bc8ad3SAndrew Lunn if (cfg->last > MAX_CABLE_LENGTH_CM) {
279f2bc8ad3SAndrew Lunn NL_SET_ERR_MSG_ATTR(info->extack,
280f2bc8ad3SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
281f2bc8ad3SAndrew Lunn "invalid last parameter");
282f2bc8ad3SAndrew Lunn return -EINVAL;
283f2bc8ad3SAndrew Lunn }
284f2bc8ad3SAndrew Lunn
285f2bc8ad3SAndrew Lunn if (cfg->first > cfg->last) {
286f2bc8ad3SAndrew Lunn NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
287f2bc8ad3SAndrew Lunn return -EINVAL;
288f2bc8ad3SAndrew Lunn }
289f2bc8ad3SAndrew Lunn
290f2bc8ad3SAndrew Lunn if (!cfg->step) {
291f2bc8ad3SAndrew Lunn NL_SET_ERR_MSG_ATTR(info->extack,
292f2bc8ad3SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
293f2bc8ad3SAndrew Lunn "invalid step parameter");
294f2bc8ad3SAndrew Lunn return -EINVAL;
295f2bc8ad3SAndrew Lunn }
296f2bc8ad3SAndrew Lunn
297f2bc8ad3SAndrew Lunn if (cfg->step > (cfg->last - cfg->first)) {
298f2bc8ad3SAndrew Lunn NL_SET_ERR_MSG_ATTR(info->extack,
299f2bc8ad3SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
300f2bc8ad3SAndrew Lunn "step parameter too big");
301f2bc8ad3SAndrew Lunn return -EINVAL;
302f2bc8ad3SAndrew Lunn }
303f2bc8ad3SAndrew Lunn
304f2bc8ad3SAndrew Lunn return 0;
305f2bc8ad3SAndrew Lunn }
306f2bc8ad3SAndrew Lunn
ethnl_act_cable_test_tdr(struct sk_buff * skb,struct genl_info * info)3071a644de2SAndrew Lunn int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
3081a644de2SAndrew Lunn {
3091a644de2SAndrew Lunn struct ethnl_req_info req_info = {};
310f3631ab0SFlorian Fainelli const struct ethtool_phy_ops *ops;
3115028588bSJakub Kicinski struct nlattr **tb = info->attrs;
312f2bc8ad3SAndrew Lunn struct phy_tdr_config cfg;
3131a644de2SAndrew Lunn struct net_device *dev;
3141a644de2SAndrew Lunn int ret;
3151a644de2SAndrew Lunn
3161a644de2SAndrew Lunn ret = ethnl_parse_header_dev_get(&req_info,
3171a644de2SAndrew Lunn tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
3181a644de2SAndrew Lunn genl_info_net(info), info->extack,
3191a644de2SAndrew Lunn true);
3201a644de2SAndrew Lunn if (ret < 0)
3211a644de2SAndrew Lunn return ret;
3221a644de2SAndrew Lunn
3231a644de2SAndrew Lunn dev = req_info.dev;
3241a644de2SAndrew Lunn if (!dev->phydev) {
3251a644de2SAndrew Lunn ret = -EOPNOTSUPP;
3261a644de2SAndrew Lunn goto out_dev_put;
3271a644de2SAndrew Lunn }
3281a644de2SAndrew Lunn
329f2bc8ad3SAndrew Lunn ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
330f2bc8ad3SAndrew Lunn info, &cfg);
331f2bc8ad3SAndrew Lunn if (ret)
332f2bc8ad3SAndrew Lunn goto out_dev_put;
333f2bc8ad3SAndrew Lunn
3341a644de2SAndrew Lunn rtnl_lock();
335f3631ab0SFlorian Fainelli ops = ethtool_phy_ops;
336f3631ab0SFlorian Fainelli if (!ops || !ops->start_cable_test_tdr) {
337f3631ab0SFlorian Fainelli ret = -EOPNOTSUPP;
338f3631ab0SFlorian Fainelli goto out_rtnl;
339f3631ab0SFlorian Fainelli }
340f3631ab0SFlorian Fainelli
3411a644de2SAndrew Lunn ret = ethnl_ops_begin(dev);
3421a644de2SAndrew Lunn if (ret < 0)
3431a644de2SAndrew Lunn goto out_rtnl;
3441a644de2SAndrew Lunn
345f3631ab0SFlorian Fainelli ret = ops->start_cable_test_tdr(dev->phydev, info->extack, &cfg);
3461a644de2SAndrew Lunn
3471a644de2SAndrew Lunn ethnl_ops_complete(dev);
3481a644de2SAndrew Lunn
3491a644de2SAndrew Lunn if (!ret)
3501a644de2SAndrew Lunn ethnl_cable_test_started(dev->phydev,
3511a644de2SAndrew Lunn ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
3521a644de2SAndrew Lunn
3531a644de2SAndrew Lunn out_rtnl:
3541a644de2SAndrew Lunn rtnl_unlock();
3551a644de2SAndrew Lunn out_dev_put:
356*34ac17ecSEric Dumazet ethnl_parse_header_dev_put(&req_info);
3571a644de2SAndrew Lunn return ret;
3581a644de2SAndrew Lunn }
3596b4a0fc1SAndrew Lunn
ethnl_cable_test_amplitude(struct phy_device * phydev,u8 pair,s16 mV)3606b4a0fc1SAndrew Lunn int ethnl_cable_test_amplitude(struct phy_device *phydev,
3616b4a0fc1SAndrew Lunn u8 pair, s16 mV)
3626b4a0fc1SAndrew Lunn {
3636b4a0fc1SAndrew Lunn struct nlattr *nest;
3646b4a0fc1SAndrew Lunn int ret = -EMSGSIZE;
3656b4a0fc1SAndrew Lunn
3666b4a0fc1SAndrew Lunn nest = nla_nest_start(phydev->skb,
3676b4a0fc1SAndrew Lunn ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
3686b4a0fc1SAndrew Lunn if (!nest)
3696b4a0fc1SAndrew Lunn return -EMSGSIZE;
3706b4a0fc1SAndrew Lunn
3716b4a0fc1SAndrew Lunn if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
3726b4a0fc1SAndrew Lunn goto err;
3736b4a0fc1SAndrew Lunn if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
3746b4a0fc1SAndrew Lunn goto err;
3756b4a0fc1SAndrew Lunn
3766b4a0fc1SAndrew Lunn nla_nest_end(phydev->skb, nest);
3776b4a0fc1SAndrew Lunn return 0;
3786b4a0fc1SAndrew Lunn
3796b4a0fc1SAndrew Lunn err:
3806b4a0fc1SAndrew Lunn nla_nest_cancel(phydev->skb, nest);
3816b4a0fc1SAndrew Lunn return ret;
3826b4a0fc1SAndrew Lunn }
3836b4a0fc1SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
3846b4a0fc1SAndrew Lunn
ethnl_cable_test_pulse(struct phy_device * phydev,u16 mV)3856b4a0fc1SAndrew Lunn int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
3866b4a0fc1SAndrew Lunn {
3876b4a0fc1SAndrew Lunn struct nlattr *nest;
3886b4a0fc1SAndrew Lunn int ret = -EMSGSIZE;
3896b4a0fc1SAndrew Lunn
3906b4a0fc1SAndrew Lunn nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
3916b4a0fc1SAndrew Lunn if (!nest)
3926b4a0fc1SAndrew Lunn return -EMSGSIZE;
3936b4a0fc1SAndrew Lunn
3946b4a0fc1SAndrew Lunn if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
3956b4a0fc1SAndrew Lunn goto err;
3966b4a0fc1SAndrew Lunn
3976b4a0fc1SAndrew Lunn nla_nest_end(phydev->skb, nest);
3986b4a0fc1SAndrew Lunn return 0;
3996b4a0fc1SAndrew Lunn
4006b4a0fc1SAndrew Lunn err:
4016b4a0fc1SAndrew Lunn nla_nest_cancel(phydev->skb, nest);
4026b4a0fc1SAndrew Lunn return ret;
4036b4a0fc1SAndrew Lunn }
4046b4a0fc1SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
4056b4a0fc1SAndrew Lunn
ethnl_cable_test_step(struct phy_device * phydev,u32 first,u32 last,u32 step)4066b4a0fc1SAndrew Lunn int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
4076b4a0fc1SAndrew Lunn u32 step)
4086b4a0fc1SAndrew Lunn {
4096b4a0fc1SAndrew Lunn struct nlattr *nest;
4106b4a0fc1SAndrew Lunn int ret = -EMSGSIZE;
4116b4a0fc1SAndrew Lunn
4126b4a0fc1SAndrew Lunn nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
4136b4a0fc1SAndrew Lunn if (!nest)
4146b4a0fc1SAndrew Lunn return -EMSGSIZE;
4156b4a0fc1SAndrew Lunn
4166b4a0fc1SAndrew Lunn if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
4176b4a0fc1SAndrew Lunn first))
4186b4a0fc1SAndrew Lunn goto err;
4196b4a0fc1SAndrew Lunn
4206b4a0fc1SAndrew Lunn if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
4216b4a0fc1SAndrew Lunn goto err;
4226b4a0fc1SAndrew Lunn
4236b4a0fc1SAndrew Lunn if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
4246b4a0fc1SAndrew Lunn goto err;
4256b4a0fc1SAndrew Lunn
4266b4a0fc1SAndrew Lunn nla_nest_end(phydev->skb, nest);
4276b4a0fc1SAndrew Lunn return 0;
4286b4a0fc1SAndrew Lunn
4296b4a0fc1SAndrew Lunn err:
4306b4a0fc1SAndrew Lunn nla_nest_cancel(phydev->skb, nest);
4316b4a0fc1SAndrew Lunn return ret;
4326b4a0fc1SAndrew Lunn }
4336b4a0fc1SAndrew Lunn EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
434