]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: ethtool: Introduce per-PHY DUMP operations
authorMaxime Chevallier <maxime.chevallier@bootlin.com>
Fri, 2 May 2025 08:52:39 +0000 (10:52 +0200)
committerJakub Kicinski <kuba@kernel.org>
Tue, 6 May 2025 00:17:40 +0000 (17:17 -0700)
ethnl commands that target a phy_device need a DUMP implementation that
will fill the reply for every PHY behind a netdev. We therefore need to
iterate over the dev->topo to list them.

When multiple PHYs are behind the same netdev, it's also useful to
perform DUMP with a filter on a given netdev, to get the capability of
every PHY.

Implement dedicated genl ->start(), ->dumpit() and ->done() operations
for PHY-targetting command, allowing filtered dumps and using a dump
context that keep track of the PHY iteration for multi-message dump.

PSE-PD and PLCA are converted to this new set of ops along the way.

Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Link: https://patch.msgid.link/20250502085242.248645-2-maxime.chevallier@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/ethtool/netlink.c

index 977beeaaa2f991c17c03116dcd15e46fad7288eb..a58570c68db27e79ff1c73947f6a560e6bc9b206 100644 (file)
@@ -357,6 +357,18 @@ struct ethnl_dump_ctx {
        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,
@@ -407,6 +419,12 @@ 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
@@ -662,6 +680,173 @@ free_req_info:
        return ret;
 }
 
+/* per-PHY ->start() handler for GET requests */
+static int ethnl_perphy_start(struct netlink_callback *cb)
+{
+       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 (req_info->dev) {
+               phy_ctx->ifindex = req_info->dev->ifindex;
+               netdev_put(req_info->dev, &req_info->dev_tracker);
+               req_info->dev = NULL;
+       }
+       if (ret < 0)
+               goto free_reply_data;
+
+       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 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;
+
+               /* 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);
+               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);
+
+       return 0;
+}
+
 /* default ->done() handler for GET requests */
 static int ethnl_default_done(struct netlink_callback *cb)
 {
@@ -1200,9 +1385,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
        {
                .cmd    = ETHTOOL_MSG_PSE_GET,
                .doit   = ethnl_default_doit,
-               .start  = ethnl_default_start,
-               .dumpit = ethnl_default_dumpit,
-               .done   = ethnl_default_done,
+               .start  = ethnl_perphy_start,
+               .dumpit = ethnl_perphy_dumpit,
+               .done   = ethnl_perphy_done,
                .policy = ethnl_pse_get_policy,
                .maxattr = ARRAY_SIZE(ethnl_pse_get_policy) - 1,
        },
@@ -1224,9 +1409,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
        {
                .cmd    = ETHTOOL_MSG_PLCA_GET_CFG,
                .doit   = ethnl_default_doit,
-               .start  = ethnl_default_start,
-               .dumpit = ethnl_default_dumpit,
-               .done   = ethnl_default_done,
+               .start  = ethnl_perphy_start,
+               .dumpit = ethnl_perphy_dumpit,
+               .done   = ethnl_perphy_done,
                .policy = ethnl_plca_get_cfg_policy,
                .maxattr = ARRAY_SIZE(ethnl_plca_get_cfg_policy) - 1,
        },
@@ -1240,9 +1425,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
        {
                .cmd    = ETHTOOL_MSG_PLCA_GET_STATUS,
                .doit   = ethnl_default_doit,
-               .start  = ethnl_default_start,
-               .dumpit = ethnl_default_dumpit,
-               .done   = ethnl_default_done,
+               .start  = ethnl_perphy_start,
+               .dumpit = ethnl_perphy_dumpit,
+               .done   = ethnl_perphy_done,
                .policy = ethnl_plca_get_status_policy,
                .maxattr = ARRAY_SIZE(ethnl_plca_get_status_policy) - 1,
        },