Extend ETHTOOL_MSG_LINKSTATE_GET to optionally return a simplified Mean Square Error (MSE) reading alongside existing link status fields. The new attributes are: - ETHTOOL_A_LINKSTATE_MSE_VALUE: current average MSE value - ETHTOOL_A_LINKSTATE_MSE_MAX: scale limit for the reported value - ETHTOOL_A_LINKSTATE_MSE_CHANNEL: source channel selector This path reuses the PHY MSE core API, but only retrieves a single value intended for quick link-health checks: * If the PHY supports a WORST channel selector, report its current average MSE. * Otherwise, if LINK-wide measurements are supported, report those. * If neither is available, omit the attributes. Unlike the full MSE_GET interface, LINKSTATE_GET does not expose per-channel or peak/worst-peak values and incurs minimal overhead. Drivers that implement get_mse_config() / get_mse_snapshot() will automatically populate this data. The intent is to provide tooling with a “fast path” health indicator without issuing a separate MSE_GET request, though the long-term overlap with the full interface may need reevaluation. Signed-off-by: Oleksij Rempel --- Documentation/networking/ethtool-netlink.rst | 9 ++ .../uapi/linux/ethtool_netlink_generated.h | 3 + net/ethtool/linkstate.c | 84 +++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 55efe3673f85..a3c47dfc0605 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -530,6 +530,9 @@ Kernel response contents: ``ETHTOOL_A_LINKSTATE_EXT_STATE`` u8 link extended state ``ETHTOOL_A_LINKSTATE_EXT_SUBSTATE`` u8 link extended substate ``ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT`` u32 count of link down events + ``ETHTOOL_A_LINKSTATE_MSE_VALUE`` u32 Current average MSE value + ``ETHTOOL_A_LINKSTATE_MSE_MAX`` u32 Max scale for average MSE + ``ETHTOOL_A_LINKSTATE_MSE_CHANNEL`` u32 Source of MSE value ==================================== ====== ============================ For most NIC drivers, the value of ``ETHTOOL_A_LINKSTATE_LINK`` returns @@ -541,6 +544,12 @@ optional values. ethtool core can provide either both ``ETHTOOL_A_LINKSTATE_EXT_STATE`` and ``ETHTOOL_A_LINKSTATE_EXT_SUBSTATE``, or only ``ETHTOOL_A_LINKSTATE_EXT_STATE``, or none of them. +``ETHTOOL_A_LINKSTATE_MSE_VALUE`` and ``ETHTOOL_A_LINKSTATE_MSE_MAX`` are +optional values. The MSE value provided by this interface is a lightweight, +less detailed version for quick health checks. If only one channel is used, it +returns the current average MSE value. If multiple channels are supported, it +returns the current average MSE of the channel with the worst MSE. + ``LINKSTATE_GET`` allows dump requests (kernel returns reply messages for all devices supporting the request). diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 9c37a96a320b..6ef03a7de4ab 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -322,6 +322,9 @@ enum { ETHTOOL_A_LINKSTATE_EXT_STATE, ETHTOOL_A_LINKSTATE_EXT_SUBSTATE, ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT, + ETHTOOL_A_LINKSTATE_MSE_VALUE, + ETHTOOL_A_LINKSTATE_MSE_MAX, + ETHTOOL_A_LINKSTATE_MSE_CHANNEL, __ETHTOOL_A_LINKSTATE_CNT, ETHTOOL_A_LINKSTATE_MAX = (__ETHTOOL_A_LINKSTATE_CNT - 1) diff --git a/net/ethtool/linkstate.c b/net/ethtool/linkstate.c index 05a5f72c99fa..b27fb0ffc526 100644 --- a/net/ethtool/linkstate.c +++ b/net/ethtool/linkstate.c @@ -14,6 +14,9 @@ struct linkstate_reply_data { int link; int sqi; int sqi_max; + u32 mse_value; + u32 mse_max; + u32 mse_channel; struct ethtool_link_ext_stats link_stats; bool link_ext_state_provided; struct ethtool_link_ext_state_info ethtool_link_ext_state_info; @@ -76,6 +79,65 @@ static bool linkstate_sqi_valid(struct linkstate_reply_data *data) data->sqi <= data->sqi_max; } +static int linkstate_get_mse(struct phy_device *phydev, + struct linkstate_reply_data *data) +{ + struct phy_mse_snapshot snapshot = {}; + struct phy_mse_config config = {}; + int channel, ret; + + if (!phydev) + return -EOPNOTSUPP; + + mutex_lock(&phydev->lock); + + if (!phydev->drv || !phydev->drv->get_mse_config || + !phydev->drv->get_mse_snapshot) { + ret = -EOPNOTSUPP; + goto unlock; + } + + if (!phydev->link) { + ret = -ENETDOWN; + goto unlock; + } + + ret = phydev->drv->get_mse_config(phydev, &config); + if (ret) + goto unlock; + + if (config.supported_caps & PHY_MSE_CAP_WORST_CHANNEL) { + channel = PHY_MSE_CHANNEL_WORST; + } else if (config.supported_caps & PHY_MSE_CAP_LINK) { + channel = PHY_MSE_CHANNEL_LINK; + } else { + ret = -EOPNOTSUPP; + goto unlock; + } + + ret = phydev->drv->get_mse_snapshot(phydev, channel, &snapshot); + if (ret) + goto unlock; + + data->mse_value = snapshot.average_mse; + data->mse_max = config.max_average_mse; + data->mse_channel = channel; + +unlock: + mutex_unlock(&phydev->lock); + return ret; +} + +static bool linkstate_mse_critical_error(int err) +{ + return err < 0 && err != -EOPNOTSUPP && err != -ENETDOWN; +} + +static bool linkstate_mse_valid(struct linkstate_reply_data *data) +{ + return data->mse_max > 0 && data->mse_value <= data->mse_max; +} + static int linkstate_get_link_ext_state(struct net_device *dev, struct linkstate_reply_data *data) { @@ -125,6 +187,10 @@ static int linkstate_prepare_data(const struct ethnl_req_info *req_base, goto out; data->sqi_max = ret; + ret = linkstate_get_mse(phydev, data); + if (linkstate_mse_critical_error(ret)) + goto out; + if (dev->flags & IFF_UP) { ret = linkstate_get_link_ext_state(dev, data); if (ret < 0 && ret != -EOPNOTSUPP && ret != -ENODATA) @@ -164,6 +230,12 @@ static int linkstate_reply_size(const struct ethnl_req_info *req_base, len += nla_total_size(sizeof(u32)); /* LINKSTATE_SQI_MAX */ } + if (linkstate_mse_valid(data)) { + len += nla_total_size(sizeof(u32)); /* LINKSTATE_MSE_VALUE */ + len += nla_total_size(sizeof(u32)); /* LINKSTATE_MSE_MAX */ + len += nla_total_size(sizeof(u32)); /* LINKSTATE_MSE_CHANNEL */ + } + if (data->link_ext_state_provided) len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_STATE */ @@ -195,6 +267,18 @@ static int linkstate_fill_reply(struct sk_buff *skb, return -EMSGSIZE; } + if (linkstate_mse_valid(data)) { + if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_MSE_VALUE, + data->mse_value)) + return -EMSGSIZE; + if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_MSE_MAX, + data->mse_max)) + return -EMSGSIZE; + if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_MSE_CHANNEL, + data->mse_channel)) + return -EMSGSIZE; + } + if (data->link_ext_state_provided) { if (nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_STATE, data->ethtool_link_ext_state_info.link_ext_state)) -- 2.39.5