[PATCH net-next 01/11] ethtool: Add dump_one_dev callback for per-device sub-iteration

From: Björn Töpel

Date: Tue Mar 10 2026 - 06:50:53 EST


The per-PHY specific dump functions share a lot functionality of with
the default dumpit infrastructure, but does not share the actual code.
By introducing a new sub-iterator function, the two dumpit variants
can be folded into one set of functions.

Add a new dump_one_dev callback in ethnl_request_ops. When
ops->dump_one_dev is set, ethnl_default_start() saves the target
device's ifindex for filtered dumps, and ethnl_default_dumpit()
delegates per-device iteration to the callback instead of calling
ethnl_default_dump_one() directly. No separate start/dumpit/done
functions are needed.

For the existing per-PHY commands (PSE, PLCA, PHY, MSE), the shared
ethnl_perphy_dump_one_dev helper provides the xa_for_each_start loop
over the device's PHY topology.

This prepares the ethtool infrastructure for other commands that need
similar per-device sub-iteration.

Suggested-by: Maxime Chevallier <maxime.chevallier@xxxxxxxxxxx>
Signed-off-by: Björn Töpel <bjorn@xxxxxxxxxx>
---
net/ethtool/mse.c | 1 +
net/ethtool/netlink.c | 261 ++++++++++--------------------------------
net/ethtool/netlink.h | 31 +++++
net/ethtool/phy.c | 1 +
net/ethtool/plca.c | 2 +
net/ethtool/pse-pd.c | 1 +
6 files changed, 94 insertions(+), 203 deletions(-)

diff --git a/net/ethtool/mse.c b/net/ethtool/mse.c
index 8cb3fc5e7be4..0f2709b4f7de 100644
--- a/net/ethtool/mse.c
+++ b/net/ethtool/mse.c
@@ -325,4 +325,5 @@ const struct ethnl_request_ops ethnl_mse_request_ops = {
.cleanup_data = mse_cleanup_data,
.reply_size = mse_reply_size,
.fill_reply = mse_fill_reply,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6e5f0f4f815a..e740b11a0609 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -345,36 +345,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;
-};
-
-/**
- * struct ethnl_perphy_dump_ctx - context for dumpit() PHY-aware callbacks
- * @ethnl_ctx: generic ethnl context
- * @ifindex: For Filtered DUMP requests, the ifindex of the targeted netdev
- * @pos_phyindex: iterator position for multi-msg DUMP
- */
-struct ethnl_perphy_dump_ctx {
- struct ethnl_dump_ctx ethnl_ctx;
- unsigned int ifindex;
- unsigned long pos_phyindex;
-};
-
static const struct ethnl_request_ops *
ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_STRSET_GET] = &ethnl_strset_request_ops,
@@ -428,12 +398,6 @@ static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
return (struct ethnl_dump_ctx *)cb->ctx;
}

-static struct ethnl_perphy_dump_ctx *
-ethnl_perphy_dump_context(struct netlink_callback *cb)
-{
- return (struct ethnl_perphy_dump_ctx *)cb->ctx;
-}
-
/**
* ethnl_default_parse() - Parse request message
* @req_info: pointer to structure to put data into
@@ -616,17 +580,41 @@ static int ethnl_default_dumpit(struct sk_buff *skb,
struct netlink_callback *cb)
{
struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
+ const struct genl_info *info = genl_info_dump(cb);
struct net *net = sock_net(skb->sk);
netdevice_tracker dev_tracker;
struct net_device *dev;
int ret = 0;

+ if (ctx->ops->dump_one_dev && ctx->ifindex) {
+ dev = netdev_get_by_index(net, ctx->ifindex, &dev_tracker,
+ GFP_KERNEL);
+ if (!dev)
+ return -ENODEV;
+
+ ctx->req_info->dev = dev;
+ ret = ctx->ops->dump_one_dev(skb, ctx, &ctx->pos_sub, info);
+
+ if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
+ ret = skb->len;
+
+ netdev_put(dev, &dev_tracker);
+ return ret;
+ }
+
rcu_read_lock();
for_each_netdev_dump(net, dev, ctx->pos_ifindex) {
netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
rcu_read_unlock();

- ret = ethnl_default_dump_one(skb, dev, ctx, genl_info_dump(cb));
+ if (ctx->ops->dump_one_dev) {
+ ctx->req_info->dev = dev;
+ ret = ctx->ops->dump_one_dev(skb, ctx, &ctx->pos_sub,
+ info);
+ ctx->req_info->dev = NULL;
+ } else {
+ ret = ethnl_default_dump_one(skb, dev, ctx, info);
+ }

rcu_read_lock();
netdev_put(dev, &dev_tracker);
@@ -673,10 +661,13 @@ static int ethnl_default_start(struct netlink_callback *cb)
if (ret < 0)
goto free_reply_data;
if (req_info->dev) {
- /* We ignore device specification in dump requests but as the
- * same parser as for non-dump (doit) requests is used, it
- * would take reference to the device if it finds one
- */
+ if (ops->dump_one_dev) {
+ /* Sub-iterator dumps keep track of the dev's ifindex
+ * so the dumpit handler can grab/release the netdev
+ * per iteration.
+ */
+ ctx->ifindex = req_info->dev->ifindex;
+ }
netdev_put(req_info->dev, &req_info->dev_tracker);
req_info->dev = NULL;
}
@@ -696,169 +687,33 @@ static int ethnl_default_start(struct netlink_callback *cb)
return ret;
}

