net: ethtool: Introduce per-PHY DUMP operations

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>
This commit is contained in:
Maxime Chevallier 2025-05-02 10:52:39 +02:00 committed by Jakub Kicinski
parent 953d9480f7
commit 172265b44c

View 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 @@ 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)
{
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,
},