Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'add-alcd-support-to-cable-testing-interface'

Oleksij Rempel says:

====================
Add ALCD Support to Cable Testing Interface

This patch series introduces support for Active Link Cable Diagnostics
(ALCD) in the ethtool cable testing interface and the DP83TD510 PHY
driver.

Why ALCD?
On a 10BaseT1L interface, TDR (Time Domain Reflectometry) is not
possible if the link partner is active - TDR will fail in these cases
because it requires interrupting the link. Since the link is active, we
already know the cable is functioning, so instead of using TDR, we can
use ALCD.

ALCD lets us measure cable length without disrupting the active link,
which is crucial in environments where network uptime is important. It
provides a way to gather diagnostic data without the need for downtime.

What's in this series:
- Extended the ethtool cable testing interface to specify the source of
diagnostic results (TDR or ALCD).
- Updated the DP83TD510 PHY driver to use ALCD when the link is
active, ensuring we can still get cable length info without dropping the
connection.
====================

Link: https://patch.msgid.link/20240822120703.1393130-1-o.rempel@pengutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+173 -16
+6
Documentation/netlink/specs/ethtool.yaml
··· 667 667 - 668 668 name: code 669 669 type: u8 670 + - 671 + name: src 672 + type: u32 670 673 - 671 674 name: cable-fault-length 672 675 attributes: ··· 678 675 type: u8 679 676 - 680 677 name: cm 678 + type: u32 679 + - 680 + name: src 681 681 type: u32 682 682 - 683 683 name: cable-nest
+5
Documentation/networking/ethtool-netlink.rst
··· 1314 1314 +-+-+-----------------------------------------+--------+---------------------+ 1315 1315 | | | ``ETHTOOL_A_CABLE_RESULTS_CODE`` | u8 | result code | 1316 1316 +-+-+-----------------------------------------+--------+---------------------+ 1317 + | | | ``ETHTOOL_A_CABLE_RESULT_SRC`` | u32 | information source | 1318 + +-+-+-----------------------------------------+--------+---------------------+ 1317 1319 | | ``ETHTOOL_A_CABLE_NEST_FAULT_LENGTH`` | nested | cable length | 1318 1320 +-+-+-----------------------------------------+--------+---------------------+ 1319 1321 | | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR`` | u8 | pair number | 1320 1322 +-+-+-----------------------------------------+--------+---------------------+ 1321 1323 | | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_CM`` | u32 | length in cm | 1322 1324 +-+-+-----------------------------------------+--------+---------------------+ 1325 + | | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_SRC`` | u32 | information source | 1326 + +-+-+-----------------------------------------+--------+---------------------+ 1327 + 1323 1328 1324 1329 CABLE_TEST TDR 1325 1330 ==============
+113 -6
drivers/net/phy/dp83td510.c
··· 58 58 0x0000 /* 24dB =< SNR */ 59 59 }; 60 60 61 + struct dp83td510_priv { 62 + bool alcd_test_active; 63 + }; 64 + 61 65 /* Time Domain Reflectometry (TDR) Functionality of DP83TD510 PHY 62 66 * 63 67 * I assume that this PHY is using a variation of Spread Spectrum Time Domain ··· 172 168 */ 173 169 #define DP83TD510E_UNKN_030E 0x30e 174 170 #define DP83TD510E_030E_VAL 0x2520 171 + 172 + #define DP83TD510E_ALCD_STAT 0xa9f 173 + #define DP83TD510E_ALCD_COMPLETE BIT(15) 174 + #define DP83TD510E_ALCD_CABLE_LENGTH GENMASK(10, 0) 175 175 176 176 static int dp83td510_config_intr(struct phy_device *phydev) 177 177 { ··· 333 325 */ 334 326 static int dp83td510_cable_test_start(struct phy_device *phydev) 335 327 { 328 + struct dp83td510_priv *priv = phydev->priv; 336 329 int ret; 330 + 331 + /* If link partner is active, we won't be able to use TDR, since 332 + * we can't force link partner to be silent. The autonegotiation 333 + * pulses will be too frequent and the TDR sequence will be 334 + * too long. So, TDR will always fail. Since the link is established 335 + * we already know that the cable is working, so we can get some 336 + * extra information line the cable length using ALCD. 337 + */ 338 + if (phydev->link) { 339 + priv->alcd_test_active = true; 340 + return 0; 341 + } 342 + 343 + priv->alcd_test_active = false; 337 344 338 345 ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_CTRL, 339 346 DP83TD510E_CTRL_HW_RESET); ··· 425 402 } 426 403 427 404 /** 428 - * dp83td510_cable_test_get_status - Get the status of the cable test for the 429 - * DP83TD510 PHY. 405 + * dp83td510_cable_test_get_tdr_status - Get the status of the TDR test for the 406 + * DP83TD510 PHY. 430 407 * @phydev: Pointer to the phy_device structure. 431 408 * @finished: Pointer to a boolean that indicates whether the test is finished. 432 409 * ··· 434 411 * 435 412 * Returns: 0 on success or a negative error code on failure. 436 413 */ 437 - static int dp83td510_cable_test_get_status(struct phy_device *phydev, 438 - bool *finished) 414 + static int dp83td510_cable_test_get_tdr_status(struct phy_device *phydev, 415 + bool *finished) 439 416 { 440 417 int ret, stat; 441 - 442 - *finished = false; 443 418 444 419 ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG); 445 420 if (ret < 0) ··· 480 459 return phy_init_hw(phydev); 481 460 } 482 461 462 + /** 463 + * dp83td510_cable_test_get_alcd_status - Get the status of the ALCD test for the 464 + * DP83TD510 PHY. 465 + * @phydev: Pointer to the phy_device structure. 466 + * @finished: Pointer to a boolean that indicates whether the test is finished. 467 + * 468 + * The function sets the @finished flag to true if the test is complete. 469 + * The function reads the cable length and reports it to the user. 470 + * 471 + * Returns: 0 on success or a negative error code on failure. 472 + */ 473 + static int dp83td510_cable_test_get_alcd_status(struct phy_device *phydev, 474 + bool *finished) 475 + { 476 + unsigned int location; 477 + int ret, phy_sts; 478 + 479 + phy_sts = phy_read(phydev, DP83TD510E_PHY_STS); 480 + 481 + if (!(phy_sts & DP83TD510E_LINK_STATUS)) { 482 + /* If the link is down, we can't do any thing usable now */ 483 + ethnl_cable_test_result_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A, 484 + ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC, 485 + ETHTOOL_A_CABLE_INF_SRC_ALCD); 486 + *finished = true; 487 + return 0; 488 + } 489 + 490 + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_ALCD_STAT); 491 + if (ret < 0) 492 + return ret; 493 + 494 + if (!(ret & DP83TD510E_ALCD_COMPLETE)) 495 + return 0; 496 + 497 + location = FIELD_GET(DP83TD510E_ALCD_CABLE_LENGTH, ret) * 100; 498 + 499 + ethnl_cable_test_fault_length_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A, 500 + location, 501 + ETHTOOL_A_CABLE_INF_SRC_ALCD); 502 + 503 + ethnl_cable_test_result_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A, 504 + ETHTOOL_A_CABLE_RESULT_CODE_OK, 505 + ETHTOOL_A_CABLE_INF_SRC_ALCD); 506 + *finished = true; 507 + 508 + return 0; 509 + } 510 + 511 + /** 512 + * dp83td510_cable_test_get_status - Get the status of the cable test for the 513 + * DP83TD510 PHY. 514 + * @phydev: Pointer to the phy_device structure. 515 + * @finished: Pointer to a boolean that indicates whether the test is finished. 516 + * 517 + * The function sets the @finished flag to true if the test is complete. 518 + * 519 + * Returns: 0 on success or a negative error code on failure. 520 + */ 521 + static int dp83td510_cable_test_get_status(struct phy_device *phydev, 522 + bool *finished) 523 + { 524 + struct dp83td510_priv *priv = phydev->priv; 525 + *finished = false; 526 + 527 + if (priv->alcd_test_active) 528 + return dp83td510_cable_test_get_alcd_status(phydev, finished); 529 + 530 + return dp83td510_cable_test_get_tdr_status(phydev, finished); 531 + } 532 + 483 533 static int dp83td510_get_features(struct phy_device *phydev) 484 534 { 485 535 /* This PHY can't respond on MDIO bus if no RMII clock is enabled. ··· 569 477 return 0; 570 478 } 571 479 480 + static int dp83td510_probe(struct phy_device *phydev) 481 + { 482 + struct device *dev = &phydev->mdio.dev; 483 + struct dp83td510_priv *priv; 484 + 485 + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 486 + if (!priv) 487 + return -ENOMEM; 488 + 489 + phydev->priv = priv; 490 + 491 + return 0; 492 + } 493 + 572 494 static struct phy_driver dp83td510_driver[] = { 573 495 { 574 496 PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID), 575 497 .name = "TI DP83TD510E", 576 498 577 499 .flags = PHY_POLL_CABLE_TEST, 500 + .probe = dp83td510_probe, 578 501 .config_aneg = dp83td510_config_aneg, 579 502 .read_status = dp83td510_read_status, 580 503 .get_features = dp83td510_get_features,
+23 -6
include/linux/ethtool_netlink.h
··· 23 23 int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd); 24 24 void ethnl_cable_test_free(struct phy_device *phydev); 25 25 void ethnl_cable_test_finished(struct phy_device *phydev); 26 - int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result); 27 - int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm); 26 + int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair, 27 + u8 result, u32 src); 28 + int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair, 29 + u32 cm, u32 src); 28 30 int ethnl_cable_test_amplitude(struct phy_device *phydev, u8 pair, s16 mV); 29 31 int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV); 30 32 int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last, ··· 56 54 static inline void ethnl_cable_test_finished(struct phy_device *phydev) 57 55 { 58 56 } 59 - static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, 60 - u8 result) 57 + static inline int ethnl_cable_test_result_with_src(struct phy_device *phydev, 58 + u8 pair, u8 result, u32 src) 61 59 { 62 60 return -EOPNOTSUPP; 63 61 } 64 62 65 - static inline int ethnl_cable_test_fault_length(struct phy_device *phydev, 66 - u8 pair, u32 cm) 63 + static inline int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, 64 + u8 pair, u32 cm, u32 src) 67 65 { 68 66 return -EOPNOTSUPP; 69 67 } ··· 121 119 } 122 120 123 121 #endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */ 122 + 123 + static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, 124 + u8 result) 125 + { 126 + return ethnl_cable_test_result_with_src(phydev, pair, result, 127 + ETHTOOL_A_CABLE_INF_SRC_TDR); 128 + } 129 + 130 + static inline int ethnl_cable_test_fault_length(struct phy_device *phydev, 131 + u8 pair, u32 cm) 132 + { 133 + return ethnl_cable_test_fault_length_with_src(phydev, pair, cm, 134 + ETHTOOL_A_CABLE_INF_SRC_TDR); 135 + } 136 + 124 137 #endif /* _LINUX_ETHTOOL_NETLINK_H_ */
+11
include/uapi/linux/ethtool_netlink.h
··· 573 573 ETHTOOL_A_CABLE_PAIR_D, 574 574 }; 575 575 576 + /* Information source for specific results. */ 577 + enum { 578 + ETHTOOL_A_CABLE_INF_SRC_UNSPEC, 579 + /* Results provided by the Time Domain Reflectometry (TDR) */ 580 + ETHTOOL_A_CABLE_INF_SRC_TDR, 581 + /* Results provided by the Active Link Cable Diagnostic (ALCD) */ 582 + ETHTOOL_A_CABLE_INF_SRC_ALCD, 583 + }; 584 + 576 585 enum { 577 586 ETHTOOL_A_CABLE_RESULT_UNSPEC, 578 587 ETHTOOL_A_CABLE_RESULT_PAIR, /* u8 ETHTOOL_A_CABLE_PAIR_ */ 579 588 ETHTOOL_A_CABLE_RESULT_CODE, /* u8 ETHTOOL_A_CABLE_RESULT_CODE_ */ 589 + ETHTOOL_A_CABLE_RESULT_SRC, /* u32 ETHTOOL_A_CABLE_INF_SRC_ */ 580 590 581 591 __ETHTOOL_A_CABLE_RESULT_CNT, 582 592 ETHTOOL_A_CABLE_RESULT_MAX = (__ETHTOOL_A_CABLE_RESULT_CNT - 1) ··· 596 586 ETHTOOL_A_CABLE_FAULT_LENGTH_UNSPEC, 597 587 ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, /* u8 ETHTOOL_A_CABLE_PAIR_ */ 598 588 ETHTOOL_A_CABLE_FAULT_LENGTH_CM, /* u32 */ 589 + ETHTOOL_A_CABLE_FAULT_LENGTH_SRC, /* u32 ETHTOOL_A_CABLE_INF_SRC_ */ 599 590 600 591 __ETHTOOL_A_CABLE_FAULT_LENGTH_CNT, 601 592 ETHTOOL_A_CABLE_FAULT_LENGTH_MAX = (__ETHTOOL_A_CABLE_FAULT_LENGTH_CNT - 1)
+15 -4
net/ethtool/cabletest.c
··· 164 164 } 165 165 EXPORT_SYMBOL_GPL(ethnl_cable_test_finished); 166 166 167 - int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result) 167 + int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair, 168 + u8 result, u32 src) 168 169 { 169 170 struct nlattr *nest; 170 171 int ret = -EMSGSIZE; ··· 178 177 goto err; 179 178 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result)) 180 179 goto err; 180 + if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) { 181 + if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_RESULT_SRC, src)) 182 + goto err; 183 + } 181 184 182 185 nla_nest_end(phydev->skb, nest); 183 186 return 0; ··· 190 185 nla_nest_cancel(phydev->skb, nest); 191 186 return ret; 192 187 } 193 - EXPORT_SYMBOL_GPL(ethnl_cable_test_result); 188 + EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src); 194 189 195 - int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm) 190 + int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair, 191 + u32 cm, u32 src) 196 192 { 197 193 struct nlattr *nest; 198 194 int ret = -EMSGSIZE; ··· 207 201 goto err; 208 202 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm)) 209 203 goto err; 204 + if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) { 205 + if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_SRC, 206 + src)) 207 + goto err; 208 + } 210 209 211 210 nla_nest_end(phydev->skb, nest); 212 211 return 0; ··· 220 209 nla_nest_cancel(phydev->skb, nest); 221 210 return ret; 222 211 } 223 - EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length); 212 + EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src); 224 213 225 214 static const struct nla_policy cable_test_tdr_act_cfg_policy[] = { 226 215 [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 },