Commit 172265b4 authored by Maxime Chevallier's avatar Maxime Chevallier Committed by Jakub Kicinski
Browse files

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: default avatarMaxime Chevallier <maxime.chevallier@bootlin.com>
Link: https://patch.msgid.link/20250502085242.248645-2-maxime.chevallier@bootlin.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 953d9480
Loading
Loading
Loading
Loading
+194 −9
Original line number Diff line number Diff line
@@ -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,
	},