public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH ethtool v1] ethtool: add --show-mse netlink support
@ 2026-01-13 14:20 Oleksij Rempel
  2026-02-05  8:26 ` Marc Kleine-Budde
  2026-02-14 21:10 ` patchwork-bot+netdevbpf
  0 siblings, 2 replies; 3+ messages in thread
From: Oleksij Rempel @ 2026-01-13 14:20 UTC (permalink / raw)
  To: Michal Kubecek; +Cc: Oleksij Rempel, kernel, netdev

Add support for querying PHY Mean Square Error (MSE) diagnostics via
ETHTOOL_MSG_MSE_GET. This exposes MSE capability information and the
latest available snapshots for supported PHYs.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 Makefile.am      |   1 +
 ethtool.8.in     |  46 ++++++
 ethtool.c        |   7 +
 netlink/extapi.h |   2 +
 netlink/mse.c    | 388 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 444 insertions(+)
 create mode 100644 netlink/mse.c

diff --git a/Makefile.am b/Makefile.am
index c58325c71a82..524b0922fd62 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -55,6 +55,7 @@ ethtool_SOURCES += \
 		  netlink/pse-pd.c \
 		  netlink/phy.c \
 		  netlink/tsconfig.c \
+		  netlink/mse.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/ethtool_netlink_generated.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h \
diff --git a/ethtool.8.in b/ethtool.8.in
index 0aafb4b0321b..e73f8e7cfcca 100644
--- a/ethtool.8.in
+++ b/ethtool.8.in
@@ -639,6 +639,7 @@ lB	l.
 \-\-get\-plca\-status
 \-\-show-pse
 \-\-set-pse
+\-\-show-mse
 .TE
 .TP
 .B \-a \-\-show\-pause
@@ -1977,6 +1978,51 @@ When a single domain exceeds its budget, ports in that domain are
 powered up/down by priority (highest first for power-up; lowest shed
 first).

+.RE
+.TP
+.B \-\-show\-mse
+Show PHY Mean Square Error (MSE) diagnostics.
+.RS 4
+.P
+Metrics follow the OPEN Alliance PHY diagnostics model (used by automotive and
+industrial PHYs). Numeric scaling, sampling windows, and update intervals are
+vendor specific and reported by the capability block.
+.P
+Values are raw snapshots from the PHY DSP. Lower values generally indicate
+better signal quality; 0 is ideal. Interpret values relative to the reported
+max-* scales for this PHY and link mode.
+.P
+The set of returned channels depends on PHY support. When per-channel data is
+available, Channel A/B/C/D nests are returned. Otherwise, the kernel may return
+a single WORST or LINK aggregate snapshot.
+.TP
+.B Capabilities
+Driver-provided scale and timing:
+.RS 4
+.TP
+.B max\-average\-mse
+Scale for average MSE values.
+.TP
+.B max\-peak\-mse
+Scale for peak MSE values. Present only if the PHY reports peak-mse and/or
+worst-peak-mse.
+.TP
+.B refresh\-rate\-ps
+Typical hardware update interval in picoseconds.
+.TP
+.B symbols\-per\-sample
+Number of symbols collected per hardware sample.
+.RE
+.TP
+.B Snapshots
+One nest per selector (Channel A/B/C/D, WORST, or LINK). Each contains the
+metrics supported by the PHY: average-mse and optionally peak-mse and/or
+worst-peak-mse. Values are raw and must be interpreted using the capability
+scales above.
+.P
+Short windows (low refresh-rate-ps or few symbols-per-sample) can yield high
+variance; userspace can improve stability by polling and averaging over time.
+
 .RE
 .TP
 .B \-\-flash\-module\-firmware
diff --git a/ethtool.c b/ethtool.c
index 86214581e55f..c9c15023f8b2 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -6371,6 +6371,13 @@ static const struct option args[] = {
 		.nlfunc	= nl_get_phy,
 		.help	= "List PHYs"
 	},
