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