Commit | Line | Data |
---|---|---|
e16c3386 MK |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | ||
3 | #include "netlink.h" | |
4 | #include "common.h" | |
5 | #include "bitset.h" | |
6 | ||
7 | struct privflags_req_info { | |
8 | struct ethnl_req_info base; | |
9 | }; | |
10 | ||
11 | struct privflags_reply_data { | |
12 | struct ethnl_reply_data base; | |
13 | const char (*priv_flag_names)[ETH_GSTRING_LEN]; | |
14 | unsigned int n_priv_flags; | |
15 | u32 priv_flags; | |
16 | }; | |
17 | ||
18 | #define PRIVFLAGS_REPDATA(__reply_base) \ | |
19 | container_of(__reply_base, struct privflags_reply_data, base) | |
20 | ||
ff419afa | 21 | const struct nla_policy ethnl_privflags_get_policy[] = { |
329d9c33 JK |
22 | [ETHTOOL_A_PRIVFLAGS_HEADER] = |
23 | NLA_POLICY_NESTED(ethnl_header_policy), | |
e16c3386 MK |
24 | }; |
25 | ||
26 | static int ethnl_get_priv_flags_info(struct net_device *dev, | |
27 | unsigned int *count, | |
28 | const char (**names)[ETH_GSTRING_LEN]) | |
29 | { | |
30 | const struct ethtool_ops *ops = dev->ethtool_ops; | |
31 | int nflags; | |
32 | ||
33 | nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); | |
34 | if (nflags < 0) | |
35 | return nflags; | |
36 | ||
37 | if (names) { | |
38 | *names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL); | |
39 | if (!*names) | |
40 | return -ENOMEM; | |
41 | ops->get_strings(dev, ETH_SS_PRIV_FLAGS, (u8 *)*names); | |
42 | } | |
43 | ||
44 | /* We can pass more than 32 private flags to userspace via netlink but | |
45 | * we cannot get more with ethtool_ops::get_priv_flags(). Note that we | |
46 | * must not adjust nflags before allocating the space for flag names | |
47 | * as the buffer must be large enough for all flags. | |
48 | */ | |
49 | if (WARN_ONCE(nflags > 32, | |
50 | "device %s reports more than 32 private flags (%d)\n", | |
51 | netdev_name(dev), nflags)) | |
52 | nflags = 32; | |
53 | *count = nflags; | |
54 | ||
55 | return 0; | |
56 | } | |
57 | ||
58 | static int privflags_prepare_data(const struct ethnl_req_info *req_base, | |
59 | struct ethnl_reply_data *reply_base, | |
60 | struct genl_info *info) | |
61 | { | |
62 | struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base); | |
63 | struct net_device *dev = reply_base->dev; | |
64 | const char (*names)[ETH_GSTRING_LEN]; | |
65 | const struct ethtool_ops *ops; | |
66 | unsigned int nflags; | |
67 | int ret; | |
68 | ||
69 | ops = dev->ethtool_ops; | |
70 | if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings) | |
71 | return -EOPNOTSUPP; | |
72 | ret = ethnl_ops_begin(dev); | |
73 | if (ret < 0) | |
74 | return ret; | |
75 | ||
76 | ret = ethnl_get_priv_flags_info(dev, &nflags, &names); | |
77 | if (ret < 0) | |
78 | goto out_ops; | |
79 | data->priv_flags = ops->get_priv_flags(dev); | |
80 | data->priv_flag_names = names; | |
81 | data->n_priv_flags = nflags; | |
82 | ||
83 | out_ops: | |
84 | ethnl_ops_complete(dev); | |
85 | return ret; | |
86 | } | |
87 | ||
88 | static int privflags_reply_size(const struct ethnl_req_info *req_base, | |
89 | const struct ethnl_reply_data *reply_base) | |
90 | { | |
91 | const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base); | |
92 | bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; | |
93 | const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags); | |
94 | ||
95 | return ethnl_bitset32_size(&data->priv_flags, &all_flags, | |
96 | data->n_priv_flags, | |
97 | data->priv_flag_names, compact); | |
98 | } | |
99 | ||
100 | static int privflags_fill_reply(struct sk_buff *skb, | |
101 | const struct ethnl_req_info *req_base, | |
102 | const struct ethnl_reply_data *reply_base) | |
103 | { | |
104 | const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base); | |
105 | bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; | |
106 | const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags); | |
107 | ||
108 | return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS, | |
109 | &data->priv_flags, &all_flags, | |
110 | data->n_priv_flags, data->priv_flag_names, | |
111 | compact); | |
112 | } | |
113 | ||
114 | static void privflags_cleanup_data(struct ethnl_reply_data *reply_data) | |
115 | { | |
116 | struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data); | |
117 | ||
118 | kfree(data->priv_flag_names); | |
119 | } | |
120 | ||
121 | const struct ethnl_request_ops ethnl_privflags_request_ops = { | |
122 | .request_cmd = ETHTOOL_MSG_PRIVFLAGS_GET, | |
123 | .reply_cmd = ETHTOOL_MSG_PRIVFLAGS_GET_REPLY, | |
124 | .hdr_attr = ETHTOOL_A_PRIVFLAGS_HEADER, | |
e16c3386 MK |
125 | .req_info_size = sizeof(struct privflags_req_info), |
126 | .reply_data_size = sizeof(struct privflags_reply_data), | |
e16c3386 MK |
127 | |
128 | .prepare_data = privflags_prepare_data, | |
129 | .reply_size = privflags_reply_size, | |
130 | .fill_reply = privflags_fill_reply, | |
131 | .cleanup_data = privflags_cleanup_data, | |
132 | }; | |
f265d799 MK |
133 | |
134 | /* PRIVFLAGS_SET */ | |
135 | ||
ff419afa | 136 | const struct nla_policy ethnl_privflags_set_policy[] = { |
329d9c33 JK |
137 | [ETHTOOL_A_PRIVFLAGS_HEADER] = |
138 | NLA_POLICY_NESTED(ethnl_header_policy), | |
f265d799 MK |
139 | [ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_NESTED }, |
140 | }; | |
141 | ||
142 | int ethnl_set_privflags(struct sk_buff *skb, struct genl_info *info) | |
143 | { | |
f265d799 MK |
144 | const char (*names)[ETH_GSTRING_LEN] = NULL; |
145 | struct ethnl_req_info req_info = {}; | |
5028588b | 146 | struct nlattr **tb = info->attrs; |
f265d799 MK |
147 | const struct ethtool_ops *ops; |
148 | struct net_device *dev; | |
149 | unsigned int nflags; | |
150 | bool mod = false; | |
151 | bool compact; | |
152 | u32 flags; | |
153 | int ret; | |
154 | ||
f265d799 MK |
155 | if (!tb[ETHTOOL_A_PRIVFLAGS_FLAGS]) |
156 | return -EINVAL; | |
157 | ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact); | |
158 | if (ret < 0) | |
159 | return ret; | |
160 | ret = ethnl_parse_header_dev_get(&req_info, | |
161 | tb[ETHTOOL_A_PRIVFLAGS_HEADER], | |
162 | genl_info_net(info), info->extack, | |
163 | true); | |
164 | if (ret < 0) | |
165 | return ret; | |
166 | dev = req_info.dev; | |
167 | ops = dev->ethtool_ops; | |
b51fb771 | 168 | ret = -EOPNOTSUPP; |
f265d799 MK |
169 | if (!ops->get_priv_flags || !ops->set_priv_flags || |
170 | !ops->get_sset_count || !ops->get_strings) | |
b51fb771 | 171 | goto out_dev; |
f265d799 MK |
172 | |
173 | rtnl_lock(); | |
174 | ret = ethnl_ops_begin(dev); | |
175 | if (ret < 0) | |
176 | goto out_rtnl; | |
177 | ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names); | |
178 | if (ret < 0) | |
179 | goto out_ops; | |
180 | flags = ops->get_priv_flags(dev); | |
181 | ||
182 | ret = ethnl_update_bitset32(&flags, nflags, | |
183 | tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names, | |
184 | info->extack, &mod); | |
185 | if (ret < 0 || !mod) | |
186 | goto out_free; | |
187 | ret = ops->set_priv_flags(dev, flags); | |
111dcba3 MK |
188 | if (ret < 0) |
189 | goto out_free; | |
190 | ethtool_notify(dev, ETHTOOL_MSG_PRIVFLAGS_NTF, NULL); | |
f265d799 MK |
191 | |
192 | out_free: | |
193 | kfree(names); | |
194 | out_ops: | |
195 | ethnl_ops_complete(dev); | |
196 | out_rtnl: | |
197 | rtnl_unlock(); | |
b51fb771 | 198 | out_dev: |
34ac17ec | 199 | ethnl_parse_header_dev_put(&req_info); |
f265d799 MK |
200 | return ret; |
201 | } |