xref: /openbmc/linux/net/ethtool/privflags.c (revision f946270d)
1e16c3386SMichal Kubecek // SPDX-License-Identifier: GPL-2.0-only
2e16c3386SMichal Kubecek 
3e16c3386SMichal Kubecek #include "netlink.h"
4e16c3386SMichal Kubecek #include "common.h"
5e16c3386SMichal Kubecek #include "bitset.h"
6e16c3386SMichal Kubecek 
7e16c3386SMichal Kubecek struct privflags_req_info {
8e16c3386SMichal Kubecek 	struct ethnl_req_info		base;
9e16c3386SMichal Kubecek };
10e16c3386SMichal Kubecek 
11e16c3386SMichal Kubecek struct privflags_reply_data {
12e16c3386SMichal Kubecek 	struct ethnl_reply_data		base;
13e16c3386SMichal Kubecek 	const char			(*priv_flag_names)[ETH_GSTRING_LEN];
14e16c3386SMichal Kubecek 	unsigned int			n_priv_flags;
15e16c3386SMichal Kubecek 	u32				priv_flags;
16e16c3386SMichal Kubecek };
17e16c3386SMichal Kubecek 
18e16c3386SMichal Kubecek #define PRIVFLAGS_REPDATA(__reply_base) \
19e16c3386SMichal Kubecek 	container_of(__reply_base, struct privflags_reply_data, base)
20e16c3386SMichal Kubecek 
21ff419afaSJakub Kicinski const struct nla_policy ethnl_privflags_get_policy[] = {
22329d9c33SJakub Kicinski 	[ETHTOOL_A_PRIVFLAGS_HEADER]		=
23329d9c33SJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy),
24e16c3386SMichal Kubecek };
25e16c3386SMichal Kubecek 
ethnl_get_priv_flags_info(struct net_device * dev,unsigned int * count,const char (** names)[ETH_GSTRING_LEN])26e16c3386SMichal Kubecek static int ethnl_get_priv_flags_info(struct net_device *dev,
27e16c3386SMichal Kubecek 				     unsigned int *count,
28e16c3386SMichal Kubecek 				     const char (**names)[ETH_GSTRING_LEN])
29e16c3386SMichal Kubecek {
30e16c3386SMichal Kubecek 	const struct ethtool_ops *ops = dev->ethtool_ops;
31e16c3386SMichal Kubecek 	int nflags;
32e16c3386SMichal Kubecek 
33e16c3386SMichal Kubecek 	nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
34e16c3386SMichal Kubecek 	if (nflags < 0)
35e16c3386SMichal Kubecek 		return nflags;
36e16c3386SMichal Kubecek 
37e16c3386SMichal Kubecek 	if (names) {
38e16c3386SMichal Kubecek 		*names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL);
39e16c3386SMichal Kubecek 		if (!*names)
40e16c3386SMichal Kubecek 			return -ENOMEM;
41e16c3386SMichal Kubecek 		ops->get_strings(dev, ETH_SS_PRIV_FLAGS, (u8 *)*names);
42e16c3386SMichal Kubecek 	}
43e16c3386SMichal Kubecek 
44e16c3386SMichal Kubecek 	/* We can pass more than 32 private flags to userspace via netlink but
45e16c3386SMichal Kubecek 	 * we cannot get more with ethtool_ops::get_priv_flags(). Note that we
46e16c3386SMichal Kubecek 	 * must not adjust nflags before allocating the space for flag names
47e16c3386SMichal Kubecek 	 * as the buffer must be large enough for all flags.
48e16c3386SMichal Kubecek 	 */
49e16c3386SMichal Kubecek 	if (WARN_ONCE(nflags > 32,
50e16c3386SMichal Kubecek 		      "device %s reports more than 32 private flags (%d)\n",
51e16c3386SMichal Kubecek 		      netdev_name(dev), nflags))
52e16c3386SMichal Kubecek 		nflags = 32;
53e16c3386SMichal Kubecek 	*count = nflags;
54e16c3386SMichal Kubecek 
55e16c3386SMichal Kubecek 	return 0;
56e16c3386SMichal Kubecek }
57e16c3386SMichal Kubecek 
privflags_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)58e16c3386SMichal Kubecek static int privflags_prepare_data(const struct ethnl_req_info *req_base,
59e16c3386SMichal Kubecek 				  struct ethnl_reply_data *reply_base,
60*f946270dSJakub Kicinski 				  const struct genl_info *info)
61e16c3386SMichal Kubecek {
62e16c3386SMichal Kubecek 	struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
63e16c3386SMichal Kubecek 	struct net_device *dev = reply_base->dev;
64e16c3386SMichal Kubecek 	const char (*names)[ETH_GSTRING_LEN];
65e16c3386SMichal Kubecek 	const struct ethtool_ops *ops;
66e16c3386SMichal Kubecek 	unsigned int nflags;
67e16c3386SMichal Kubecek 	int ret;
68e16c3386SMichal Kubecek 
69e16c3386SMichal Kubecek 	ops = dev->ethtool_ops;
70e16c3386SMichal Kubecek 	if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings)
71e16c3386SMichal Kubecek 		return -EOPNOTSUPP;
72e16c3386SMichal Kubecek 	ret = ethnl_ops_begin(dev);
73e16c3386SMichal Kubecek 	if (ret < 0)
74e16c3386SMichal Kubecek 		return ret;
75e16c3386SMichal Kubecek 
76e16c3386SMichal Kubecek 	ret = ethnl_get_priv_flags_info(dev, &nflags, &names);
77e16c3386SMichal Kubecek 	if (ret < 0)
78e16c3386SMichal Kubecek 		goto out_ops;
79e16c3386SMichal Kubecek 	data->priv_flags = ops->get_priv_flags(dev);
80e16c3386SMichal Kubecek 	data->priv_flag_names = names;
81e16c3386SMichal Kubecek 	data->n_priv_flags = nflags;
82e16c3386SMichal Kubecek 
83e16c3386SMichal Kubecek out_ops:
84e16c3386SMichal Kubecek 	ethnl_ops_complete(dev);
85e16c3386SMichal Kubecek 	return ret;
86e16c3386SMichal Kubecek }
87e16c3386SMichal Kubecek 
privflags_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)88e16c3386SMichal Kubecek static int privflags_reply_size(const struct ethnl_req_info *req_base,
89e16c3386SMichal Kubecek 				const struct ethnl_reply_data *reply_base)
90e16c3386SMichal Kubecek {
91e16c3386SMichal Kubecek 	const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
92e16c3386SMichal Kubecek 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
93e16c3386SMichal Kubecek 	const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
94e16c3386SMichal Kubecek 
95e16c3386SMichal Kubecek 	return ethnl_bitset32_size(&data->priv_flags, &all_flags,
96e16c3386SMichal Kubecek 				   data->n_priv_flags,
97e16c3386SMichal Kubecek 				   data->priv_flag_names, compact);
98e16c3386SMichal Kubecek }
99e16c3386SMichal Kubecek 
privflags_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)100e16c3386SMichal Kubecek static int privflags_fill_reply(struct sk_buff *skb,
101e16c3386SMichal Kubecek 				const struct ethnl_req_info *req_base,
102e16c3386SMichal Kubecek 				const struct ethnl_reply_data *reply_base)
103e16c3386SMichal Kubecek {
104e16c3386SMichal Kubecek 	const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
105e16c3386SMichal Kubecek 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
106e16c3386SMichal Kubecek 	const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
107e16c3386SMichal Kubecek 
108e16c3386SMichal Kubecek 	return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS,
109e16c3386SMichal Kubecek 				  &data->priv_flags, &all_flags,
110e16c3386SMichal Kubecek 				  data->n_priv_flags, data->priv_flag_names,
111e16c3386SMichal Kubecek 				  compact);
112e16c3386SMichal Kubecek }
113e16c3386SMichal Kubecek 
privflags_cleanup_data(struct ethnl_reply_data * reply_data)114e16c3386SMichal Kubecek static void privflags_cleanup_data(struct ethnl_reply_data *reply_data)
115e16c3386SMichal Kubecek {
116e16c3386SMichal Kubecek 	struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data);
117e16c3386SMichal Kubecek 
118e16c3386SMichal Kubecek 	kfree(data->priv_flag_names);
119e16c3386SMichal Kubecek }
120e16c3386SMichal Kubecek 
12104007961SJakub Kicinski /* PRIVFLAGS_SET */
12204007961SJakub Kicinski 
12304007961SJakub Kicinski const struct nla_policy ethnl_privflags_set_policy[] = {
12404007961SJakub Kicinski 	[ETHTOOL_A_PRIVFLAGS_HEADER]		=
12504007961SJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy),
12604007961SJakub Kicinski 	[ETHTOOL_A_PRIVFLAGS_FLAGS]		= { .type = NLA_NESTED },
12704007961SJakub Kicinski };
12804007961SJakub Kicinski 
12904007961SJakub Kicinski static int
ethnl_set_privflags_validate(struct ethnl_req_info * req_info,struct genl_info * info)13004007961SJakub Kicinski ethnl_set_privflags_validate(struct ethnl_req_info *req_info,
13104007961SJakub Kicinski 			     struct genl_info *info)
13204007961SJakub Kicinski {
13304007961SJakub Kicinski 	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
13404007961SJakub Kicinski 
13504007961SJakub Kicinski 	if (!info->attrs[ETHTOOL_A_PRIVFLAGS_FLAGS])
13604007961SJakub Kicinski 		return -EINVAL;
13704007961SJakub Kicinski 
13804007961SJakub Kicinski 	if (!ops->get_priv_flags || !ops->set_priv_flags ||
13904007961SJakub Kicinski 	    !ops->get_sset_count || !ops->get_strings)
14004007961SJakub Kicinski 		return -EOPNOTSUPP;
14104007961SJakub Kicinski 	return 1;
14204007961SJakub Kicinski }
14304007961SJakub Kicinski 
14404007961SJakub Kicinski static int
ethnl_set_privflags(struct ethnl_req_info * req_info,struct genl_info * info)14504007961SJakub Kicinski ethnl_set_privflags(struct ethnl_req_info *req_info, struct genl_info *info)
14604007961SJakub Kicinski {
14704007961SJakub Kicinski 	const char (*names)[ETH_GSTRING_LEN] = NULL;
14804007961SJakub Kicinski 	struct net_device *dev = req_info->dev;
14904007961SJakub Kicinski 	struct nlattr **tb = info->attrs;
15004007961SJakub Kicinski 	unsigned int nflags;
15104007961SJakub Kicinski 	bool mod = false;
15204007961SJakub Kicinski 	bool compact;
15304007961SJakub Kicinski 	u32 flags;
15404007961SJakub Kicinski 	int ret;
15504007961SJakub Kicinski 
15604007961SJakub Kicinski 	ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact);
15704007961SJakub Kicinski 	if (ret < 0)
15804007961SJakub Kicinski 		return ret;
15904007961SJakub Kicinski 
16004007961SJakub Kicinski 	ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names);
16104007961SJakub Kicinski 	if (ret < 0)
16204007961SJakub Kicinski 		return ret;
16304007961SJakub Kicinski 	flags = dev->ethtool_ops->get_priv_flags(dev);
16404007961SJakub Kicinski 
16504007961SJakub Kicinski 	ret = ethnl_update_bitset32(&flags, nflags,
16604007961SJakub Kicinski 				    tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names,
16704007961SJakub Kicinski 				    info->extack, &mod);
16804007961SJakub Kicinski 	if (ret < 0 || !mod)
16904007961SJakub Kicinski 		goto out_free;
17004007961SJakub Kicinski 	ret = dev->ethtool_ops->set_priv_flags(dev, flags);
17104007961SJakub Kicinski 	if (ret < 0)
17204007961SJakub Kicinski 		goto out_free;
17304007961SJakub Kicinski 	ret = 1;
17404007961SJakub Kicinski 
17504007961SJakub Kicinski out_free:
17604007961SJakub Kicinski 	kfree(names);
17704007961SJakub Kicinski 	return ret;
17804007961SJakub Kicinski }
17904007961SJakub Kicinski 
180e16c3386SMichal Kubecek const struct ethnl_request_ops ethnl_privflags_request_ops = {
181e16c3386SMichal Kubecek 	.request_cmd		= ETHTOOL_MSG_PRIVFLAGS_GET,
182e16c3386SMichal Kubecek 	.reply_cmd		= ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
183e16c3386SMichal Kubecek 	.hdr_attr		= ETHTOOL_A_PRIVFLAGS_HEADER,
184e16c3386SMichal Kubecek 	.req_info_size		= sizeof(struct privflags_req_info),
185e16c3386SMichal Kubecek 	.reply_data_size	= sizeof(struct privflags_reply_data),
186e16c3386SMichal Kubecek 
187e16c3386SMichal Kubecek 	.prepare_data		= privflags_prepare_data,
188e16c3386SMichal Kubecek 	.reply_size		= privflags_reply_size,
189e16c3386SMichal Kubecek 	.fill_reply		= privflags_fill_reply,
190e16c3386SMichal Kubecek 	.cleanup_data		= privflags_cleanup_data,
19104007961SJakub Kicinski 
19204007961SJakub Kicinski 	.set_validate		= ethnl_set_privflags_validate,
19304007961SJakub Kicinski 	.set			= ethnl_set_privflags,
19404007961SJakub Kicinski 	.set_ntf_cmd		= ETHTOOL_MSG_PRIVFLAGS_NTF,
195e16c3386SMichal Kubecek };
196