-/* per-PHY ->start() handler for GET requests */
-static int ethnl_perphy_start(struct netlink_callback *cb)
+/* Shared dump_one_dev for per-PHY commands (PSE, PLCA, PHY, MSE) */
+int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info)
{
- struct ethnl_perphy_dump_ctx *phy_ctx = ethnl_perphy_dump_context(cb);
- const struct genl_dumpit_info *info = genl_dumpit_info(cb);
- struct ethnl_dump_ctx *ctx = &phy_ctx->ethnl_ctx;
- struct ethnl_reply_data *reply_data;
- const struct ethnl_request_ops *ops;
- struct ethnl_req_info *req_info;
- struct genlmsghdr *ghdr;
- int ret;
-
- BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
-
- ghdr = nlmsg_data(cb->nlh);
- ops = ethnl_default_requests[ghdr->cmd];
- if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", ghdr->cmd))
- return -EOPNOTSUPP;
- req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
- if (!req_info)
- return -ENOMEM;
- reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
- if (!reply_data) {
- ret = -ENOMEM;
- goto free_req_info;
- }
-
- /* Unlike per-dev dump, don't ignore dev. The dump handler
- * will notice it and dump PHYs from given dev. We only keep track of
- * the dev's ifindex, .dumpit() will grab and release the netdev itself.
- */
- ret = ethnl_default_parse(req_info, &info->info, ops, false);
- if (ret < 0)
- goto free_reply_data;
- if (req_info->dev) {
- phy_ctx->ifindex = req_info->dev->ifindex;
- netdev_put(req_info->dev, &req_info->dev_tracker);
- req_info->dev = NULL;
- }
-
- ctx->ops = ops;
- ctx->req_info = req_info;
- ctx->reply_data = reply_data;
- ctx->pos_ifindex = 0;
-
- return 0;
-
-free_reply_data:
- kfree(reply_data);
-free_req_info:
- kfree(req_info);
-
- return ret;
-}
-
-static int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
- struct ethnl_perphy_dump_ctx *ctx,
- const struct genl_info *info)
-{
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
- struct net_device *dev = ethnl_ctx->req_info->dev;
+ struct net_device *dev = ctx->req_info->dev;
struct phy_device_node *pdn;
int ret;

if (!dev->link_topo)
return 0;

- xa_for_each_start(&dev->link_topo->phys, ctx->pos_phyindex, pdn,
- ctx->pos_phyindex) {
- ethnl_ctx->req_info->phy_index = ctx->pos_phyindex;
+ xa_for_each_start(&dev->link_topo->phys, *pos_sub, pdn,
+ *pos_sub) {
+ ctx->req_info->phy_index = *pos_sub;

/* We can re-use the original dump_one as ->prepare_data in
* commands use ethnl_req_get_phydev(), which gets the PHY from
* the req_info->phy_index
*/
- ret = ethnl_default_dump_one(skb, dev, ethnl_ctx, info);
+ ret = ethnl_default_dump_one(skb, dev, ctx, info);
if (ret)
return ret;
}

- ctx->pos_phyindex = 0;
-
- return 0;
-}
-
-static int ethnl_perphy_dump_all_dev(struct sk_buff *skb,
- struct ethnl_perphy_dump_ctx *ctx,
- const struct genl_info *info)
-{
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
- struct net *net = sock_net(skb->sk);
- netdevice_tracker dev_tracker;
- struct net_device *dev;
- int ret = 0;
-
- rcu_read_lock();
- for_each_netdev_dump(net, dev, ethnl_ctx->pos_ifindex) {
- netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
- rcu_read_unlock();
-
- /* per-PHY commands use ethnl_req_get_phydev(), which needs the
- * net_device in the req_info
- */
- ethnl_ctx->req_info->dev = dev;
- ret = ethnl_perphy_dump_one_dev(skb, ctx, info);
-
- rcu_read_lock();
- netdev_put(dev, &dev_tracker);
- ethnl_ctx->req_info->dev = NULL;
-
- if (ret < 0 && ret != -EOPNOTSUPP) {
- if (likely(skb->len))
- ret = skb->len;
- break;
- }
- ret = 0;
- }
- rcu_read_unlock();
-
- return ret;
-}
-
-/* per-PHY ->dumpit() handler for GET requests. */
-static int ethnl_perphy_dumpit(struct sk_buff *skb,
- struct netlink_callback *cb)
-{
- struct ethnl_perphy_dump_ctx *ctx = ethnl_perphy_dump_context(cb);
- const struct genl_dumpit_info *info = genl_dumpit_info(cb);
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
- int ret = 0;
-
- if (ctx->ifindex) {
- netdevice_tracker dev_tracker;
- struct net_device *dev;
-
- dev = netdev_get_by_index(genl_info_net(&info->info),
- ctx->ifindex, &dev_tracker,
- GFP_KERNEL);
- if (!dev)
- return -ENODEV;
-
- ethnl_ctx->req_info->dev = dev;
- ret = ethnl_perphy_dump_one_dev(skb, ctx, genl_info_dump(cb));
-
- if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
- ret = skb->len;
-
- netdev_put(dev, &dev_tracker);
- } else {
- ret = ethnl_perphy_dump_all_dev(skb, ctx, genl_info_dump(cb));
- }
-
- return ret;
-}
-
-/* per-PHY ->done() handler for GET requests */
-static int ethnl_perphy_done(struct netlink_callback *cb)
-{
- struct ethnl_perphy_dump_ctx *ctx = ethnl_perphy_dump_context(cb);
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
-
- kfree(ethnl_ctx->reply_data);
- kfree(ethnl_ctx->req_info);
+ *pos_sub = 0;

return 0;
}
@@ -1420,9 +1275,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PSE_GET,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_pse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_pse_get_policy) - 1,
},
@@ -1444,9 +1299,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PLCA_GET_CFG,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_plca_get_cfg_policy,
.maxattr = ARRAY_SIZE(ethnl_plca_get_cfg_policy) - 1,
},
@@ -1460,9 +1315,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PLCA_GET_STATUS,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_plca_get_status_policy,
.maxattr = ARRAY_SIZE(ethnl_plca_get_status_policy) - 1,
},
@@ -1492,9 +1347,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PHY_GET,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_phy_get_policy,
.maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1,
},
@@ -1538,9 +1393,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_MSE_GET,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_mse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
},
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 89010eaa67df..aa8d51903ecc 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -10,6 +10,28 @@

