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