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 | ||
f265d799 MK |
121 | /* PRIVFLAGS_SET */ |
122 | ||
ff419afa | 123 | const struct nla_policy ethnl_privflags_set_policy[] = { |
329d9c33 JK |
124 | [ETHTOOL_A_PRIVFLAGS_HEADER] = |
125 | NLA_POLICY_NESTED(ethnl_header_policy), | |
f265d799 MK |
126 | [ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_NESTED }, |
127 | }; | |
128 | ||
04007961 JK |
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) | |
f265d799 | 146 | { |
f265d799 | 147 | const char (*names)[ETH_GSTRING_LEN] = NULL; |
04007961 | 148 | struct net_device *dev = req_info->dev; |
5028588b | 149 | struct nlattr **tb = info->attrs; |
f265d799 MK |
150 | unsigned int nflags; |
151 | bool mod = false; | |
152 | bool compact; | |
153 | u32 flags; | |
154 | int ret; | |
155 | ||
f265d799 MK |
156 | ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact); |
157 | if (ret < 0) | |
158 | return ret; | |
f265d799 | 159 | |
f265d799 MK |
160 | ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names); |
161 | if (ret < 0) | |
04007961 JK |
162 | return ret; |
163 | flags = dev->ethtool_ops->get_priv_flags(dev); | |
f265d799 MK |
164 | |
165 | ret = ethnl_update_bitset32(&flags, nflags, | |
166 | tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names, | |
167 | info->extack, &mod); | |
168 | if (ret < 0 || !mod) | |
169 | goto out_free; | |
04007961 | 170 | ret = dev->ethtool_ops->set_priv_flags(dev, flags); |
111dcba3 MK |
171 | if (ret < 0) |
172 | goto out_free; | |
04007961 | 173 | ret = 1; |
f265d799 MK |
174 | |
175 | out_free: | |
176 | kfree(names); | |
f265d799 MK |
177 | return ret; |
178 | } | |
04007961 JK |
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 | }; |