struct ethnl_req_info;

+/**
+ * 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
+ * @ifindex: for filtered dump requests, the ifindex of the targeted netdev
+ * @pos_sub: iterator position for per-device iteration
+ *
+ * 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;
+ unsigned int ifindex;
+ unsigned long pos_sub;
+};
+
u32 ethnl_bcast_seq_next(void);
int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net,
@@ -408,6 +430,11 @@ struct ethnl_request_ops {
const struct ethnl_reply_data *reply_data);
void (*cleanup_data)(struct ethnl_reply_data *reply_data);

+ int (*dump_one_dev)(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info);
+
int (*set_validate)(struct ethnl_req_info *req_info,
struct genl_info *info);
int (*set)(struct ethnl_req_info *req_info,
@@ -514,6 +541,10 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_tsinfo_done(struct netlink_callback *cb);
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info);
+int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info);

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/phy.c b/net/ethtool/phy.c
index 68372bef4b2f..4158c2abb235 100644
--- a/net/ethtool/phy.c
+++ b/net/ethtool/phy.c
@@ -162,4 +162,5 @@ const struct ethnl_request_ops ethnl_phy_request_ops = {
.reply_size = phy_reply_size,
.fill_reply = phy_fill_reply,
.cleanup_data = phy_cleanup_data,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
};
diff --git a/net/ethtool/plca.c b/net/ethtool/plca.c
index e1f7820a6158..cad0ba476c29 100644
--- a/net/ethtool/plca.c
+++ b/net/ethtool/plca.c
@@ -188,6 +188,7 @@ const struct ethnl_request_ops ethnl_plca_cfg_request_ops = {
.prepare_data = plca_get_cfg_prepare_data,
.reply_size = plca_get_cfg_reply_size,
.fill_reply = plca_get_cfg_fill_reply,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,

.set = ethnl_set_plca,
.set_ntf_cmd = ETHTOOL_MSG_PLCA_NTF,
@@ -268,4 +269,5 @@ const struct ethnl_request_ops ethnl_plca_status_request_ops = {
.prepare_data = plca_get_status_prepare_data,
.reply_size = plca_get_status_reply_size,
.fill_reply = plca_get_status_fill_reply,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
};
diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c
index 24def9c9dd54..1442a59e033f 100644
--- a/net/ethtool/pse-pd.c
+++ b/net/ethtool/pse-pd.c
@@ -337,6 +337,7 @@ const struct ethnl_request_ops ethnl_pse_request_ops = {
.reply_size = pse_reply_size,
.fill_reply = pse_fill_reply,
.cleanup_data = pse_cleanup_data,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,

.set = ethnl_set_pse,
/* PSE has no notification */
--
2.53.0