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