xref: /kernel/linux/linux-6.6/net/ethtool/privflags.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include "netlink.h"
462306a36Sopenharmony_ci#include "common.h"
562306a36Sopenharmony_ci#include "bitset.h"
662306a36Sopenharmony_ci
762306a36Sopenharmony_cistruct privflags_req_info {
862306a36Sopenharmony_ci	struct ethnl_req_info		base;
962306a36Sopenharmony_ci};
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_cistruct privflags_reply_data {
1262306a36Sopenharmony_ci	struct ethnl_reply_data		base;
1362306a36Sopenharmony_ci	const char			(*priv_flag_names)[ETH_GSTRING_LEN];
1462306a36Sopenharmony_ci	unsigned int			n_priv_flags;
1562306a36Sopenharmony_ci	u32				priv_flags;
1662306a36Sopenharmony_ci};
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define PRIVFLAGS_REPDATA(__reply_base) \
1962306a36Sopenharmony_ci	container_of(__reply_base, struct privflags_reply_data, base)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciconst struct nla_policy ethnl_privflags_get_policy[] = {
2262306a36Sopenharmony_ci	[ETHTOOL_A_PRIVFLAGS_HEADER]		=
2362306a36Sopenharmony_ci		NLA_POLICY_NESTED(ethnl_header_policy),
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic int ethnl_get_priv_flags_info(struct net_device *dev,
2762306a36Sopenharmony_ci				     unsigned int *count,
2862306a36Sopenharmony_ci				     const char (**names)[ETH_GSTRING_LEN])
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	const struct ethtool_ops *ops = dev->ethtool_ops;
3162306a36Sopenharmony_ci	int nflags;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
3462306a36Sopenharmony_ci	if (nflags < 0)
3562306a36Sopenharmony_ci		return nflags;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (names) {
3862306a36Sopenharmony_ci		*names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL);
3962306a36Sopenharmony_ci		if (!*names)
4062306a36Sopenharmony_ci			return -ENOMEM;
4162306a36Sopenharmony_ci		ops->get_strings(dev, ETH_SS_PRIV_FLAGS, (u8 *)*names);
4262306a36Sopenharmony_ci	}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	/* We can pass more than 32 private flags to userspace via netlink but
4562306a36Sopenharmony_ci	 * we cannot get more with ethtool_ops::get_priv_flags(). Note that we
4662306a36Sopenharmony_ci	 * must not adjust nflags before allocating the space for flag names
4762306a36Sopenharmony_ci	 * as the buffer must be large enough for all flags.
4862306a36Sopenharmony_ci	 */
4962306a36Sopenharmony_ci	if (WARN_ONCE(nflags > 32,
5062306a36Sopenharmony_ci		      "device %s reports more than 32 private flags (%d)\n",
5162306a36Sopenharmony_ci		      netdev_name(dev), nflags))
5262306a36Sopenharmony_ci		nflags = 32;
5362306a36Sopenharmony_ci	*count = nflags;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return 0;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic int privflags_prepare_data(const struct ethnl_req_info *req_base,
5962306a36Sopenharmony_ci				  struct ethnl_reply_data *reply_base,
6062306a36Sopenharmony_ci				  const struct genl_info *info)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
6362306a36Sopenharmony_ci	struct net_device *dev = reply_base->dev;
6462306a36Sopenharmony_ci	const char (*names)[ETH_GSTRING_LEN];
6562306a36Sopenharmony_ci	const struct ethtool_ops *ops;
6662306a36Sopenharmony_ci	unsigned int nflags;
6762306a36Sopenharmony_ci	int ret;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	ops = dev->ethtool_ops;
7062306a36Sopenharmony_ci	if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings)
7162306a36Sopenharmony_ci		return -EOPNOTSUPP;
7262306a36Sopenharmony_ci	ret = ethnl_ops_begin(dev);
7362306a36Sopenharmony_ci	if (ret < 0)
7462306a36Sopenharmony_ci		return ret;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	ret = ethnl_get_priv_flags_info(dev, &nflags, &names);
7762306a36Sopenharmony_ci	if (ret < 0)
7862306a36Sopenharmony_ci		goto out_ops;
7962306a36Sopenharmony_ci	data->priv_flags = ops->get_priv_flags(dev);
8062306a36Sopenharmony_ci	data->priv_flag_names = names;
8162306a36Sopenharmony_ci	data->n_priv_flags = nflags;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ciout_ops:
8462306a36Sopenharmony_ci	ethnl_ops_complete(dev);
8562306a36Sopenharmony_ci	return ret;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int privflags_reply_size(const struct ethnl_req_info *req_base,
8962306a36Sopenharmony_ci				const struct ethnl_reply_data *reply_base)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
9262306a36Sopenharmony_ci	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
9362306a36Sopenharmony_ci	const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return ethnl_bitset32_size(&data->priv_flags, &all_flags,
9662306a36Sopenharmony_ci				   data->n_priv_flags,
9762306a36Sopenharmony_ci				   data->priv_flag_names, compact);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int privflags_fill_reply(struct sk_buff *skb,
10162306a36Sopenharmony_ci				const struct ethnl_req_info *req_base,
10262306a36Sopenharmony_ci				const struct ethnl_reply_data *reply_base)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
10562306a36Sopenharmony_ci	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
10662306a36Sopenharmony_ci	const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS,
10962306a36Sopenharmony_ci				  &data->priv_flags, &all_flags,
11062306a36Sopenharmony_ci				  data->n_priv_flags, data->priv_flag_names,
11162306a36Sopenharmony_ci				  compact);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic void privflags_cleanup_data(struct ethnl_reply_data *reply_data)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	kfree(data->priv_flag_names);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci/* PRIVFLAGS_SET */
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ciconst struct nla_policy ethnl_privflags_set_policy[] = {
12462306a36Sopenharmony_ci	[ETHTOOL_A_PRIVFLAGS_HEADER]		=
12562306a36Sopenharmony_ci		NLA_POLICY_NESTED(ethnl_header_policy),
12662306a36Sopenharmony_ci	[ETHTOOL_A_PRIVFLAGS_FLAGS]		= { .type = NLA_NESTED },
12762306a36Sopenharmony_ci};
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int
13062306a36Sopenharmony_ciethnl_set_privflags_validate(struct ethnl_req_info *req_info,
13162306a36Sopenharmony_ci			     struct genl_info *info)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (!info->attrs[ETHTOOL_A_PRIVFLAGS_FLAGS])
13662306a36Sopenharmony_ci		return -EINVAL;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	if (!ops->get_priv_flags || !ops->set_priv_flags ||
13962306a36Sopenharmony_ci	    !ops->get_sset_count || !ops->get_strings)
14062306a36Sopenharmony_ci		return -EOPNOTSUPP;
14162306a36Sopenharmony_ci	return 1;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int
14562306a36Sopenharmony_ciethnl_set_privflags(struct ethnl_req_info *req_info, struct genl_info *info)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	const char (*names)[ETH_GSTRING_LEN] = NULL;
14862306a36Sopenharmony_ci	struct net_device *dev = req_info->dev;
14962306a36Sopenharmony_ci	struct nlattr **tb = info->attrs;
15062306a36Sopenharmony_ci	unsigned int nflags;
15162306a36Sopenharmony_ci	bool mod = false;
15262306a36Sopenharmony_ci	bool compact;
15362306a36Sopenharmony_ci	u32 flags;
15462306a36Sopenharmony_ci	int ret;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact);
15762306a36Sopenharmony_ci	if (ret < 0)
15862306a36Sopenharmony_ci		return ret;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names);
16162306a36Sopenharmony_ci	if (ret < 0)
16262306a36Sopenharmony_ci		return ret;
16362306a36Sopenharmony_ci	flags = dev->ethtool_ops->get_priv_flags(dev);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	ret = ethnl_update_bitset32(&flags, nflags,
16662306a36Sopenharmony_ci				    tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names,
16762306a36Sopenharmony_ci				    info->extack, &mod);
16862306a36Sopenharmony_ci	if (ret < 0 || !mod)
16962306a36Sopenharmony_ci		goto out_free;
17062306a36Sopenharmony_ci	ret = dev->ethtool_ops->set_priv_flags(dev, flags);
17162306a36Sopenharmony_ci	if (ret < 0)
17262306a36Sopenharmony_ci		goto out_free;
17362306a36Sopenharmony_ci	ret = 1;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ciout_free:
17662306a36Sopenharmony_ci	kfree(names);
17762306a36Sopenharmony_ci	return ret;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_privflags_request_ops = {
18162306a36Sopenharmony_ci	.request_cmd		= ETHTOOL_MSG_PRIVFLAGS_GET,
18262306a36Sopenharmony_ci	.reply_cmd		= ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
18362306a36Sopenharmony_ci	.hdr_attr		= ETHTOOL_A_PRIVFLAGS_HEADER,
18462306a36Sopenharmony_ci	.req_info_size		= sizeof(struct privflags_req_info),
18562306a36Sopenharmony_ci	.reply_data_size	= sizeof(struct privflags_reply_data),
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	.prepare_data		= privflags_prepare_data,
18862306a36Sopenharmony_ci	.reply_size		= privflags_reply_size,
18962306a36Sopenharmony_ci	.fill_reply		= privflags_fill_reply,
19062306a36Sopenharmony_ci	.cleanup_data		= privflags_cleanup_data,
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	.set_validate		= ethnl_set_privflags_validate,
19362306a36Sopenharmony_ci	.set			= ethnl_set_privflags,
19462306a36Sopenharmony_ci	.set_ntf_cmd		= ETHTOOL_MSG_PRIVFLAGS_NTF,
19562306a36Sopenharmony_ci};
196