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 | |
21 | const struct nla_policy ethnl_privflags_get_policy[] = { |
22 | [ETHTOOL_A_PRIVFLAGS_HEADER] = |
23 | NLA_POLICY_NESTED(ethnl_header_policy), |
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(n: 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 | const 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, count: &nflags, names: &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(val: &data->priv_flags, mask: &all_flags, |
96 | nbits: data->n_priv_flags, |
97 | names: 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, attrtype: ETHTOOL_A_PRIVFLAGS_FLAGS, |
109 | val: &data->priv_flags, mask: &all_flags, |
110 | nbits: data->n_priv_flags, names: 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(objp: data->priv_flag_names); |
119 | } |
120 | |
121 | /* PRIVFLAGS_SET */ |
122 | |
123 | const struct nla_policy ethnl_privflags_set_policy[] = { |
124 | [ETHTOOL_A_PRIVFLAGS_HEADER] = |
125 | NLA_POLICY_NESTED(ethnl_header_policy), |
126 | [ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_NESTED }, |
127 | }; |
128 | |
129 | static int |
130 | ethnl_set_privflags_validate(struct ethnl_req_info *req_info, |
131 | struct genl_info *info) |
132 | { |
133 | const struct ethtool_ops *ops = req_info->dev->ethtool_ops; |
134 | |
135 | if (!info->attrs[ETHTOOL_A_PRIVFLAGS_FLAGS]) |
136 | return -EINVAL; |
137 | |
138 | if (!ops->get_priv_flags || !ops->set_priv_flags || |
139 | !ops->get_sset_count || !ops->get_strings) |
140 | return -EOPNOTSUPP; |
141 | return 1; |
142 | } |
143 | |
144 | static int |
145 | ethnl_set_privflags(struct ethnl_req_info *req_info, struct genl_info *info) |
146 | { |
147 | const char (*names)[ETH_GSTRING_LEN] = NULL; |
148 | struct net_device *dev = req_info->dev; |
149 | struct nlattr **tb = info->attrs; |
150 | unsigned int nflags; |
151 | bool mod = false; |
152 | bool compact; |
153 | u32 flags; |
154 | int ret; |
155 | |
156 | ret = ethnl_bitset_is_compact(bitset: tb[ETHTOOL_A_PRIVFLAGS_FLAGS], compact: &compact); |
157 | if (ret < 0) |
158 | return ret; |
159 | |
160 | ret = ethnl_get_priv_flags_info(dev, count: &nflags, names: compact ? NULL : &names); |
161 | if (ret < 0) |
162 | return ret; |
163 | flags = dev->ethtool_ops->get_priv_flags(dev); |
164 | |
165 | ret = ethnl_update_bitset32(bitmap: &flags, nbits: nflags, |
166 | attr: tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names, |
167 | extack: info->extack, mod: &mod); |
168 | if (ret < 0 || !mod) |
169 | goto out_free; |
170 | ret = dev->ethtool_ops->set_priv_flags(dev, flags); |
171 | if (ret < 0) |
172 | goto out_free; |
173 | ret = 1; |
174 | |
175 | out_free: |
176 | kfree(objp: names); |
177 | return ret; |
178 | } |
179 | |
180 | const struct ethnl_request_ops ethnl_privflags_request_ops = { |
181 | .request_cmd = ETHTOOL_MSG_PRIVFLAGS_GET, |
182 | .reply_cmd = ETHTOOL_MSG_PRIVFLAGS_GET_REPLY, |
183 | .hdr_attr = ETHTOOL_A_PRIVFLAGS_HEADER, |
184 | .req_info_size = sizeof(struct privflags_req_info), |
185 | .reply_data_size = sizeof(struct privflags_reply_data), |
186 | |
187 | .prepare_data = privflags_prepare_data, |
188 | .reply_size = privflags_reply_size, |
189 | .fill_reply = privflags_fill_reply, |
190 | .cleanup_data = privflags_cleanup_data, |
191 | |
192 | .set_validate = ethnl_set_privflags_validate, |
193 | .set = ethnl_set_privflags, |
194 | .set_ntf_cmd = ETHTOOL_MSG_PRIVFLAGS_NTF, |
195 | }; |
196 | |