Netdev List
 help / color / mirror / Atom feed
From: Gal Pressman <gal@nvidia.com>
To: <netdev@vger.kernel.org>
Cc: Michal Kubecek <mkubecek@suse.cz>,
	Jakub Kicinski <kuba@kernel.org>, "Gal Pressman" <gal@nvidia.com>,
	Nimrod Oren <noren@nvidia.com>
Subject: [PATCH ethtool-next] netlink: rss: Add set support
Date: Tue, 7 Jul 2026 09:00:52 +0300	[thread overview]
Message-ID: <20260707060052.3523136-1-gal@nvidia.com> (raw)

The kernel has exposed ETHTOOL_MSG_RSS_SET, RSS_CREATE_ACT and
RSS_DELETE_ACT since v6.17, but ethtool's -X has remained ioctl only.
Add nl_srss() so the command goes through netlink by default, the ioctl
path remains as fallback for older kernels.

All CLI keywords accepted by the ioctl path are handled. nl_srss()
dispatches by parsed args:

  context new        -> ETHTOOL_MSG_RSS_CREATE_ACT
  context N delete   -> ETHTOOL_MSG_RSS_DELETE_ACT
  otherwise          -> ETHTOOL_MSG_RSS_SET

Reviewed-by: Nimrod Oren <noren@nvidia.com>
Signed-off-by: Gal Pressman <gal@nvidia.com>
---
 ethtool.c        |  10 +-
 internal.h       |   5 +
 netlink/extapi.h |   2 +
 netlink/rss.c    | 510 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 522 insertions(+), 5 deletions(-)

diff --git a/ethtool.c b/ethtool.c
index 3076c128bb34..c8b6a38c288d 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -1207,8 +1207,7 @@ err:
 	return 2;
 }
 
-static int parse_hkey(char **rss_hkey, u32 key_size,
-		      const char *rss_hkey_string)
+int parse_hkey(char **rss_hkey, u32 key_size, const char *rss_hkey_string)
 {
 	if (!key_size) {
 		fprintf(stderr,
@@ -4207,9 +4206,9 @@ out:
 	return 0;
 }
 
-static int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
-			    int rxfhindir_start, int rxfhindir_equal,
-			    char **rxfhindir_weight, u32 num_weights)
+int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
+		     int rxfhindir_start, int rxfhindir_equal,
+		     char **rxfhindir_weight, u32 num_weights)
 {
 	u32 i;
 
@@ -6215,6 +6214,7 @@ static const struct option args[] = {
 	{
 		.opts	= "-X|--set-rxfh-indir|--rxfh",
 		.func	= do_srxfh,
+		.nlfunc	= nl_srss,
 		.help	= "Set Rx flow hash indirection table and/or RSS hash key",
 		.xhelp	= "		[ context %d|new ]\n"
 			  "		[ equal N | weight W0 W1 ... | default ]\n"
diff --git a/internal.h b/internal.h
index 19fff417ab0f..50d43ccd844a 100644
--- a/internal.h
+++ b/internal.h
@@ -286,6 +286,11 @@ int test_fclose(FILE *fh);
 int ioctl_init(struct cmd_context *ctx, bool no_dev);
 int send_ioctl(struct cmd_context *ctx, void *cmd);
 
+int parse_hkey(char **rss_hkey, u32 key_size, const char *rss_hkey_string);
+int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
+		     int rxfhindir_start, int rxfhindir_equal,
+		     char **rxfhindir_weight, u32 num_weights);
+
 void dump_hex(FILE *f, const u8 *data, int len, int offset);
 
 /* National Semiconductor DP83815, DP83816 */
diff --git a/netlink/extapi.h b/netlink/extapi.h
index b0e458804297..e10af6230bb3 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -50,6 +50,7 @@ int nl_smodule(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 int nl_getmodule(struct cmd_context *ctx);
 int nl_grss(struct cmd_context *ctx);
+int nl_srss(struct cmd_context *ctx);
 int nl_plca_get_cfg(struct cmd_context *ctx);
 int nl_plca_set_cfg(struct cmd_context *ctx);
 int nl_plca_get_status(struct cmd_context *ctx);
@@ -130,6 +131,7 @@ nl_get_eeprom_page(struct cmd_context *ctx __maybe_unused,
 #define nl_gmodule		NULL
 #define nl_smodule		NULL
 #define nl_grss			NULL
+#define nl_srss			NULL
 #define nl_plca_get_cfg		NULL
 #define nl_plca_set_cfg		NULL
 #define nl_plca_get_status	NULL
diff --git a/netlink/rss.c b/netlink/rss.c
index 83cc50416dc7..c0fbba8bc2ac 100644
--- a/netlink/rss.c
+++ b/netlink/rss.c
@@ -4,7 +4,10 @@
  * Implementation of "ethtool -x <dev>"
  */
 
+#include <ctype.h>
 #include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 
@@ -246,3 +249,510 @@ int nl_grss(struct cmd_context *ctx)
 err:
 	return nlctx->exit_code ?: 1;
 }
+
+/* RSS_SET */
+enum {
+	SRSS_PARAM_EQUAL,
+	SRSS_PARAM_WEIGHT,
+	SRSS_PARAM_START,
+	SRSS_PARAM_DEFAULT,
+	SRSS_PARAM_HKEY,
+	SRSS_PARAM_HFUNC,
+	SRSS_PARAM_XFRM,
+	SRSS_PARAM_CONTEXT,
+	SRSS_PARAM_DELETE,
+};
+
+struct srss_cmd {
+	unsigned long	present;
+	u32		equal;
+	u32		start;
+	char		**weight;
+	u32		num_weights;
+	char		*hkey;
+	char		*hfunc;
+	u32		input_xfrm;
+	u32		context;
+};
+
+#define SRSS_HAS(cmd, p)	((cmd)->present & (1UL << (p)))
+
+struct rss_sizes {
+	u32	indir_size;
+	u32	key_size;
+};
+
+static int rss_sizes_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_RSS_MAX + 1] = {};
+	struct rss_sizes *sizes = data;
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return MNL_CB_ERROR;
+
+	if (tb[ETHTOOL_A_RSS_INDIR])
+		sizes->indir_size =
+			mnl_attr_get_payload_len(tb[ETHTOOL_A_RSS_INDIR]) /
+			sizeof(u32);
+
+	if (tb[ETHTOOL_A_RSS_HKEY])
+		sizes->key_size =
+			mnl_attr_get_payload_len(tb[ETHTOOL_A_RSS_HKEY]);
+
+	return MNL_CB_OK;
+}
+
+static int rss_get_sizes(struct nl_context *nlctx, u32 *indir_size,
+			 u32 *key_size)
+{
+	struct nl_socket *nlsk = nlctx->ethnl_socket;
+	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+	struct rss_sizes sizes = {};
+	int ret;
+
+	ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_RSS_GET,
+		       NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return ret;
+
+	if (ethnla_fill_header(msgbuff, ETHTOOL_A_RSS_HEADER,
+			       nlctx->devname, 0))
+		return -EMSGSIZE;
+
+	ret = nlsock_sendmsg(nlsk, NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = nlsock_process_reply(nlsk, rss_sizes_reply_cb, &sizes);
+	if (ret < 0)
+		return ret;
+
+	*indir_size = sizes.indir_size;
+	*key_size = sizes.key_size;
+	return 0;
+}
+
+static int rss_resolve_hfunc(struct nl_context *nlctx, const char *name,
+			     u32 *hfunc)
+{
+	const struct stringset *hash_funcs;
+	unsigned int i, count;
+	int ret;
+
+	ret = netlink_init_ethnl2_socket(nlctx);
+	if (ret < 0) {
+		fprintf(stderr, "Cannot get hash function names\n");
+		return ret;
+	}
+
+	hash_funcs = global_stringset(ETH_SS_RSS_HASH_FUNCS,
+				      nlctx->ethnl2_socket);
+	if (!hash_funcs) {
+		fprintf(stderr, "Cannot get hash function names\n");
+		return -ENOENT;
+	}
+
+	count = get_count(hash_funcs);
+	for (i = 0; i < count; i++) {
+		if (!strcmp(get_string(hash_funcs, i), name)) {
+			*hfunc = (u32)1 << i;
+			return 0;
+		}
+	}
+
+	fprintf(stderr, "Unknown hash function: %s\n", name);
+	return -EINVAL;
+}
+
+static int rss_create_reply_cb(const struct nlmsghdr *nlhdr,
+			       void *data __maybe_unused)
+{
+	const struct nlattr *tb[ETHTOOL_A_RSS_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return MNL_CB_ERROR;
+
+	if (tb[ETHTOOL_A_RSS_CONTEXT])
+		printf("New RSS context is %u\n",
+		       mnl_attr_get_u32(tb[ETHTOOL_A_RSS_CONTEXT]));
+
+	return MNL_CB_OK;
+}
+
+static int srss_parse_weight(struct nl_context *nlctx,
+			     uint16_t type __maybe_unused,
+			     const void *data __maybe_unused,
+			     struct nl_msg_buff *msgbuff __maybe_unused,
+			     void *dest)
+{
+	struct srss_cmd *cmd = dest;
+
+	cmd->weight = nlctx->argp;
+	while (nlctx->argc > 0 &&
+	       isdigit((unsigned char)(*nlctx->argp)[0])) {
+		nlctx->argp++;
+		nlctx->argc--;
+		cmd->num_weights++;
+	}
+
+	if (cmd->num_weights == 0) {
+		fprintf(stderr, "'weight' requires at least one value\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct lookup_entry_u32 srss_xfrm_values[] = {
+	{ .arg = "none",             .val = 0 },
+	{ .arg = "symmetric-xor",    .val = RXH_XFRM_SYM_XOR },
+	{ .arg = "symmetric-or-xor", .val = RXH_XFRM_SYM_OR_XOR },
+	{}
+};
+
+static int srss_parse_context(struct nl_context *nlctx,
+			      uint16_t type __maybe_unused,
+			      const void *data __maybe_unused,
+			      struct nl_msg_buff *msgbuff __maybe_unused,
+			      void *dest)
+{
+	struct srss_cmd *cmd = dest;
+	const char *arg = *nlctx->argp;
+
+	nlctx->argp++;
+	nlctx->argc--;
+
+	if (!strcmp(arg, "new")) {
+		cmd->context = ETH_RXFH_CONTEXT_ALLOC;
+	} else {
+		u32 v;
+
+		if (parse_u32(arg, &v) ||
+		    v < 1 || v > ETH_RXFH_CONTEXT_ALLOC - 1) {
+			fprintf(stderr, "Invalid 'context' value\n");
+			return -EINVAL;
+		}
+		cmd->context = v;
+	}
+
+	return 0;
+}
+
+/* "default" and "delete" are flags with no extra arg, just being seen
+ * (recorded in cmd->present) is enough.
+ */
+static int srss_parse_flag(struct nl_context *nlctx __maybe_unused,
+			   uint16_t type __maybe_unused,
+			   const void *data __maybe_unused,
+			   struct nl_msg_buff *msgbuff __maybe_unused,
+			   void *dest __maybe_unused)
+{
+	return 0;
+}
+
+static const struct param_parser srss_params[] = {
+	[SRSS_PARAM_EQUAL] = {
+		.arg		= "equal",
+		.handler	= nl_parse_direct_u32,
+		.dest_offset	= offsetof(struct srss_cmd, equal),
+		.min_argc	= 1,
+		.alt_group	= 1,
+	},
+	[SRSS_PARAM_WEIGHT] = {
+		.arg		= "weight",
+		.handler	= srss_parse_weight,
+		.min_argc	= 1,
+		.alt_group	= 1,
+	},
+	[SRSS_PARAM_START] = {
+		.arg		= "start",
+		.handler	= nl_parse_direct_u32,
+		.dest_offset	= offsetof(struct srss_cmd, start),
+		.min_argc	= 1,
+	},
+	[SRSS_PARAM_DEFAULT] = {
+		.arg		= "default",
+		.handler	= srss_parse_flag,
+		.alt_group	= 1,
+	},
+	[SRSS_PARAM_HKEY] = {
+		.arg		= "hkey",
+		.handler	= nl_parse_string,
+		.dest_offset	= offsetof(struct srss_cmd, hkey),
+		.min_argc	= 1,
+	},
+	[SRSS_PARAM_HFUNC] = {
+		.arg		= "hfunc",
+		.handler	= nl_parse_string,
+		.dest_offset	= offsetof(struct srss_cmd, hfunc),
+		.min_argc	= 1,
+	},
+	[SRSS_PARAM_XFRM] = {
+		.arg		= "xfrm",
+		.handler	= nl_parse_lookup_u32,
+		.handler_data	= srss_xfrm_values,
+		.dest_offset	= offsetof(struct srss_cmd, input_xfrm),
+		.min_argc	= 1,
+	},
+	[SRSS_PARAM_CONTEXT] = {
+		.arg		= "context",
+		.handler	= srss_parse_context,
+		.min_argc	= 1,
+	},
+	[SRSS_PARAM_DELETE] = {
+		.arg		= "delete",
+		.handler	= srss_parse_flag,
+		.alt_group	= 1,
+	},
+	{}
+};
+
+static int srss_validate_cmd(const struct srss_cmd *cmd)
+{
+	if (SRSS_HAS(cmd, SRSS_PARAM_EQUAL) &&
+	    (cmd->equal < 1 || cmd->equal > INT_MAX)) {
+		fprintf(stderr, "Invalid 'equal' value\n");
+		return -EINVAL;
+	}
+
+	if (SRSS_HAS(cmd, SRSS_PARAM_START) && cmd->start > INT_MAX) {
+		fprintf(stderr, "Invalid 'start' value\n");
+		return -EINVAL;
+	}
+
+	if (SRSS_HAS(cmd, SRSS_PARAM_DELETE)) {
+		static const struct {
+			unsigned int	param;
+			const char	*name;
+		} delete_forbidden[] = {
+			{ SRSS_PARAM_HKEY,  "hkey"  },
+			{ SRSS_PARAM_HFUNC, "hfunc" },
+			{ SRSS_PARAM_XFRM,  "xfrm"  },
+			{ SRSS_PARAM_START, "start" },
+		};
+		unsigned int i;
+
+		if (!SRSS_HAS(cmd, SRSS_PARAM_CONTEXT)) {
+			fprintf(stderr,
+				"Delete option requires context option\n");
+			return -EINVAL;
+		}
+		if (cmd->context == ETH_RXFH_CONTEXT_ALLOC) {
+			fprintf(stderr,
+				"Delete and 'context new' are mutually exclusive\n");
+			return -EINVAL;
+		}
+		for (i = 0; i < ARRAY_SIZE(delete_forbidden); i++) {
+			if (SRSS_HAS(cmd, delete_forbidden[i].param)) {
+				fprintf(stderr,
+					"Delete and %s options are mutually exclusive\n",
+					delete_forbidden[i].name);
+				return -EINVAL;
+			}
+		}
+	}
+
+	if (SRSS_HAS(cmd, SRSS_PARAM_START) &&
+	    SRSS_HAS(cmd, SRSS_PARAM_DEFAULT)) {
+		fprintf(stderr,
+			"Start and default options are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	if (SRSS_HAS(cmd, SRSS_PARAM_START) &&
+	    !(SRSS_HAS(cmd, SRSS_PARAM_EQUAL) ||
+	      SRSS_HAS(cmd, SRSS_PARAM_WEIGHT))) {
+		fprintf(stderr,
+			"Start must be used with equal or weight options\n");
+		return -EINVAL;
+	}
+
+	if (SRSS_HAS(cmd, SRSS_PARAM_DEFAULT) &&
+	    SRSS_HAS(cmd, SRSS_PARAM_CONTEXT)) {
+		fprintf(stderr,
+			"Default and context options are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int nl_srss(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	u32 indir_size = 0, key_size = 0;
+	struct nl_msg_buff *msgbuff;
+	struct srss_cmd cmd = {};
+	struct nl_socket *nlsk;
+	unsigned int msg_type;
+	char *hkey = NULL;
+	u32 *indir = NULL;
+	u32 hfunc_val = 0;
+	int ret;
+
+	if (netlink_cmd_check(ctx, ETHTOOL_MSG_RSS_SET, false))
+		return -EOPNOTSUPP;
+
+	if (!ctx->argc) {
+		fprintf(stderr, "ethtool (-X): parameters missing\n");
+		ret = 1;
+		goto out;
+	}
+
+	nlctx->cmd = "-X";
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	nlctx->devname = ctx->devname;
+	nlsk = nlctx->ethnl_socket;
+	msgbuff = &nlsk->msgbuff;
+
+	ret = nl_parser(nlctx, srss_params, &cmd, PARSER_GROUP_NONE, NULL);
+	if (ret < 0)
+		goto out;
+
+	ret = srss_validate_cmd(&cmd);
+	if (ret)
+		goto out;
+
+	if (SRSS_HAS(&cmd, SRSS_PARAM_DELETE))
+		msg_type = ETHTOOL_MSG_RSS_DELETE_ACT;
+	else if (cmd.context == ETH_RXFH_CONTEXT_ALLOC)
+		msg_type = ETHTOOL_MSG_RSS_CREATE_ACT;
+	else
+		msg_type = ETHTOOL_MSG_RSS_SET;
+
+	if (msg_type != ETHTOOL_MSG_RSS_SET &&
+	    netlink_cmd_check(ctx, msg_type, false))
+		return -EOPNOTSUPP;
+
+	if (SRSS_HAS(&cmd, SRSS_PARAM_HKEY) ||
+	    SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ||
+	    SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ||
+	    SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT)) {
+		ret = rss_get_sizes(nlctx, &indir_size, &key_size);
+		if (ret < 0)
+			goto out;
+	}
+
+	if (indir_size == 0 && (SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ||
+				SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ||
+				SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT))) {
+		fprintf(stderr,
+			"Device does not support RX indirection table\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = msg_init(nlctx, msgbuff, msg_type, NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		goto out;
+
+	if (ethnla_fill_header(msgbuff, ETHTOOL_A_RSS_HEADER,
+			       ctx->devname, 0)) {
+		ret = -EMSGSIZE;
+		goto out;
+	}
+
+	if (msg_type == ETHTOOL_MSG_RSS_DELETE_ACT) {
+		if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_CONTEXT,
+				   cmd.context)) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+		goto send;
+	}
+
+	/* For SET: include context id if targeting a non-default context.
+	 * For CREATE: omit context (kernel auto-allocates).
+	 */
+	if (msg_type == ETHTOOL_MSG_RSS_SET &&
+	    SRSS_HAS(&cmd, SRSS_PARAM_CONTEXT)) {
+		if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_CONTEXT,
+				   cmd.context)) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+	}
+
+	if (SRSS_HAS(&cmd, SRSS_PARAM_HKEY)) {
+		ret = parse_hkey(&hkey, key_size, cmd.hkey);
+		if (ret)
+			goto out;
+
+		if (ethnla_put(msgbuff, ETHTOOL_A_RSS_HKEY, key_size, hkey)) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+	}
+
+	if (SRSS_HAS(&cmd, SRSS_PARAM_HFUNC)) {
+		ret = rss_resolve_hfunc(nlctx, cmd.hfunc, &hfunc_val);
+		if (ret < 0)
+			goto out;
+
+		if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_HFUNC, hfunc_val)) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+	}
+
+	if (SRSS_HAS(&cmd, SRSS_PARAM_XFRM)) {
+		if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_INPUT_XFRM,
+				   cmd.input_xfrm)) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+	}
+
+	if (SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ||
+	    SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ||
+	    SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT)) {
+		u32 table_size = indir_size;
+
+		indir = calloc(indir_size, sizeof(*indir));
+		if (!indir) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		ret = fill_indir_table(
+			&table_size, indir, SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT),
+			SRSS_HAS(&cmd, SRSS_PARAM_START) ? cmd.start : 0,
+			SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ? cmd.equal : 0,
+			SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ? cmd.weight : NULL,
+			cmd.num_weights);
+		if (ret)
+			goto out;
+
+		/* fill_indir_table() sets table_size to 0 to signal "reset to
+		 * default" (sent as a zero-length INDIR attribute, which the
+		 * kernel only accepts on the default context).
+		 */
+		if (ethnla_put(msgbuff, ETHTOOL_A_RSS_INDIR,
+			       table_size * sizeof(*indir), indir)) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+	}
+
+send:
+	ret = nlsock_sendmsg(nlsk, NULL);
+	if (ret < 0)
+		goto out;
+
+	if (msg_type == ETHTOOL_MSG_RSS_CREATE_ACT)
+		ret = nlsock_process_reply(nlsk, rss_create_reply_cb, nlctx);
+	else
+		ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+
+out:
+	free(indir);
+	free(hkey);
+	return ret < 0 ? 1 : ret;
+}
-- 
2.54.0


                 reply	other threads:[~2026-07-07  6:01 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260707060052.3523136-1-gal@nvidia.com \
    --to=gal@nvidia.com \
    --cc=kuba@kernel.org \
    --cc=mkubecek@suse.cz \
    --cc=netdev@vger.kernel.org \
    --cc=noren@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox