diff mbox series

[net-next,v2,09/12] ethtool: rss: support dumping RSS contexts

Message ID 20240803042624.970352-10-kuba@kernel.org (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series ethtool: rss: driver tweaks and netlink context dumps | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; GEN HAS DIFF 2 files changed, 119 insertions(+);
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 29 this patch: 29
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 3 maintainers not CCed: kory.maincent@bootlin.com maxime.chevallier@bootlin.com danieller@nvidia.com
netdev/build_clang success Errors and warnings before: 29 this patch: 29
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 29 this patch: 29
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 180 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 1 this patch: 1
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-08-06--00-00 (tests: 707)

Commit Message

Jakub Kicinski Aug. 3, 2024, 4:26 a.m. UTC
Now that we track RSS contexts in the core we can easily dump
them. This is a major introspection improvement, as previously
the only way to find all contexts would be to try all ids
(of which there may be 2^32 - 1).

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 Documentation/netlink/specs/ethtool.yaml |   9 +-
 net/ethtool/netlink.c                    |   2 +
 net/ethtool/netlink.h                    |   2 +
 net/ethtool/rss.c                        | 133 +++++++++++++++++++++++
 4 files changed, 144 insertions(+), 2 deletions(-)

Comments

Joe Damato Aug. 3, 2024, 6:11 p.m. UTC | #1
On Fri, Aug 02, 2024 at 09:26:21PM -0700, Jakub Kicinski wrote:
> Now that we track RSS contexts in the core we can easily dump
> them. This is a major introspection improvement, as previously
> the only way to find all contexts would be to try all ids
> (of which there may be 2^32 - 1).
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---

Thanks for doing this important and extremely useful work. I am
personally very excited to see this data available to userland.

[...]

> diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c
> index 023782ca1230..62e7b6fe605d 100644
> --- a/net/ethtool/rss.c
> +++ b/net/ethtool/rss.c
> @@ -208,6 +208,139 @@ static void rss_cleanup_data(struct ethnl_reply_data *reply_base)
>  	kfree(data->indir_table);
>  }
>  
> +struct rss_nl_dump_ctx {
> +	unsigned long		ifindex;
> +	unsigned long		ctx_idx;
> +
> +	unsigned int		one_ifindex;

My apologies: I'm probably just not familiar enough with the code,
but I'm having a hard time understanding what the purpose of
one_ifindex is.

I read both ethnl_rss_dump_start and ethnl_rss_dumpit, but I'm still
not following what this is used for; it'll probably be obvious in
retrospect once you explain it, but I suppose my feedback is that a
comment or something would be really helpful :)

> +};
> +
> +static struct rss_nl_dump_ctx *rss_dump_ctx(struct netlink_callback *cb)
> +{
> +	NL_ASSERT_DUMP_CTX_FITS(struct rss_nl_dump_ctx);
> +
> +	return (struct rss_nl_dump_ctx *)cb->ctx;
> +}
> +
> +int ethnl_rss_dump_start(struct netlink_callback *cb)
> +{
> +	const struct genl_info *info = genl_info_dump(cb);
> +	struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
> +	struct ethnl_req_info req_info = {};
> +	struct nlattr **tb = info->attrs;
> +	int ret;
> +
> +	/* Filtering by context not supported */
> +	if (tb[ETHTOOL_A_RSS_CONTEXT]) {
> +		NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT]);
> +		return -EINVAL;
> +	}
> +
> +	ret = ethnl_parse_header_dev_get(&req_info,
> +					 tb[ETHTOOL_A_RSS_HEADER],
> +					 sock_net(cb->skb->sk), cb->extack,
> +					 false);
> +	if (req_info.dev) {
> +		ctx->one_ifindex = req_info.dev->ifindex;
> +		ctx->ifindex = ctx->one_ifindex;
> +		ethnl_parse_header_dev_put(&req_info);
> +		req_info.dev = NULL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int
> +rss_dump_one_ctx(struct sk_buff *skb, struct netlink_callback *cb,
> +		 struct net_device *dev, u32 rss_context)
> +{
> +	const struct genl_info *info = genl_info_dump(cb);
> +	struct rss_reply_data data = {};
> +	struct rss_req_info req = {};
> +	void *ehdr;
> +	int ret;
> +
> +	req.rss_context = rss_context;
> +
> +	ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_RSS_GET_REPLY);
> +	if (!ehdr)
> +		return -EMSGSIZE;
> +
> +	ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_RSS_HEADER);
> +	if (ret < 0)
> +		goto err_cancel;
> +
> +	if (!rss_context)
> +		ret = rss_prepare_get(&req, dev, &data, info);
> +	else
> +		ret = rss_prepare_ctx(&req, dev, &data, info);
> +	if (ret)
> +		goto err_cancel;
> +
> +	ret = rss_fill_reply(skb, &req.base, &data.base);
> +	if (ret)
> +		goto err_cleanup;
> +	genlmsg_end(skb, ehdr);
> +
> +	rss_cleanup_data(&data.base);
> +	return 0;
> +
> +err_cleanup:
> +	rss_cleanup_data(&data.base);
> +err_cancel:
> +	genlmsg_cancel(skb, ehdr);
> +	return ret;
> +}
> +
> +static int
> +rss_dump_one_dev(struct sk_buff *skb, struct netlink_callback *cb,
> +		 struct net_device *dev)
> +{
> +	struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
> +	int ret;
> +
> +	if (!dev->ethtool_ops->get_rxfh)
> +		return 0;
> +
> +	if (!ctx->ctx_idx) {
> +		ret = rss_dump_one_ctx(skb, cb, dev, 0);
> +		if (ret)
> +			return ret;
> +		ctx->ctx_idx++;
> +	}
> +
> +	for (; xa_find(&dev->ethtool->rss_ctx, &ctx->ctx_idx,
> +		       ULONG_MAX, XA_PRESENT); ctx->ctx_idx++) {
> +		ret = rss_dump_one_ctx(skb, cb, dev, ctx->ctx_idx);
> +		if (ret)
> +			return ret;
> +	}
> +	ctx->ctx_idx = 0;
> +
> +	return 0;
> +}
> +
> +int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
> +{
> +	struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
> +	struct net *net = sock_net(skb->sk);
> +	struct net_device *dev;
> +	int ret = 0;
> +
> +	rtnl_lock();
> +	for_each_netdev_dump(net, dev, ctx->ifindex) {
> +		if (ctx->one_ifindex && ctx->one_ifindex != ctx->ifindex)
> +			break;
> +
> +		ret = rss_dump_one_dev(skb, cb, dev);
> +		if (ret)
> +			break;
> +	}
> +	rtnl_unlock();
> +
> +	return ret;
> +}
> +
>  const struct ethnl_request_ops ethnl_rss_request_ops = {
>  	.request_cmd		= ETHTOOL_MSG_RSS_GET,
>  	.reply_cmd		= ETHTOOL_MSG_RSS_GET_REPLY,
> -- 
> 2.45.2
>
Jakub Kicinski Aug. 5, 2024, 9:59 p.m. UTC | #2
On Sat, 3 Aug 2024 19:11:28 +0100 Joe Damato wrote:
> > +struct rss_nl_dump_ctx {
> > +	unsigned long		ifindex;
> > +	unsigned long		ctx_idx;
> > +
> > +	unsigned int		one_ifindex;  
> 
> My apologies: I'm probably just not familiar enough with the code,
> but I'm having a hard time understanding what the purpose of
> one_ifindex is.
> 
> I read both ethnl_rss_dump_start and ethnl_rss_dumpit, but I'm still
> not following what this is used for; it'll probably be obvious in
> retrospect once you explain it, but I suppose my feedback is that a
> comment or something would be really helpful :)

Better name would probably help, but can't think of any.

User can (optionally) pass an ifindex/ifname to the dump, to dump
contexts only for the specified ifindex. If they do we "preset"
the ifindex and one_ifindex:

+	if (req_info.dev) {
+		ctx->one_ifindex = req_info.dev->ifindex;
+		ctx->ifindex = ctx->one_ifindex;
+		ethnl_parse_header_dev_put(&req_info);
+		req_info.dev = NULL;
+	}

and then the iteration is stopped after first full pass:

+	rtnl_lock();
+	for_each_netdev_dump(net, dev, ctx->ifindex) {
+		if (ctx->one_ifindex && ctx->one_ifindex != ctx->ifindex)
+			break;

Unfortunately we don't have any best practice for handling filtering 
in dumps. I find this cleaner than approaches I previously tried, but
we'll see if it stands the test of time.

I'll add the following comment:

	/* User wants to dump contexts for one ifindex only */
Joe Damato Aug. 6, 2024, 10:09 a.m. UTC | #3
On Mon, Aug 05, 2024 at 02:59:33PM -0700, Jakub Kicinski wrote:
> On Sat, 3 Aug 2024 19:11:28 +0100 Joe Damato wrote:
> > > +struct rss_nl_dump_ctx {
> > > +	unsigned long		ifindex;
> > > +	unsigned long		ctx_idx;
> > > +
> > > +	unsigned int		one_ifindex;  
> > 
> > My apologies: I'm probably just not familiar enough with the code,
> > but I'm having a hard time understanding what the purpose of
> > one_ifindex is.
> > 
> > I read both ethnl_rss_dump_start and ethnl_rss_dumpit, but I'm still
> > not following what this is used for; it'll probably be obvious in
> > retrospect once you explain it, but I suppose my feedback is that a
> > comment or something would be really helpful :)
> 
> Better name would probably help, but can't think of any.
> 
> User can (optionally) pass an ifindex/ifname to the dump, to dump
> contexts only for the specified ifindex. If they do we "preset"
> the ifindex and one_ifindex:
 
> +	if (req_info.dev) {
> +		ctx->one_ifindex = req_info.dev->ifindex;
> +		ctx->ifindex = ctx->one_ifindex;
> +		ethnl_parse_header_dev_put(&req_info);
> +		req_info.dev = NULL;
> +	}
> 
> and then the iteration is stopped after first full pass:
> 
> +	rtnl_lock();
> +	for_each_netdev_dump(net, dev, ctx->ifindex) {
> +		if (ctx->one_ifindex && ctx->one_ifindex != ctx->ifindex)
> +			break;

Ah, OK; that all makes sense. Thanks for the explanation.

> Unfortunately we don't have any best practice for handling filtering 
> in dumps. I find this cleaner than approaches I previously tried, but
> we'll see if it stands the test of time.
> 
> I'll add the following comment:
> 
> 	/* User wants to dump contexts for one ifindex only */

Sounds good. If you like, you can also add:

Reviewed-by: Joe Damato <jdamato@fastly.com>
Przemek Kitszel Aug. 6, 2024, 10:44 a.m. UTC | #4
On 8/5/24 23:59, Jakub Kicinski wrote:
> On Sat, 3 Aug 2024 19:11:28 +0100 Joe Damato wrote:
>>> +struct rss_nl_dump_ctx {
>>> +	unsigned long		ifindex;
>>> +	unsigned long		ctx_idx;
>>> +
>>> +	unsigned int		one_ifindex;
>>
>> My apologies: I'm probably just not familiar enough with the code,
>> but I'm having a hard time understanding what the purpose of
>> one_ifindex is.
>>
>> I read both ethnl_rss_dump_start and ethnl_rss_dumpit, but I'm still
>> not following what this is used for; it'll probably be obvious in
>> retrospect once you explain it, but I suppose my feedback is that a
>> comment or something would be really helpful :)
> 
> Better name would probably help, but can't think of any.

perhaps:
ifindex -> if_iter
one_ifindex -> if_requested

(I would also like 'ifc' instead of 'if', for the obvious reasons)

> 
> User can (optionally) pass an ifindex/ifname to the dump, to dump
> contexts only for the specified ifindex. If they do we "preset"
> the ifindex and one_ifindex:
> 
> +	if (req_info.dev) {
> +		ctx->one_ifindex = req_info.dev->ifindex;
> +		ctx->ifindex = ctx->one_ifindex;
> +		ethnl_parse_header_dev_put(&req_info);
> +		req_info.dev = NULL;
> +	}
> 
> and then the iteration is stopped after first full pass:
> 
> +	rtnl_lock();
> +	for_each_netdev_dump(net, dev, ctx->ifindex) {
> +		if (ctx->one_ifindex && ctx->one_ifindex != ctx->ifindex)
> +			break;
> 
> Unfortunately we don't have any best practice for handling filtering
> in dumps. I find this cleaner than approaches I previously tried, but
> we'll see if it stands the test of time.

This code is clean, but by just looking at the struct one could not
expect it though :/ Perhaps a rename could help, or just wait until
people learn it (I remember you have recently explained this dump
scheme to me :))

> 
> I'll add the following comment:
> 
> 	/* User wants to dump contexts for one ifindex only */
Edward Cree Aug. 6, 2024, 1:58 p.m. UTC | #5
On 05/08/2024 22:59, Jakub Kicinski wrote:
> On Sat, 3 Aug 2024 19:11:28 +0100 Joe Damato wrote:
>>> +struct rss_nl_dump_ctx {
>>> +	unsigned long		ifindex;
>>> +	unsigned long		ctx_idx;
>>> +
>>> +	unsigned int		one_ifindex;  
>>
>> My apologies: I'm probably just not familiar enough with the code,
>> but I'm having a hard time understanding what the purpose of
>> one_ifindex is.
>>
>> I read both ethnl_rss_dump_start and ethnl_rss_dumpit, but I'm still
>> not following what this is used for; it'll probably be obvious in
>> retrospect once you explain it, but I suppose my feedback is that a
>> comment or something would be really helpful :)
> 
> Better name would probably help, but can't think of any.

'only_ifindex'?  'match_ifindex'?
Jakub Kicinski Aug. 6, 2024, 2:17 p.m. UTC | #6
On Tue, 6 Aug 2024 14:58:59 +0100 Edward Cree wrote:
> > Better name would probably help, but can't think of any.  
> 
> 'only_ifindex'?  'match_ifindex'?

'match' sounds good, I'll use that, thanks!
Edward Cree Aug. 6, 2024, 2:24 p.m. UTC | #7
On 03/08/2024 05:26, Jakub Kicinski wrote:
> Now that we track RSS contexts in the core we can easily dump
> them. This is a major introspection improvement, as previously
> the only way to find all contexts would be to try all ids
> (of which there may be 2^32 - 1).
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
...
> +	if (!ctx->ctx_idx) {
> +		ret = rss_dump_one_ctx(skb, cb, dev, 0);
> +		if (ret)
> +			return ret;
> +		ctx->ctx_idx++;
> +	}

Maybe comment this block with something like "context 0 is
 not stored in the XArray" to make clear why this is split
 out from a loop that looks like it should be able to handle
 it.

> +
> +	for (; xa_find(&dev->ethtool->rss_ctx, &ctx->ctx_idx,
> +		       ULONG_MAX, XA_PRESENT); ctx->ctx_idx++) {
> +		ret = rss_dump_one_ctx(skb, cb, dev, ctx->ctx_idx);
> +		if (ret)
> +			return ret;
> +	}
> +	ctx->ctx_idx = 0;

Feels like there has to be a way to do this with
 xa_for_each_start()?  Something like (untested):

	struct ethtool_rxfh_context *rss_ctx;

	xa_for_each_start(&dev->ethtool->rss_ctx, ctx->ctx_idx,
			  rss_ctx, ctx->ctx_idx) {
		ret = rss_dump_one_ctx(skb, cb, dev, ctx->ctx_idx);
		if (ret)
			return ret;
	}
	ctx->ctx_idx = 0;
Jakub Kicinski Aug. 6, 2024, 3:23 p.m. UTC | #8
On Tue, 6 Aug 2024 15:24:50 +0100 Edward Cree wrote:
> > +	if (!ctx->ctx_idx) {
> > +		ret = rss_dump_one_ctx(skb, cb, dev, 0);
> > +		if (ret)
> > +			return ret;
> > +		ctx->ctx_idx++;
> > +	}  
> 
> Maybe comment this block with something like "context 0 is
>  not stored in the XArray" to make clear why this is split
>  out from a loop that looks like it should be able to handle
>  it.

Will do.

> > +
> > +	for (; xa_find(&dev->ethtool->rss_ctx, &ctx->ctx_idx,
> > +		       ULONG_MAX, XA_PRESENT); ctx->ctx_idx++) {
> > +		ret = rss_dump_one_ctx(skb, cb, dev, ctx->ctx_idx);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +	ctx->ctx_idx = 0;  
> 
> Feels like there has to be a way to do this with
>  xa_for_each_start()?  Something like (untested):

It may work in the current code but I prefer to stay away from xarray
iterators in netlink code. They cause too many bugs. They do not
invalidate / move the index past the end of the array once they are
done. Which means if the dump ever gets called again after finishing
we'll re-dump the last element.
diff mbox series

Patch

diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index ea21fe135b97..cf69eedae51d 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -1749,12 +1749,12 @@  doc: Partial family for Ethtool Netlink.
 
       attribute-set: rss
 
-      do: &rss-get-op
+      do:
         request:
           attributes:
             - header
             - context
-        reply:
+        reply: &rss-reply
           attributes:
             - header
             - context
@@ -1762,6 +1762,11 @@  doc: Partial family for Ethtool Netlink.
             - indir
             - hkey
             - input_xfrm
+      dump:
+        request:
+          attributes:
+            - header
+        reply: *rss-reply
     -
       name: plca-get-cfg
       doc: Get PLCA params.
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index cb1eea00e349..041548e5f5e6 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -1128,6 +1128,8 @@  static const struct genl_ops ethtool_genl_ops[] = {
 	{
 		.cmd	= ETHTOOL_MSG_RSS_GET,
 		.doit	= ethnl_default_doit,
+		.start	= ethnl_rss_dump_start,
+		.dumpit	= ethnl_rss_dumpit,
 		.policy = ethnl_rss_get_policy,
 		.maxattr = ARRAY_SIZE(ethnl_rss_get_policy) - 1,
 	},
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 46ec273a87c5..919371383b23 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -464,6 +464,8 @@  int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info);
 int ethnl_tunnel_info_start(struct netlink_callback *cb);
 int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
 int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info);
+int ethnl_rss_dump_start(struct netlink_callback *cb);
+int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
 
 extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
 extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c
index 023782ca1230..62e7b6fe605d 100644
--- a/net/ethtool/rss.c
+++ b/net/ethtool/rss.c
@@ -208,6 +208,139 @@  static void rss_cleanup_data(struct ethnl_reply_data *reply_base)
 	kfree(data->indir_table);
 }
 
+struct rss_nl_dump_ctx {
+	unsigned long		ifindex;
+	unsigned long		ctx_idx;
+
+	unsigned int		one_ifindex;
+};
+
+static struct rss_nl_dump_ctx *rss_dump_ctx(struct netlink_callback *cb)
+{
+	NL_ASSERT_DUMP_CTX_FITS(struct rss_nl_dump_ctx);
+
+	return (struct rss_nl_dump_ctx *)cb->ctx;
+}
+
+int ethnl_rss_dump_start(struct netlink_callback *cb)
+{
+	const struct genl_info *info = genl_info_dump(cb);
+	struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
+	struct ethnl_req_info req_info = {};
+	struct nlattr **tb = info->attrs;
+	int ret;
+
+	/* Filtering by context not supported */
+	if (tb[ETHTOOL_A_RSS_CONTEXT]) {
+		NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT]);
+		return -EINVAL;
+	}
+
+	ret = ethnl_parse_header_dev_get(&req_info,
+					 tb[ETHTOOL_A_RSS_HEADER],
+					 sock_net(cb->skb->sk), cb->extack,
+					 false);
+	if (req_info.dev) {
+		ctx->one_ifindex = req_info.dev->ifindex;
+		ctx->ifindex = ctx->one_ifindex;
+		ethnl_parse_header_dev_put(&req_info);
+		req_info.dev = NULL;
+	}
+
+	return ret;
+}
+
+static int
+rss_dump_one_ctx(struct sk_buff *skb, struct netlink_callback *cb,
+		 struct net_device *dev, u32 rss_context)
+{
+	const struct genl_info *info = genl_info_dump(cb);
+	struct rss_reply_data data = {};
+	struct rss_req_info req = {};
+	void *ehdr;
+	int ret;
+
+	req.rss_context = rss_context;
+
+	ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_RSS_GET_REPLY);
+	if (!ehdr)
+		return -EMSGSIZE;
+
+	ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_RSS_HEADER);
+	if (ret < 0)
+		goto err_cancel;
+
+	if (!rss_context)
+		ret = rss_prepare_get(&req, dev, &data, info);
+	else
+		ret = rss_prepare_ctx(&req, dev, &data, info);
+	if (ret)
+		goto err_cancel;
+
+	ret = rss_fill_reply(skb, &req.base, &data.base);
+	if (ret)
+		goto err_cleanup;
+	genlmsg_end(skb, ehdr);
+
+	rss_cleanup_data(&data.base);
+	return 0;
+
+err_cleanup:
+	rss_cleanup_data(&data.base);
+err_cancel:
+	genlmsg_cancel(skb, ehdr);
+	return ret;
+}
+
+static int
+rss_dump_one_dev(struct sk_buff *skb, struct netlink_callback *cb,
+		 struct net_device *dev)
+{
+	struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
+	int ret;
+
+	if (!dev->ethtool_ops->get_rxfh)
+		return 0;
+
+	if (!ctx->ctx_idx) {
+		ret = rss_dump_one_ctx(skb, cb, dev, 0);
+		if (ret)
+			return ret;
+		ctx->ctx_idx++;
+	}
+
+	for (; xa_find(&dev->ethtool->rss_ctx, &ctx->ctx_idx,
+		       ULONG_MAX, XA_PRESENT); ctx->ctx_idx++) {
+		ret = rss_dump_one_ctx(skb, cb, dev, ctx->ctx_idx);
+		if (ret)
+			return ret;
+	}
+	ctx->ctx_idx = 0;
+
+	return 0;
+}
+
+int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
+	struct net *net = sock_net(skb->sk);
+	struct net_device *dev;
+	int ret = 0;
+
+	rtnl_lock();
+	for_each_netdev_dump(net, dev, ctx->ifindex) {
+		if (ctx->one_ifindex && ctx->one_ifindex != ctx->ifindex)
+			break;
+
+		ret = rss_dump_one_dev(skb, cb, dev);
+		if (ret)
+			break;
+	}
+	rtnl_unlock();
+
+	return ret;
+}
+
 const struct ethnl_request_ops ethnl_rss_request_ops = {
 	.request_cmd		= ETHTOOL_MSG_RSS_GET,
 	.reply_cmd		= ETHTOOL_MSG_RSS_GET_REPLY,