``ETHTOOL_MSG_RSS_SET`` set RSS settings
``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context
``ETHTOOL_MSG_RSS_DELETE_ACT`` delete an additional RSS context
+ ``ETHTOOL_MSG_MSE_GET`` get MSE diagnostic data
===================================== =================================
Kernel to userspace:
``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY`` create an additional RSS context
``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created
``ETHTOOL_MSG_RSS_DELETE_NTF`` additional RSS context deleted
+ ``ETHTOOL_MSG_MSE_GET_REPLY`` MSE diagnostic data
======================================== =================================
``GET`` requests are sent by userspace applications to retrieve device
For a description of each attribute, see ``TSCONFIG_GET``.
+MSE_GET
+=======
+
+Retrieves detailed Mean Square Error (MSE) diagnostic information from the PHY.
+
+Request Contents:
+
+ ==================================== ====== ============================
+ ``ETHTOOL_A_MSE_HEADER`` nested request header
+ ==================================== ====== ============================
+
+Kernel Response Contents:
+
+ ==================================== ====== ================================
+ ``ETHTOOL_A_MSE_HEADER`` nested reply header
+ ``ETHTOOL_A_MSE_CAPABILITIES`` nested capability/scale info for MSE
+ measurements
+ ``ETHTOOL_A_MSE_CHANNEL_A`` nested snapshot for Channel A
+ ``ETHTOOL_A_MSE_CHANNEL_B`` nested snapshot for Channel B
+ ``ETHTOOL_A_MSE_CHANNEL_C`` nested snapshot for Channel C
+ ``ETHTOOL_A_MSE_CHANNEL_D`` nested snapshot for Channel D
+ ``ETHTOOL_A_MSE_WORST_CHANNEL`` nested snapshot for worst channel
+ ``ETHTOOL_A_MSE_LINK`` nested snapshot for link-wide aggregate
+ ==================================== ====== ================================
+
+MSE Capabilities
+----------------
+
+This nested attribute reports the capability / scaling properties used to
+interpret snapshot values.
+
+ ============================================== ====== =========================
+ ``ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE`` uint max avg_mse scale
+ ``ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE`` uint max peak_mse scale
+ ``ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS`` uint sample rate (picoseconds)
+ ``ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS`` uint symbols per HW sample
+ ============================================== ====== =========================
+
+The max-average/peak fields are included only if the corresponding metric
+is supported by the PHY. Their absence indicates that the metric is not
+available.
+
+See ``struct phy_mse_capability`` kernel documentation in
+``include/linux/phy.h``.
+
+MSE Snapshot
+------------
+
+Each per-channel nest contains an atomic snapshot of MSE values for that
+selector (channel A/B/C/D, worst channel, or link).
+
+ ========================================== ====== ===================
+ ``ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE`` uint average MSE value
+ ``ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE`` uint current peak MSE
+ ``ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE`` uint worst-case peak MSE
+ ========================================== ====== ===================
+
+Within each channel nest, only the metrics supported by the PHY will be present.
+
+See ``struct phy_mse_snapshot`` kernel documentation in
+``include/linux/phy.h``.
+
Request translation
===================
ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1)
};
+enum {
+ ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE = 1,
+ ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE,
+ ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS,
+ ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS,
+
+ __ETHTOOL_A_MSE_CAPABILITIES_CNT,
+ ETHTOOL_A_MSE_CAPABILITIES_MAX = (__ETHTOOL_A_MSE_CAPABILITIES_CNT - 1)
+};
+
+enum {
+ ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE = 1,
+ ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE,
+ ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE,
+
+ __ETHTOOL_A_MSE_SNAPSHOT_CNT,
+ ETHTOOL_A_MSE_SNAPSHOT_MAX = (__ETHTOOL_A_MSE_SNAPSHOT_CNT - 1)
+};
+
+enum {
+ ETHTOOL_A_MSE_HEADER = 1,
+ ETHTOOL_A_MSE_CAPABILITIES,
+ ETHTOOL_A_MSE_CHANNEL_A,
+ ETHTOOL_A_MSE_CHANNEL_B,
+ ETHTOOL_A_MSE_CHANNEL_C,
+ ETHTOOL_A_MSE_CHANNEL_D,
+ ETHTOOL_A_MSE_WORST_CHANNEL,
+ ETHTOOL_A_MSE_LINK,
+
+ __ETHTOOL_A_MSE_CNT,
+ ETHTOOL_A_MSE_MAX = (__ETHTOOL_A_MSE_CNT - 1)
+};
+
enum {
ETHTOOL_MSG_USER_NONE = 0,
ETHTOOL_MSG_STRSET_GET = 1,
ETHTOOL_MSG_RSS_SET,
ETHTOOL_MSG_RSS_CREATE_ACT,
ETHTOOL_MSG_RSS_DELETE_ACT,
+ ETHTOOL_MSG_MSE_GET,
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
ETHTOOL_MSG_RSS_CREATE_ACT_REPLY,
ETHTOOL_MSG_RSS_CREATE_NTF,
ETHTOOL_MSG_RSS_DELETE_NTF,
+ ETHTOOL_MSG_MSE_GET_REPLY,
__ETHTOOL_MSG_KERNEL_CNT,
ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/slab.h>
+
+#include "netlink.h"
+#include "common.h"
+
+/* Channels A-D only; WORST and LINK are exclusive alternatives */
+#define PHY_MSE_CHANNEL_COUNT 4
+
+struct mse_req_info {
+ struct ethnl_req_info base;
+};
+
+struct mse_snapshot_entry {
+ struct phy_mse_snapshot snapshot;
+ int channel;
+};
+
+struct mse_reply_data {
+ struct ethnl_reply_data base;
+ struct phy_mse_capability capability;
+ struct mse_snapshot_entry *snapshots;
+ unsigned int num_snapshots;
+};
+
+static struct mse_reply_data *
+mse_repdata(const struct ethnl_reply_data *reply_base)
+{
+ return container_of(reply_base, struct mse_reply_data, base);
+}
+
+const struct nla_policy ethnl_mse_get_policy[] = {
+ [ETHTOOL_A_MSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy),
+};
+
+static int get_snapshot_if_supported(struct phy_device *phydev,
+ struct mse_reply_data *data,
+ unsigned int *idx, u32 cap_bit,
+ enum phy_mse_channel channel)
+{
+ int ret;
+
+ if (data->capability.supported_caps & cap_bit) {
+ ret = phydev->drv->get_mse_snapshot(phydev, channel,
+ &data->snapshots[*idx].snapshot);
+ if (ret)
+ return ret;
+ data->snapshots[*idx].channel = channel;
+ (*idx)++;
+ }
+
+ return 0;
+}
+
+static int mse_get_channels(struct phy_device *phydev,
+ struct mse_reply_data *data)
+{
+ unsigned int i = 0;
+ int ret;
+
+ if (!data->capability.supported_caps)
+ return 0;
+
+ data->snapshots = kcalloc(PHY_MSE_CHANNEL_COUNT,
+ sizeof(*data->snapshots), GFP_KERNEL);
+ if (!data->snapshots)
+ return -ENOMEM;
+
+ /* Priority 1: Individual channels */
+ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_A,
+ PHY_MSE_CHANNEL_A);
+ if (ret)
+ return ret;
+ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_B,
+ PHY_MSE_CHANNEL_B);
+ if (ret)
+ return ret;
+ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_C,
+ PHY_MSE_CHANNEL_C);
+ if (ret)
+ return ret;
+ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_D,
+ PHY_MSE_CHANNEL_D);
+ if (ret)
+ return ret;
+
+ /* If any individual channels were found, we are done. */
+ if (i > 0) {
+ data->num_snapshots = i;
+ return 0;
+ }
+
+ /* Priority 2: Worst channel, if no individual channels supported. */
+ ret = get_snapshot_if_supported(phydev, data, &i,
+ PHY_MSE_CAP_WORST_CHANNEL,
+ PHY_MSE_CHANNEL_WORST);
+ if (ret)
+ return ret;
+
+ /* If worst channel was found, we are done. */
+ if (i > 0) {
+ data->num_snapshots = i;
+ return 0;
+ }
+
+ /* Priority 3: Link-wide, if nothing else is supported. */
+ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_LINK,
+ PHY_MSE_CHANNEL_LINK);
+ if (ret)
+ return ret;
+
+ data->num_snapshots = i;
+ return 0;
+}
+
+static int mse_prepare_data(const struct ethnl_req_info *req_base,
+ struct ethnl_reply_data *reply_base,
+ const struct genl_info *info)
+{
+ struct mse_reply_data *data = mse_repdata(reply_base);
+ struct net_device *dev = reply_base->dev;
+ struct phy_device *phydev;
+ int ret;
+
+ phydev = ethnl_req_get_phydev(req_base, info->attrs,
+ ETHTOOL_A_MSE_HEADER, info->extack);
+ if (IS_ERR(phydev))
+ return PTR_ERR(phydev);
+ if (!phydev)
+ return -EOPNOTSUPP;
+
+ ret = ethnl_ops_begin(dev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&phydev->lock);
+
+ if (!phydev->drv || !phydev->drv->get_mse_capability ||
+ !phydev->drv->get_mse_snapshot) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+ if (!phydev->link) {
+ ret = -ENETDOWN;
+ goto out_unlock;
+ }
+
+ ret = phydev->drv->get_mse_capability(phydev, &data->capability);
+ if (ret)
+ goto out_unlock;
+
+ ret = mse_get_channels(phydev, data);
+
+out_unlock:
+ mutex_unlock(&phydev->lock);
+ ethnl_ops_complete(dev);
+ if (ret)
+ kfree(data->snapshots);
+ return ret;
+}
+
+static void mse_cleanup_data(struct ethnl_reply_data *reply_base)
+{
+ struct mse_reply_data *data = mse_repdata(reply_base);
+
+ kfree(data->snapshots);
+}
+
+static int mse_reply_size(const struct ethnl_req_info *req_base,
+ const struct ethnl_reply_data *reply_base)
+{
+ const struct mse_reply_data *data = mse_repdata(reply_base);
+ size_t len = 0;
+ unsigned int i;
+
+ /* ETHTOOL_A_MSE_CAPABILITIES */
+ len += nla_total_size(0);
+ if (data->capability.supported_caps & PHY_MSE_CAP_AVG)
+ /* ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE */
+ len += nla_total_size(sizeof(u64));
+ if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK |
+ PHY_MSE_CAP_WORST_PEAK))
+ /* ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE */
+ len += nla_total_size(sizeof(u64));
+ /* ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS */
+ len += nla_total_size(sizeof(u64));
+ /* ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS */
+ len += nla_total_size(sizeof(u64));
+
+ for (i = 0; i < data->num_snapshots; i++) {
+ size_t snapshot_len = 0;
+
+ /* Per-channel nest (e.g., ETHTOOL_A_MSE_CHANNEL_A / _B / _C /
+ * _D / _WORST_CHANNEL / _LINK)
+ */
+ snapshot_len += nla_total_size(0);
+
+ if (data->capability.supported_caps & PHY_MSE_CAP_AVG)
+ snapshot_len += nla_total_size(sizeof(u64));
+ if (data->capability.supported_caps & PHY_MSE_CAP_PEAK)
+ snapshot_len += nla_total_size(sizeof(u64));
+ if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK)
+ snapshot_len += nla_total_size(sizeof(u64));
+
+ len += snapshot_len;
+ }
+
+ return len;
+}
+
+static int mse_channel_to_attr(int ch)
+{
+ switch (ch) {
+ case PHY_MSE_CHANNEL_A:
+ return ETHTOOL_A_MSE_CHANNEL_A;
+ case PHY_MSE_CHANNEL_B:
+ return ETHTOOL_A_MSE_CHANNEL_B;
+ case PHY_MSE_CHANNEL_C:
+ return ETHTOOL_A_MSE_CHANNEL_C;
+ case PHY_MSE_CHANNEL_D:
+ return ETHTOOL_A_MSE_CHANNEL_D;
+ case PHY_MSE_CHANNEL_WORST:
+ return ETHTOOL_A_MSE_WORST_CHANNEL;
+ case PHY_MSE_CHANNEL_LINK:
+ return ETHTOOL_A_MSE_LINK;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mse_fill_reply(struct sk_buff *skb,
+ const struct ethnl_req_info *req_base,
+ const struct ethnl_reply_data *reply_base)
+{
+ const struct mse_reply_data *data = mse_repdata(reply_base);
+ struct nlattr *nest;
+ unsigned int i;
+ int ret;
+
+ nest = nla_nest_start(skb, ETHTOOL_A_MSE_CAPABILITIES);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (data->capability.supported_caps & PHY_MSE_CAP_AVG) {
+ ret = nla_put_uint(skb,
+ ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE,
+ data->capability.max_average_mse);
+ if (ret < 0)
+ goto nla_put_nest_failure;
+ }
+
+ if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK |
+ PHY_MSE_CAP_WORST_PEAK)) {
+ ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE,
+ data->capability.max_peak_mse);
+ if (ret < 0)
+ goto nla_put_nest_failure;
+ }
+
+ ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS,
+ data->capability.refresh_rate_ps);
+ if (ret < 0)
+ goto nla_put_nest_failure;
+
+ ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS,
+ data->capability.num_symbols);
+ if (ret < 0)
+ goto nla_put_nest_failure;
+
+ nla_nest_end(skb, nest);
+
+ for (i = 0; i < data->num_snapshots; i++) {
+ const struct mse_snapshot_entry *s = &data->snapshots[i];
+ int chan_attr;
+
+ chan_attr = mse_channel_to_attr(s->channel);
+ if (chan_attr < 0)
+ return chan_attr;
+
+ nest = nla_nest_start(skb, chan_attr);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (data->capability.supported_caps & PHY_MSE_CAP_AVG) {
+ ret = nla_put_uint(skb,
+ ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE,
+ s->snapshot.average_mse);
+ if (ret)
+ goto nla_put_nest_failure;
+ }
+ if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) {
+ ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE,
+ s->snapshot.peak_mse);
+ if (ret)
+ goto nla_put_nest_failure;
+ }
+ if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) {
+ ret = nla_put_uint(skb,
+ ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE,
+ s->snapshot.worst_peak_mse);
+ if (ret)
+ goto nla_put_nest_failure;
+ }
+
+ nla_nest_end(skb, nest);
+ }
+
+ return 0;
+
+nla_put_nest_failure:
+ nla_nest_cancel(skb, nest);
+ return ret;
+}
+
+const struct ethnl_request_ops ethnl_mse_request_ops = {
+ .request_cmd = ETHTOOL_MSG_MSE_GET,
+ .reply_cmd = ETHTOOL_MSG_MSE_GET_REPLY,
+ .hdr_attr = ETHTOOL_A_MSE_HEADER,
+ .req_info_size = sizeof(struct mse_req_info),
+ .reply_data_size = sizeof(struct mse_reply_data),
+
+ .prepare_data = mse_prepare_data,
+ .cleanup_data = mse_cleanup_data,
+ .reply_size = mse_reply_size,
+ .fill_reply = mse_fill_reply,
+};