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