+	{
+		.opts	= "--show-mse",
+		.targets_phy	= true,
+		.json	= true,
+		.nlfunc	= nl_gmse,
+		.help	= "Show Mean Square Error (MSE) diagnostics",
+	},
 	{
 		.opts	= "-h|--help",
 		.no_dev	= true,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index f2bf422a3f2d..b0e458804297 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -59,6 +59,7 @@ int nl_gpse(struct cmd_context *ctx);
 int nl_spse(struct cmd_context *ctx);
 int nl_flash_module_fw(struct cmd_context *ctx);
 int nl_get_phy(struct cmd_context *ctx);
+int nl_gmse(struct cmd_context *ctx);

 void nl_monitor_usage(void);

@@ -138,6 +139,7 @@ nl_get_eeprom_page(struct cmd_context *ctx __maybe_unused,
 #define nl_spse			NULL
 #define nl_flash_module_fw	NULL
 #define nl_get_phy		NULL
+#define nl_gmse			NULL

 #endif /* ETHTOOL_ENABLE_NETLINK */

diff --git a/netlink/mse.c b/netlink/mse.c
new file mode 100644
index 000000000000..cca0c1f272f5
--- /dev/null
+++ b/netlink/mse.c
@@ -0,0 +1,388 @@
+/*
+ * Implementation of "ethtool --show-mse <dev>"
+ *
+ * Background:
+ * - Kernel MSE GET is defined in Documentation/netlink/specs/ethtool.yaml
+ *   and implemented in net/ethtool/mse.c.
+ * - Capabilities describe scale and timing for MSE readings:
+ *     max-average-mse / max-peak-mse : scale
+ *     refresh-rate-ps                : nominal update interval (picoseconds)
+ *     num-symbols                    : symbols per sample window
+ *   These two timing fields are mandatory in the kernel reply; limits are
+ *   present only when the corresponding metrics are supported.
+ * - Metrics originate from OPEN Alliance PHY diagnostics (100/1000BASE-T1),
+ *   but scaling, windows, and refresh reate are vendor-specific; the
+ *   capability block reports the implementation details provided by the PHY
+ *   driver.
+ * - Snapshots carry per-channel values (A-D, WORST, LINK) chosen by the
+ *   kernel in priority order (per-channel first, else WORST, else LINK).
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+enum mse_attr_kind {
+	MSE_ATTR_HEADER,
+	MSE_ATTR_CAPS,
+	MSE_ATTR_SNAPSHOT,
+	MSE_ATTR_UNKNOWN,
+};
+
+struct mse_field_desc {
+	uint16_t attr;
+	const char *json_key;
+	const char *plain_fmt;
+	bool required;
+};
+
+static const struct mse_field_desc mse_cap_fields[] = {
+	{
+		.attr = ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS,
+		.json_key = "refresh-rate-ps",
+		.plain_fmt = "\tRefresh Rate: %" PRIu64 " ps\n",
+		.required = true,
+	},
+	{
+		.attr = ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS,
+		.json_key = "symbols-per-sample",
+		.plain_fmt = "\tSymbols per Sample: %" PRIu64 "\n",
+		.required = true,
+	},
+	{
+		.attr = ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE,
+		.json_key = "max-average-mse",
+		.plain_fmt = "\tMax Average MSE: %" PRIu64 "\n",
+		.required = false,
+	},
+	{
+		.attr = ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE,
+		.json_key = "max-peak-mse",
+		.plain_fmt = "\tMax Peak MSE: %" PRIu64 "\n",
+		.required = false,
+	},
+};
+
+static const struct mse_field_desc mse_snapshot_fields[] = {
+	{
+		.attr = ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE,
+		.json_key = "average-mse",
+		.plain_fmt = "\tAverage MSE: %" PRIu64 "\n",
+		.required = false,
+	},
+	{
+		.attr = ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE,
+		.json_key = "peak-mse",
+		.plain_fmt = "\tPeak MSE: %" PRIu64 "\n",
+		.required = false,
+	},
+	{
+		.attr = ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE,
+		.json_key = "worst-peak-mse",
+		.plain_fmt = "\tWorst-Peak MSE: %" PRIu64 "\n",
+		.required = false,
+	},
+};
+
+static enum mse_attr_kind mse_classify_attr(uint16_t at)
+{
+	switch (at) {
+	case ETHTOOL_A_MSE_HEADER:
+		return MSE_ATTR_HEADER;
+	case ETHTOOL_A_MSE_CAPABILITIES:
+		return MSE_ATTR_CAPS;
+	case ETHTOOL_A_MSE_CHANNEL_A:
+	case ETHTOOL_A_MSE_CHANNEL_B:
+	case ETHTOOL_A_MSE_CHANNEL_C:
+	case ETHTOOL_A_MSE_CHANNEL_D:
+	case ETHTOOL_A_MSE_WORST_CHANNEL:
+	case ETHTOOL_A_MSE_LINK:
+		return MSE_ATTR_SNAPSHOT;
+	default:
+		return MSE_ATTR_UNKNOWN;
+	}
+}
+
+/* Validate presence (if required) and width of integer attrs, then fetch the
+ * value. The kernel uses nla_put_uint(), which may encode values in
+ * 8/16/32/64-bit payloads; rely on attr_get_uint() for size handling.
+ * @present reports whether the attribute was found.
+ *
+ * Return: 0 on success, -EINVAL/-EMSGSIZE on malformed attributes.
+ */
+static int mse_validate_get_u64_attr(const struct nlattr *attr, const char *name,
+				     bool required, u64 *val, bool *present)
+{
+	if (present)
+		*present = false;
+	if (!attr) {
+		if (required)
+			fprintf(stderr, "warning: missing %s attribute in MSE reply; skipping\n",
+				name);
+		if (val)
+			*val = 0;
+		return 0;
+	}
+
+	*val = attr_get_uint(attr);
+	if (*val == UINT64_MAX) {
+		fprintf(stderr, "invalid %s attribute size in MSE reply\n", name);
+		return -EMSGSIZE;
+	}
+	if (present)
+		*present = true;
+
+	return 0;
+}
+
+static int mse_print_fields(const struct nlattr **tb,
+			    const struct mse_field_desc *fields, size_t n,
+			    bool *has_value)
+{
+	const struct mse_field_desc *f;
+	bool present;
+	u64 val;
+	int ret;
+
+	for (f = fields; f < fields + n; f++) {
+		ret = mse_validate_get_u64_attr(tb[f->attr], f->json_key,
+						f->required, &val, &present);
+		if (ret < 0)
+			return ret;
+		if (present) {
+			print_u64(PRINT_ANY, f->json_key, f->plain_fmt, val);
+			if (has_value)
+				*has_value = true;
+		}
+	}
+
+	return 0;
+}
+
+static const char *mse_get_channel_name(uint16_t channel)
+{
+	switch (channel) {
+	case ETHTOOL_A_MSE_CHANNEL_A:
+		return "a";
+	case ETHTOOL_A_MSE_CHANNEL_B:
+		return "b";
+	case ETHTOOL_A_MSE_CHANNEL_C:
+		return "c";
+	case ETHTOOL_A_MSE_CHANNEL_D:
+		return "d";
+	case ETHTOOL_A_MSE_WORST_CHANNEL:
+		return "worst";
+	case ETHTOOL_A_MSE_LINK:
+		return "link";
+	default:
+		return "unknown";
+	}
+}
+
+static int mse_dump_capabilities(const struct nlattr *cap_attr)
+{
+	const struct nlattr *tb[ETHTOOL_A_MSE_CAPABILITIES_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	bool has_value = false;
+	int ret;
+
+	ret = mnl_attr_parse_nested(cap_attr, attr_cb, &tb_info);
+	if (ret != MNL_CB_OK) {
+		fprintf(stderr, "malformed netlink message (capabilities)\n");
+		return -EINVAL;
+	}
+
+	open_json_object("mse-capabilities");
+	if (!is_json_context())
+		printf("MSE Capabilities:\n");
+
+	/* Kernel sends max-average/peak only if corresponding PHY_MSE_CAP_* bits
+	 * are set; refresh-rate-ps and num-symbols are always present.
+	 */
+	ret = mse_print_fields(tb, mse_cap_fields, ARRAY_SIZE(mse_cap_fields),
+			       &has_value);
+
+	if (!has_value)
+		fprintf(stderr, "warning: kernel returned empty MSE capability block\n");
+
+	close_json_object();
+
+	return ret;
+}
+
+static int mse_dump_snapshot(const struct nlattr *snapshot_attr,
+			     uint16_t channel)
+{
+	const struct nlattr *tb[ETHTOOL_A_MSE_SNAPSHOT_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	const char *channel_name;
+	bool has_value = false;
+	int ret;
+
+	ret = mnl_attr_parse_nested(snapshot_attr, attr_cb, &tb_info);
+	if (ret != MNL_CB_OK) {
+		fprintf(stderr, "malformed netlink message (snapshot)\n");
+		return -EINVAL;
+	}
+
+	channel_name = mse_get_channel_name(channel);
+	print_string(PRINT_ANY, "channel", "\nMSE Snapshot (Channel: %s):\n",
+		     channel_name);
+
+	ret = mse_print_fields(tb, mse_snapshot_fields,
+			       ARRAY_SIZE(mse_snapshot_fields), &has_value);
+	if (ret < 0)
+		return ret;
+
+	if (!has_value)
+		fprintf(stderr, "warning: kernel returned empty MSE snapshot for channel %s\n",
+			channel_name);
+
+	return 0;
+}
+
+static int mse_process_snapshot_attr(const struct nlattr *attr)
+{
+	uint16_t channel = mnl_attr_get_type(attr);
+	int ret;
+
+	open_json_object(NULL);
+
+	ret = mse_dump_snapshot(attr, channel);
+
+	close_json_object();
+
+	return ret;
+}
+
+static int mse_dump_snapshots(const struct nlmsghdr *nlhdr)
+{
+	bool snapshots_started = false;
+	unsigned int unknown_cnt = 0;
+	const struct nlattr *attr;
+	int ret = 0;
+
+	/*
+	 * If the kernel provides no per-channel snapshot nests, still emit an
+	 * empty "mse-snapshots" array in JSON mode. This keeps the JSON schema
+	 * stable for consumers (always an array, possibly empty).
+	 */
+	if (is_json_context())
+		open_json_array("mse-snapshots", NULL);
+
+	/* Kernel already picks per-channel over WORST over LINK; we just dump
+	 * whatever nests are present.
+	 */
+	mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+		uint16_t at = mnl_attr_get_type(attr);
+
+		switch (mse_classify_attr(at)) {
+		case MSE_ATTR_SNAPSHOT:
+			ret = mse_process_snapshot_attr(attr);
+			if (ret < 0)
+				goto out;
+
+			snapshots_started = true;
+
+			break;
+		case MSE_ATTR_UNKNOWN:
+			unknown_cnt++;
+			break;
+		case MSE_ATTR_HEADER:
+		case MSE_ATTR_CAPS:
+		default:
+			break;
+		}
+	}
+
+	if (!snapshots_started)
+		fprintf(stderr, "warning: no MSE snapshot data available from kernel\n");
+
+	if (unknown_cnt)
+		fprintf(stderr, "warning: %u unknown MSE attribute(s) ignored\n",
+			unknown_cnt);
+out:
+	if (is_json_context())
+		close_json_array(NULL);
+
+	return ret;
+}
+
+int mse_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_MSE_MAX + 1] = {};
+	struct nl_context *nlctx = data;
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret != MNL_CB_OK)
+		return -EINVAL;
+
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_MSE_HEADER]);
+	if (!dev_ok(nlctx))
+		return 0;
+
+	open_json_object(NULL);
+	print_string(PRINT_ANY, "ifname", "MSE diagnostics for %s:\n",
+		     nlctx->devname);
+
+	if (tb[ETHTOOL_A_MSE_CAPABILITIES]) {
+		ret = mse_dump_capabilities(tb[ETHTOOL_A_MSE_CAPABILITIES]);
+		if (ret < 0)
+			goto out;
+	} else {
+		fprintf(stderr, "warning: missing MSE capabilities; continuing with snapshots\n");
+	}
+
+	ret = mse_dump_snapshots(nlhdr);
+
+	print_nl();
+out:
+	close_json_object();
+
+	return ret;
+}
+
+int nl_gmse(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nl_msg_buff *msgbuff;
+	struct nl_socket *nlsk;
+	int ret;
+
+	if (netlink_cmd_check(ctx, ETHTOOL_MSG_MSE_GET, true))
+		return -EOPNOTSUPP;
+
+	nlctx->cmd = "--show-mse";
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	nlctx->devname = ctx->devname;
+	nlsk = nlctx->ethnl_socket;
+	msgbuff = &nlsk->msgbuff;
+
+	ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_MSE_GET,
+		       NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return ret;
+	ret = ethnla_fill_header_phy(msgbuff, ETHTOOL_A_MSE_HEADER,
+				     ctx->devname, ctx->phy_index, 0);
+	if (ret < 0)
+		return ret;
+
+	new_json_obj(ctx->json);
+	ret = nlsock_sendmsg(nlsk, NULL);
+	if (ret < 0)
+		goto out;
+	ret = nlsock_process_reply(nlsk, mse_reply_cb, nlctx);
+
+out:
+	delete_json_obj();
+	return ret;
+}
--
2.47.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH ethtool v1] ethtool: add --show-mse netlink support
  2026-01-13 14:20 [PATCH ethtool v1] ethtool: add --show-mse netlink support Oleksij Rempel
@ 2026-02-05  8:26 ` Marc Kleine-Budde
  2026-02-14 21:10 ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 3+ messages in thread
From: Marc Kleine-Budde @ 2026-02-05  8:26 UTC (permalink / raw)
  To: Oleksij Rempel; +Cc: Michal Kubecek, kernel, netdev

On 13.01.2026 15:20:31, Oleksij Rempel wrote:
> Add support for querying PHY Mean Square Error (MSE) diagnostics via
> ETHTOOL_MSG_MSE_GET. This exposes MSE capability information and the
> latest available snapshots for supported PHYs.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>

Can someone look at this or should we re-send after the merge window?

regards,
Marc

-- 
Pengutronix e.K.                 | Marc Kleine-Budde          |
Embedded Linux                   | https://www.pengutronix.de |
Vertretung Nürnberg              | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax:   +49-5121-206917-9   |

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH ethtool v1] ethtool: add --show-mse netlink support
  2026-01-13 14:20 [PATCH ethtool v1] ethtool: add --show-mse netlink support Oleksij Rempel
  2026-02-05  8:26 ` Marc Kleine-Budde
@ 2026-02-14 21:10 ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 3+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-02-14 21:10 UTC (permalink / raw)
  To: Oleksij Rempel; +Cc: mkubecek, kernel, netdev

Hello:

This patch was applied to ethtool/ethtool.git (master)
by Jakub Kicinski <kuba@kernel.org>:

On Tue, 13 Jan 2026 15:20:31 +0100 you wrote:
> Add support for querying PHY Mean Square Error (MSE) diagnostics via
> ETHTOOL_MSG_MSE_GET. This exposes MSE capability information and the
> latest available snapshots for supported PHYs.
> 
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
>  Makefile.am      |   1 +
>  ethtool.8.in     |  46 ++++++
>  ethtool.c        |   7 +
>  netlink/extapi.h |   2 +
>  netlink/mse.c    | 388 +++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 444 insertions(+)
>  create mode 100644 netlink/mse.c
> 
> [...]

Here is the summary with links:
  - [ethtool,v1] ethtool: add --show-mse netlink support
    https://git.kernel.org/pub/scm/network/ethtool/ethtool.git/commit/?id=de3d3c94132c

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-02-14 21:10 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-13 14:20 [PATCH ethtool v1] ethtool: add --show-mse netlink support Oleksij Rempel
2026-02-05  8:26 ` Marc Kleine-Budde
2026-02-14 21:10 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox