From b68aca17006f04962cdca1e9096ed61a1ee9e7ee Mon Sep 17 00:00:00 2001
From: Maxime Bizon <mbizon@freebox.fr>
Date: Mon, 24 Mar 2025 18:28:59 +0100
Subject: [PATCH 8/8] ethtool: use new EEE ioctl to handle link modes bit
 higher than 32

---
 ethtool.c  | 158 +++++++++++++++++++++++++++++++++++++++++++----------
 internal.h |   9 +++
 2 files changed, 139 insertions(+), 28 deletions(-)

diff --git a/ethtool.c b/ethtool.c
index 9b82d64..da3fbe1 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -1665,40 +1665,36 @@ static int dump_rxfhash(int fhash, u64 val)
 	return 0;
 }
 
-static void dump_eeecmd(struct ethtool_eee *ep)
+static void dump_eeecmd(struct ethtool_eee_usettings *ep)
 {
-	ETHTOOL_DECLARE_LINK_MODE_MASK(link_mode);
-
 	fprintf(stdout, "	EEE status: ");
-	if (!ep->supported) {
+	if (!ep->link_modes.supported[0]) {
 		fprintf(stdout, "not supported\n");
 		return;
-	} else if (!ep->eee_enabled) {
+	} else if (!ep->base.eee_enabled) {
 		fprintf(stdout, "disabled\n");
 	} else {
 		fprintf(stdout, "enabled - ");
-		if (ep->eee_active)
+		if (ep->base.eee_active)
 			fprintf(stdout, "active\n");
 		else
 			fprintf(stdout, "inactive\n");
 	}
 
 	fprintf(stdout, "	Tx LPI:");
-	if (ep->tx_lpi_enabled)
-		fprintf(stdout, " %d (us)\n", ep->tx_lpi_timer);
+	if (ep->base.tx_lpi_enabled)
+		fprintf(stdout, " %d (us)\n", ep->base.tx_lpi_timer);
 	else
 		fprintf(stdout, " disabled\n");
 
-	ethtool_link_mode_zero(link_mode);
-
-	link_mode[0] = ep->supported;
-	dump_link_caps("Supported EEE", "", link_mode, 1);
+	dump_link_caps("Supported EEE", "",
+		       ep->link_modes.supported, 1);
 
-	link_mode[0] = ep->advertised;
-	dump_link_caps("Advertised EEE", "", link_mode, 1);
+	dump_link_caps("Advertised EEE", "",
+		       ep->link_modes.advertised, 1);
 
-	link_mode[0] = ep->lp_advertised;
-	dump_link_caps("Link partner advertised EEE", "", link_mode, 1);
+	dump_link_caps("Link partner advertised EEE", "",
+		       ep->link_modes.lp_advertised, 1);
 }
 
 static void dump_fec(u32 fec)
@@ -4811,35 +4807,106 @@ static int do_getmodule(struct cmd_context *ctx)
 	return 0;
 }
 
+static int get_linkmode_words(struct cmd_context *ctx)
+{
+	struct {
+		struct ethtool_link_settings req;
+		__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+	} ecmd;
+	int ret;
+
+	/* Handshake with kernel to determine number of words for link
+	 * mode bitmaps. When requested number of bitmap words is not
+	 * the one expected by kernel, the latter returns the integer
+	 * opposite of what it is expecting. We request length 0 below
+	 * (aka. invalid bitmap length) to get this info.
+	 */
+	memset(&ecmd, 0, sizeof(ecmd));
+	ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
+	ret = send_ioctl(ctx, &ecmd);
+	if (ret < 0)
+		return -1;
+
+	/* see above: we expect a strictly negative value from kernel.
+	 */
+	if (ecmd.req.link_mode_masks_nwords >= 0
+	    || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
+		return -1;
+
+	return -ecmd.req.link_mode_masks_nwords;
+}
+
+
 static int do_geee(struct cmd_context *ctx)
 {
-	struct ethtool_eee eeecmd;
+	struct {
+		struct ethtool_eee_linkmode req;
+		__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+	} eeecmd;
+	struct ethtool_eee_usettings eee_usettings;
+	unsigned int u32_offs;
+	int nwords;
 
 	if (ctx->argc != 0)
 		exit_bad_args();
 
-	eeecmd.cmd = ETHTOOL_GEEE;
+	nwords = get_linkmode_words(ctx);
+	if (nwords < 0)
+		return 1;
+
+	memset(&eeecmd, 0, sizeof(eeecmd));
+	eeecmd.req.cmd = ETHTOOL_GEEE_LINKMODE;
+
 	if (send_ioctl(ctx, &eeecmd)) {
 		perror("Cannot get EEE settings");
 		return 1;
 	}
 
+	if (nwords != eeecmd.req.link_mode_masks_nwords) {
+		perror("invalid link words count");
+		return 1;
+	}
+
+	memset(&eee_usettings, 0, sizeof (eee_usettings));
+	memcpy(&eee_usettings.base, &eeecmd.req, sizeof (eeecmd.req));
+
+	u32_offs = 0;
+        memcpy(eee_usettings.link_modes.supported,
+               &eeecmd.link_mode_data[u32_offs],
+	       4 * nwords);
+
+        u32_offs += nwords;
+        memcpy(eee_usettings.link_modes.advertised,
+               &eeecmd.link_mode_data[u32_offs],
+               4 * nwords);
+
+        u32_offs += nwords;
+        memcpy(eee_usettings.link_modes.lp_advertised,
+               &eeecmd.link_mode_data[u32_offs],
+               4 * nwords);
+
 	fprintf(stdout, "EEE Settings for %s:\n", ctx->devname);
-	dump_eeecmd(&eeecmd);
+	dump_eeecmd(&eee_usettings);
 
 	return 0;
 }
 
 static int do_seee(struct cmd_context *ctx)
 {
-	int adv_c = -1, lpi_c = -1, lpi_time_c = -1, eee_c = -1;
+	struct {
+		struct ethtool_eee_linkmode req;
+		__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+	} eeecmd;
+	int lpi_c = -1, lpi_time_c = -1, eee_c = -1;
 	int change = -1, change2 = 0;
-	struct ethtool_eee eeecmd;
+	int nwords;
+	u32 dummy = 0;
 	struct cmdline_info cmdline_eee[] = {
-		{ "advertise",    CMDL_U32,  &adv_c,       &eeecmd.advertised },
-		{ "tx-lpi",       CMDL_BOOL, &lpi_c,   &eeecmd.tx_lpi_enabled },
-		{ "tx-timer",	  CMDL_U32,  &lpi_time_c, &eeecmd.tx_lpi_timer},
-		{ "eee",	  CMDL_BOOL, &eee_c,	   &eeecmd.eee_enabled},
+		{ "tx-lpi",       CMDL_BOOL, &lpi_c,   &eeecmd.req.tx_lpi_enabled },
+		{ "tx-timer",	  CMDL_U32,  &lpi_time_c, &eeecmd.req.tx_lpi_timer},
+		{ "eee",	  CMDL_BOOL, &eee_c,	   &eeecmd.req.eee_enabled},
+		/* xxx leave last */
+		{ "advertise",       CMDL_U64, &dummy,   &dummy },
 	};
 
 	if (ctx->argc == 0)
@@ -4848,16 +4915,51 @@ static int do_seee(struct cmd_context *ctx)
 	parse_generic_cmdline(ctx, &change, cmdline_eee,
 			      ARRAY_SIZE(cmdline_eee));
 
-	eeecmd.cmd = ETHTOOL_GEEE;
+	nwords = get_linkmode_words(ctx);
+	if (nwords < 0)
+		return 1;
+
+	memset(&eeecmd, 0, sizeof(eeecmd));
+	eeecmd.req.cmd = ETHTOOL_GEEE_LINKMODE;
+
 	if (send_ioctl(ctx, &eeecmd)) {
 		perror("Cannot get EEE settings");
 		return 1;
 	}
 
-	do_generic_set(cmdline_eee, ARRAY_SIZE(cmdline_eee), &change2);
+	if (nwords != eeecmd.req.link_mode_masks_nwords) {
+		perror("invalid link words count");
+		return 1;
+	}
+
+	/* xxx: skip advertise check here */
+	do_generic_set(cmdline_eee, ARRAY_SIZE(cmdline_eee) - 1, &change2);
+
+	for (int i = 0; i < ctx->argc; i++) {
+		if (!strcmp(ctx->argp[i], "advertise")) {
+			ETHTOOL_DECLARE_LINK_MODE_MASK(adv);
+
+			i += 1;
+			if (i >= ctx->argc)
+				exit_bad_args();
+			if (parse_hex_u32_bitmap(
+				    ctx->argp[i],
+				    ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBITS,
+				    adv))
+				exit_bad_args();
+
+			if (memcmp(adv,
+				   &eeecmd.link_mode_data[nwords],
+				   4 * nwords)) {
+				change2 = 2;
+				memcpy(&eeecmd.link_mode_data[nwords],
+				       adv, 4 * nwords);
+			}
+		}
+	}
 
 	if (change2) {
-		eeecmd.cmd = ETHTOOL_SEEE;
+		eeecmd.req.cmd = ETHTOOL_SEEE_LINKMODE;
 		if (send_ioctl(ctx, &eeecmd)) {
 			perror("Cannot set EEE settings");
 			return 1;
diff --git a/internal.h b/internal.h
index ad8800f..d99a78a 100644
--- a/internal.h
+++ b/internal.h
@@ -146,6 +146,15 @@ struct ethtool_link_usettings {
 	} link_modes;
 };
 
+struct ethtool_eee_usettings {
+	struct ethtool_eee_linkmode base;
+	struct {
+		ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+		ETHTOOL_DECLARE_LINK_MODE_MASK(advertised);
+		ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertised);
+	} link_modes;
+};
+
 #define ethtool_link_mode_for_each_u32(index)			\
 	for ((index) = 0;					\
 	     (index) < ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32;	\
-- 
2.34.1

