Commit f85cd6ae authored by Jakub Kicinski's avatar Jakub Kicinski

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.deSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents b494b167 986a7fa4
...@@ -667,6 +667,9 @@ attribute-sets: ...@@ -667,6 +667,9 @@ attribute-sets:
- -
name: code name: code
type: u8 type: u8
-
name: src
type: u32
- -
name: cable-fault-length name: cable-fault-length
attributes: attributes:
...@@ -676,6 +679,9 @@ attribute-sets: ...@@ -676,6 +679,9 @@ attribute-sets:
- -
name: cm name: cm
type: u32 type: u32
-
name: src
type: u32
- -
name: cable-nest name: cable-nest
attributes: attributes:
......
...@@ -1314,12 +1314,17 @@ information. ...@@ -1314,12 +1314,17 @@ information.
+-+-+-----------------------------------------+--------+---------------------+ +-+-+-----------------------------------------+--------+---------------------+
| | | ``ETHTOOL_A_CABLE_RESULTS_CODE`` | u8 | result code | | | | ``ETHTOOL_A_CABLE_RESULTS_CODE`` | u8 | result code |
+-+-+-----------------------------------------+--------+---------------------+ +-+-+-----------------------------------------+--------+---------------------+
| | | ``ETHTOOL_A_CABLE_RESULT_SRC`` | u32 | information source |
+-+-+-----------------------------------------+--------+---------------------+
| | ``ETHTOOL_A_CABLE_NEST_FAULT_LENGTH`` | nested | cable length | | | ``ETHTOOL_A_CABLE_NEST_FAULT_LENGTH`` | nested | cable length |
+-+-+-----------------------------------------+--------+---------------------+ +-+-+-----------------------------------------+--------+---------------------+
| | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR`` | u8 | pair number | | | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR`` | u8 | pair number |
+-+-+-----------------------------------------+--------+---------------------+ +-+-+-----------------------------------------+--------+---------------------+
| | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_CM`` | u32 | length in cm | | | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_CM`` | u32 | length in cm |
+-+-+-----------------------------------------+--------+---------------------+ +-+-+-----------------------------------------+--------+---------------------+
| | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_SRC`` | u32 | information source |
+-+-+-----------------------------------------+--------+---------------------+
CABLE_TEST TDR CABLE_TEST TDR
============== ==============
......
...@@ -58,6 +58,10 @@ static const u16 dp83td510_mse_sqi_map[] = { ...@@ -58,6 +58,10 @@ static const u16 dp83td510_mse_sqi_map[] = {
0x0000 /* 24dB =< SNR */ 0x0000 /* 24dB =< SNR */
}; };
struct dp83td510_priv {
bool alcd_test_active;
};
/* Time Domain Reflectometry (TDR) Functionality of DP83TD510 PHY /* Time Domain Reflectometry (TDR) Functionality of DP83TD510 PHY
* *
* I assume that this PHY is using a variation of Spread Spectrum Time Domain * I assume that this PHY is using a variation of Spread Spectrum Time Domain
...@@ -169,6 +173,10 @@ static const u16 dp83td510_mse_sqi_map[] = { ...@@ -169,6 +173,10 @@ static const u16 dp83td510_mse_sqi_map[] = {
#define DP83TD510E_UNKN_030E 0x30e #define DP83TD510E_UNKN_030E 0x30e
#define DP83TD510E_030E_VAL 0x2520 #define DP83TD510E_030E_VAL 0x2520
#define DP83TD510E_ALCD_STAT 0xa9f
#define DP83TD510E_ALCD_COMPLETE BIT(15)
#define DP83TD510E_ALCD_CABLE_LENGTH GENMASK(10, 0)
static int dp83td510_config_intr(struct phy_device *phydev) static int dp83td510_config_intr(struct phy_device *phydev)
{ {
int ret; int ret;
...@@ -325,8 +333,23 @@ static int dp83td510_get_sqi_max(struct phy_device *phydev) ...@@ -325,8 +333,23 @@ static int dp83td510_get_sqi_max(struct phy_device *phydev)
*/ */
static int dp83td510_cable_test_start(struct phy_device *phydev) static int dp83td510_cable_test_start(struct phy_device *phydev)
{ {
struct dp83td510_priv *priv = phydev->priv;
int ret; int ret;
/* If link partner is active, we won't be able to use TDR, since
* we can't force link partner to be silent. The autonegotiation
* pulses will be too frequent and the TDR sequence will be
* too long. So, TDR will always fail. Since the link is established
* we already know that the cable is working, so we can get some
* extra information line the cable length using ALCD.
*/
if (phydev->link) {
priv->alcd_test_active = true;
return 0;
}
priv->alcd_test_active = false;
ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_CTRL, ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_CTRL,
DP83TD510E_CTRL_HW_RESET); DP83TD510E_CTRL_HW_RESET);
if (ret) if (ret)
...@@ -402,8 +425,8 @@ static int dp83td510_cable_test_start(struct phy_device *phydev) ...@@ -402,8 +425,8 @@ static int dp83td510_cable_test_start(struct phy_device *phydev)
} }
/** /**
* dp83td510_cable_test_get_status - Get the status of the cable test for the * dp83td510_cable_test_get_tdr_status - Get the status of the TDR test for the
* DP83TD510 PHY. * DP83TD510 PHY.
* @phydev: Pointer to the phy_device structure. * @phydev: Pointer to the phy_device structure.
* @finished: Pointer to a boolean that indicates whether the test is finished. * @finished: Pointer to a boolean that indicates whether the test is finished.
* *
...@@ -411,13 +434,11 @@ static int dp83td510_cable_test_start(struct phy_device *phydev) ...@@ -411,13 +434,11 @@ static int dp83td510_cable_test_start(struct phy_device *phydev)
* *
* Returns: 0 on success or a negative error code on failure. * Returns: 0 on success or a negative error code on failure.
*/ */
static int dp83td510_cable_test_get_status(struct phy_device *phydev, static int dp83td510_cable_test_get_tdr_status(struct phy_device *phydev,
bool *finished) bool *finished)
{ {
int ret, stat; int ret, stat;
*finished = false;
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG); ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG);
if (ret < 0) if (ret < 0)
return ret; return ret;
...@@ -459,6 +480,77 @@ static int dp83td510_cable_test_get_status(struct phy_device *phydev, ...@@ -459,6 +480,77 @@ static int dp83td510_cable_test_get_status(struct phy_device *phydev,
return phy_init_hw(phydev); return phy_init_hw(phydev);
} }
/**
* dp83td510_cable_test_get_alcd_status - Get the status of the ALCD test for the
* DP83TD510 PHY.
* @phydev: Pointer to the phy_device structure.
* @finished: Pointer to a boolean that indicates whether the test is finished.
*
* The function sets the @finished flag to true if the test is complete.
* The function reads the cable length and reports it to the user.
*
* Returns: 0 on success or a negative error code on failure.
*/
static int dp83td510_cable_test_get_alcd_status(struct phy_device *phydev,
bool *finished)
{
unsigned int location;
int ret, phy_sts;
phy_sts = phy_read(phydev, DP83TD510E_PHY_STS);
if (!(phy_sts & DP83TD510E_LINK_STATUS)) {
/* If the link is down, we can't do any thing usable now */
ethnl_cable_test_result_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A,
ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC,
ETHTOOL_A_CABLE_INF_SRC_ALCD);
*finished = true;
return 0;
}
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_ALCD_STAT);
if (ret < 0)
return ret;
if (!(ret & DP83TD510E_ALCD_COMPLETE))
return 0;
location = FIELD_GET(DP83TD510E_ALCD_CABLE_LENGTH, ret) * 100;
ethnl_cable_test_fault_length_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A,
location,
ETHTOOL_A_CABLE_INF_SRC_ALCD);
ethnl_cable_test_result_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A,
ETHTOOL_A_CABLE_RESULT_CODE_OK,
ETHTOOL_A_CABLE_INF_SRC_ALCD);
*finished = true;
return 0;
}
/**
* dp83td510_cable_test_get_status - Get the status of the cable test for the
* DP83TD510 PHY.
* @phydev: Pointer to the phy_device structure.
* @finished: Pointer to a boolean that indicates whether the test is finished.
*
* The function sets the @finished flag to true if the test is complete.
*
* Returns: 0 on success or a negative error code on failure.
*/
static int dp83td510_cable_test_get_status(struct phy_device *phydev,
bool *finished)
{
struct dp83td510_priv *priv = phydev->priv;
*finished = false;
if (priv->alcd_test_active)
return dp83td510_cable_test_get_alcd_status(phydev, finished);
return dp83td510_cable_test_get_tdr_status(phydev, finished);
}
static int dp83td510_get_features(struct phy_device *phydev) static int dp83td510_get_features(struct phy_device *phydev)
{ {
/* This PHY can't respond on MDIO bus if no RMII clock is enabled. /* This PHY can't respond on MDIO bus if no RMII clock is enabled.
...@@ -477,12 +569,27 @@ static int dp83td510_get_features(struct phy_device *phydev) ...@@ -477,12 +569,27 @@ static int dp83td510_get_features(struct phy_device *phydev)
return 0; return 0;
} }
static int dp83td510_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
struct dp83td510_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
return 0;
}
static struct phy_driver dp83td510_driver[] = { static struct phy_driver dp83td510_driver[] = {
{ {
PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID), PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID),
.name = "TI DP83TD510E", .name = "TI DP83TD510E",
.flags = PHY_POLL_CABLE_TEST, .flags = PHY_POLL_CABLE_TEST,
.probe = dp83td510_probe,
.config_aneg = dp83td510_config_aneg, .config_aneg = dp83td510_config_aneg,
.read_status = dp83td510_read_status, .read_status = dp83td510_read_status,
.get_features = dp83td510_get_features, .get_features = dp83td510_get_features,
......
...@@ -23,8 +23,10 @@ struct phy_device; ...@@ -23,8 +23,10 @@ struct phy_device;
int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd); int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd);
void ethnl_cable_test_free(struct phy_device *phydev); void ethnl_cable_test_free(struct phy_device *phydev);
void ethnl_cable_test_finished(struct phy_device *phydev); void ethnl_cable_test_finished(struct phy_device *phydev);
int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result); int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair,
int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm); u8 result, u32 src);
int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair,
u32 cm, u32 src);
int ethnl_cable_test_amplitude(struct phy_device *phydev, u8 pair, s16 mV); int ethnl_cable_test_amplitude(struct phy_device *phydev, u8 pair, s16 mV);
int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV); int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV);
int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last, int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
...@@ -54,14 +56,14 @@ static inline void ethnl_cable_test_free(struct phy_device *phydev) ...@@ -54,14 +56,14 @@ static inline void ethnl_cable_test_free(struct phy_device *phydev)
static inline void ethnl_cable_test_finished(struct phy_device *phydev) static inline void ethnl_cable_test_finished(struct phy_device *phydev)
{ {
} }
static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, static inline int ethnl_cable_test_result_with_src(struct phy_device *phydev,
u8 result) u8 pair, u8 result, u32 src)
{ {
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
static inline int ethnl_cable_test_fault_length(struct phy_device *phydev, static inline int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev,
u8 pair, u32 cm) u8 pair, u32 cm, u32 src)
{ {
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
...@@ -119,4 +121,19 @@ static inline bool ethtool_dev_mm_supported(struct net_device *dev) ...@@ -119,4 +121,19 @@ static inline bool ethtool_dev_mm_supported(struct net_device *dev)
} }
#endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */ #endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */
static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair,
u8 result)
{
return ethnl_cable_test_result_with_src(phydev, pair, result,
ETHTOOL_A_CABLE_INF_SRC_TDR);
}
static inline int ethnl_cable_test_fault_length(struct phy_device *phydev,
u8 pair, u32 cm)
{
return ethnl_cable_test_fault_length_with_src(phydev, pair, cm,
ETHTOOL_A_CABLE_INF_SRC_TDR);
}
#endif /* _LINUX_ETHTOOL_NETLINK_H_ */ #endif /* _LINUX_ETHTOOL_NETLINK_H_ */
...@@ -573,10 +573,20 @@ enum { ...@@ -573,10 +573,20 @@ enum {
ETHTOOL_A_CABLE_PAIR_D, ETHTOOL_A_CABLE_PAIR_D,
}; };
/* Information source for specific results. */
enum {
ETHTOOL_A_CABLE_INF_SRC_UNSPEC,
/* Results provided by the Time Domain Reflectometry (TDR) */
ETHTOOL_A_CABLE_INF_SRC_TDR,
/* Results provided by the Active Link Cable Diagnostic (ALCD) */
ETHTOOL_A_CABLE_INF_SRC_ALCD,
};
enum { enum {
ETHTOOL_A_CABLE_RESULT_UNSPEC, ETHTOOL_A_CABLE_RESULT_UNSPEC,
ETHTOOL_A_CABLE_RESULT_PAIR, /* u8 ETHTOOL_A_CABLE_PAIR_ */ ETHTOOL_A_CABLE_RESULT_PAIR, /* u8 ETHTOOL_A_CABLE_PAIR_ */
ETHTOOL_A_CABLE_RESULT_CODE, /* u8 ETHTOOL_A_CABLE_RESULT_CODE_ */ ETHTOOL_A_CABLE_RESULT_CODE, /* u8 ETHTOOL_A_CABLE_RESULT_CODE_ */
ETHTOOL_A_CABLE_RESULT_SRC, /* u32 ETHTOOL_A_CABLE_INF_SRC_ */
__ETHTOOL_A_CABLE_RESULT_CNT, __ETHTOOL_A_CABLE_RESULT_CNT,
ETHTOOL_A_CABLE_RESULT_MAX = (__ETHTOOL_A_CABLE_RESULT_CNT - 1) ETHTOOL_A_CABLE_RESULT_MAX = (__ETHTOOL_A_CABLE_RESULT_CNT - 1)
...@@ -586,6 +596,7 @@ enum { ...@@ -586,6 +596,7 @@ enum {
ETHTOOL_A_CABLE_FAULT_LENGTH_UNSPEC, ETHTOOL_A_CABLE_FAULT_LENGTH_UNSPEC,
ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, /* u8 ETHTOOL_A_CABLE_PAIR_ */ ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, /* u8 ETHTOOL_A_CABLE_PAIR_ */
ETHTOOL_A_CABLE_FAULT_LENGTH_CM, /* u32 */ ETHTOOL_A_CABLE_FAULT_LENGTH_CM, /* u32 */
ETHTOOL_A_CABLE_FAULT_LENGTH_SRC, /* u32 ETHTOOL_A_CABLE_INF_SRC_ */
__ETHTOOL_A_CABLE_FAULT_LENGTH_CNT, __ETHTOOL_A_CABLE_FAULT_LENGTH_CNT,
ETHTOOL_A_CABLE_FAULT_LENGTH_MAX = (__ETHTOOL_A_CABLE_FAULT_LENGTH_CNT - 1) ETHTOOL_A_CABLE_FAULT_LENGTH_MAX = (__ETHTOOL_A_CABLE_FAULT_LENGTH_CNT - 1)
......
...@@ -164,7 +164,8 @@ void ethnl_cable_test_finished(struct phy_device *phydev) ...@@ -164,7 +164,8 @@ void ethnl_cable_test_finished(struct phy_device *phydev)
} }
EXPORT_SYMBOL_GPL(ethnl_cable_test_finished); EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result) int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair,
u8 result, u32 src)
{ {
struct nlattr *nest; struct nlattr *nest;
int ret = -EMSGSIZE; int ret = -EMSGSIZE;
...@@ -177,6 +178,10 @@ int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result) ...@@ -177,6 +178,10 @@ int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
goto err; goto err;
if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result)) if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
goto err; goto err;
if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_RESULT_SRC, src))
goto err;
}
nla_nest_end(phydev->skb, nest); nla_nest_end(phydev->skb, nest);
return 0; return 0;
...@@ -185,9 +190,10 @@ int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result) ...@@ -185,9 +190,10 @@ int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
nla_nest_cancel(phydev->skb, nest); nla_nest_cancel(phydev->skb, nest);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(ethnl_cable_test_result); EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm) int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair,
u32 cm, u32 src)
{ {
struct nlattr *nest; struct nlattr *nest;
int ret = -EMSGSIZE; int ret = -EMSGSIZE;
...@@ -201,6 +207,11 @@ int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm) ...@@ -201,6 +207,11 @@ int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
goto err; goto err;
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm)) if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
goto err; goto err;
if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_SRC,
src))
goto err;
}
nla_nest_end(phydev->skb, nest); nla_nest_end(phydev->skb, nest);
return 0; return 0;
...@@ -209,7 +220,7 @@ int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm) ...@@ -209,7 +220,7 @@ int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
nla_nest_cancel(phydev->skb, nest); nla_nest_cancel(phydev->skb, nest);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length); EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
static const struct nla_policy cable_test_tdr_act_cfg_policy[] = { static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 }, [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment