[PATCH net-next 3/7] net: ethtool: netlink: Introduce command-specific dump_one_dev

From: Maxime Chevallier
Date: Wed Mar 05 2025 - 09:21:07 EST


Prepare more generic ethnl DUMP hanldling, by allowing netlink commands
to register their own dump_one_dev() callback. This avoids having to
roll with a fully custom genl ->dumpit callback, allowing the re-use of some
ethnl plumbing.

Fallback to the default dump_one_dev behaviour when no custom callback
is found.

The command dump context is maintained within the ethnl_dump_ctx, that
we move in netlink.h so that command handlers can access it.

This context can be allocated/freed in new ->dump_start() and
->dump_done() callbacks.

Signed-off-by: Maxime Chevallier <maxime.chevallier@xxxxxxxxxxx>
---
net/ethtool/netlink.c | 58 +++++++++++++++++++++++++------------------
net/ethtool/netlink.h | 35 ++++++++++++++++++++++++++
2 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6dddaea2babb..c0215f4acc05 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -336,24 +336,6 @@ int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)

/* GET request helpers */

-/**
- * struct ethnl_dump_ctx - context structure for generic dumpit() callback
- * @ops: request ops of currently processed message type
- * @req_info: parsed request header of processed request
- * @reply_data: data needed to compose the reply
- * @pos_ifindex: saved iteration position - ifindex
- *
- * These parameters are kept in struct netlink_callback as context preserved
- * between iterations. They are initialized by ethnl_default_start() and used
- * in ethnl_default_dumpit() and ethnl_default_done().
- */
-struct ethnl_dump_ctx {
- const struct ethnl_request_ops *ops;
- struct ethnl_req_info *req_info;
- struct ethnl_reply_data *reply_data;
- unsigned long pos_ifindex;
-};
-
static const struct ethnl_request_ops *
ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_STRSET_GET] = &ethnl_strset_request_ops,
@@ -533,9 +515,9 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
return ret;
}

-static int ethnl_default_dump_one_dev(struct sk_buff *skb, struct net_device *dev,
- const struct ethnl_dump_ctx *ctx,
- const struct genl_info *info)
+static int ethnl_default_dump_one(struct sk_buff *skb,
+ const struct ethnl_dump_ctx *ctx,
+ const struct genl_info *info)
{
void *ehdr;
int ret;
@@ -546,13 +528,13 @@ static int ethnl_default_dump_one_dev(struct sk_buff *skb, struct net_device *de
if (!ehdr)
return -EMSGSIZE;

- ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
rtnl_lock();
ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info);
rtnl_unlock();
if (ret < 0)
goto out;
- ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr);
+ ret = ethnl_fill_reply_header(skb, ctx->reply_data->dev,
+ ctx->ops->hdr_attr);
if (ret < 0)
goto out;
ret = ctx->ops->fill_reply(skb, ctx->req_info, ctx->reply_data);
@@ -560,11 +542,29 @@ static int ethnl_default_dump_one_dev(struct sk_buff *skb, struct net_device *de
out:
if (ctx->ops->cleanup_data)
ctx->ops->cleanup_data(ctx->reply_data);
- ctx->reply_data->dev = NULL;
+
if (ret < 0)
genlmsg_cancel(skb, ehdr);
else
genlmsg_end(skb, ehdr);
+
+ return ret;
+}
+
+static int ethnl_default_dump_one_dev(struct sk_buff *skb, struct net_device *dev,
+ struct ethnl_dump_ctx *ctx,
+ const struct genl_info *info)
+{
+ int ret;
+
+ ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
+
+ if (ctx->ops->dump_one_dev)
+ ret = ctx->ops->dump_one_dev(skb, ctx, info);
+ else
+ ret = ethnl_default_dump_one(skb, ctx, info);
+
+ ctx->reply_data->dev = NULL;
return ret;
}

@@ -593,6 +593,7 @@ static int ethnl_default_dumpit(struct sk_buff *skb,
dev_hold(dev);
rcu_read_unlock();

+ ctx->req_info->dev = dev;
ret = ethnl_default_dump_one_dev(skb, dev, ctx,
genl_info_dump(cb));

@@ -655,6 +656,12 @@ static int ethnl_default_start(struct netlink_callback *cb)
ctx->reply_data = reply_data;
ctx->pos_ifindex = 0;

+ if (ctx->ops->dump_start) {
+ ret = ctx->ops->dump_start(ctx);
+ if (ret)
+ goto free_reply_data;
+ }
+
return 0;

free_reply_data:
@@ -670,6 +677,9 @@ static int ethnl_default_done(struct netlink_callback *cb)
{
struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);

+ if (ctx->ops->dump_done)
+ ctx->ops->dump_done(ctx);
+
kfree(ctx->reply_data);
kfree(ctx->req_info);

diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 4db27182741f..d7506b08e5d6 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -307,6 +307,26 @@ struct ethnl_reply_data {
struct net_device *dev;
};

+/**
+ * struct ethnl_dump_ctx - context structure for generic dumpit() callback
+ * @ops: request ops of currently processed message type
+ * @req_info: parsed request header of processed request
+ * @reply_data: data needed to compose the reply
+ * @pos_ifindex: saved iteration position - ifindex
+ * @cmd_ctx: command-specific context to maintain across the dump.
+ *
+ * These parameters are kept in struct netlink_callback as context preserved
+ * between iterations. They are initialized by ethnl_default_start() and used
+ * in ethnl_default_dumpit() and ethnl_default_done().
+ */
+struct ethnl_dump_ctx {
+ const struct ethnl_request_ops *ops;
+ struct ethnl_req_info *req_info;
+ struct ethnl_reply_data *reply_data;
+ unsigned long pos_ifindex;
+ void *cmd_ctx;
+};
+
int ethnl_ops_begin(struct net_device *dev);
void ethnl_ops_complete(struct net_device *dev);

@@ -372,6 +392,15 @@ int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
* - 0 if no configuration has changed
* - 1 if configuration changed and notification should be generated
* - negative errno on errors
+ * @dump_start:
+ * Optional callback to prepare a dump operation, should there be a need
+ * to maintain some context across the dump.
+ * @dump_one_dev;
+ * Optional callback to generate all messages for a given netdev. This
+ * is relevant only when a request can produce different results for the
+ * same netdev depending on command-specific attributes.
+ * @dump_done:
+ * Optional callback to cleanup any context allocated in ->dump_start()
*
* Description of variable parts of GET request handling when using the
* unified infrastructure. When used, a pointer to an instance of this
@@ -408,6 +437,12 @@ struct ethnl_request_ops {
struct genl_info *info);
int (*set)(struct ethnl_req_info *req_info,
struct genl_info *info);
+
+ int (*dump_start)(struct ethnl_dump_ctx *ctx);
+ int (*dump_one_dev)(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ const struct genl_info *info);
+ void (*dump_done)(struct ethnl_dump_ctx *ctx);
};

/* request handlers */
--
2.48.1