Commit | Line | Data |
---|---|---|
2b4a8990 MK |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | ||
041b1c5d | 3 | #include <net/sock.h> |
2b4a8990 | 4 | #include <linux/ethtool_netlink.h> |
d43c65b0 | 5 | #include <linux/pm_runtime.h> |
2b4a8990 MK |
6 | #include "netlink.h" |
7 | ||
041b1c5d MK |
8 | static struct genl_family ethtool_genl_family; |
9 | ||
6b08d6c1 | 10 | static bool ethnl_ok __read_mostly; |
5cf2a548 | 11 | static u32 ethnl_bcast_seq; |
6b08d6c1 | 12 | |
a0de1cd3 JK |
13 | #define ETHTOOL_FLAGS_BASIC (ETHTOOL_FLAG_COMPACT_BITSETS | \ |
14 | ETHTOOL_FLAG_OMIT_REPLY) | |
15 | #define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS) | |
16 | ||
329d9c33 | 17 | const struct nla_policy ethnl_header_policy[] = { |
041b1c5d MK |
18 | [ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 }, |
19 | [ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING, | |
20 | .len = ALTIFNAMSIZ - 1 }, | |
a0de1cd3 JK |
21 | [ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32, |
22 | ETHTOOL_FLAGS_BASIC), | |
23 | }; | |
24 | ||
25 | const struct nla_policy ethnl_header_policy_stats[] = { | |
26 | [ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 }, | |
27 | [ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING, | |
28 | .len = ALTIFNAMSIZ - 1 }, | |
29 | [ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32, | |
30 | ETHTOOL_FLAGS_STATS), | |
041b1c5d MK |
31 | }; |
32 | ||
c5ab51df HK |
33 | int ethnl_ops_begin(struct net_device *dev) |
34 | { | |
d43c65b0 HK |
35 | int ret; |
36 | ||
41107ac2 | 37 | if (!dev) |
596690e9 | 38 | return -ENODEV; |
41107ac2 | 39 | |
d43c65b0 HK |
40 | if (dev->dev.parent) |
41 | pm_runtime_get_sync(dev->dev.parent); | |
41107ac2 | 42 | |
dde91ccf AT |
43 | if (!netif_device_present(dev) || |
44 | dev->reg_state == NETREG_UNREGISTERING) { | |
d43c65b0 HK |
45 | ret = -ENODEV; |
46 | goto err; | |
47 | } | |
48 | ||
49 | if (dev->ethtool_ops->begin) { | |
50 | ret = dev->ethtool_ops->begin(dev); | |
51 | if (ret) | |
52 | goto err; | |
53 | } | |
54 | ||
55 | return 0; | |
56 | err: | |
57 | if (dev->dev.parent) | |
58 | pm_runtime_put(dev->dev.parent); | |
59 | ||
60 | return ret; | |
c5ab51df HK |
61 | } |
62 | ||
63 | void ethnl_ops_complete(struct net_device *dev) | |
64 | { | |
596690e9 | 65 | if (dev->ethtool_ops->complete) |
c5ab51df | 66 | dev->ethtool_ops->complete(dev); |
d43c65b0 HK |
67 | |
68 | if (dev->dev.parent) | |
69 | pm_runtime_put(dev->dev.parent); | |
c5ab51df HK |
70 | } |
71 | ||
041b1c5d | 72 | /** |
98130546 | 73 | * ethnl_parse_header_dev_get() - parse request header |
041b1c5d MK |
74 | * @req_info: structure to put results into |
75 | * @header: nest attribute with request header | |
76 | * @net: request netns | |
77 | * @extack: netlink extack for error reporting | |
78 | * @require_dev: fail if no device identified in header | |
79 | * | |
80 | * Parse request header in nested attribute @nest and puts results into | |
81 | * the structure pointed to by @req_info. Extack from @info is used for error | |
82 | * reporting. If req_info->dev is not null on return, reference to it has | |
83 | * been taken. If error is returned, *req_info is null initialized and no | |
84 | * reference is held. | |
85 | * | |
86 | * Return: 0 on success or negative error code | |
87 | */ | |
98130546 MK |
88 | int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info, |
89 | const struct nlattr *header, struct net *net, | |
90 | struct netlink_ext_ack *extack, bool require_dev) | |
041b1c5d | 91 | { |
ff419afa | 92 | struct nlattr *tb[ARRAY_SIZE(ethnl_header_policy)]; |
041b1c5d MK |
93 | const struct nlattr *devname_attr; |
94 | struct net_device *dev = NULL; | |
2363d73a | 95 | u32 flags = 0; |
041b1c5d MK |
96 | int ret; |
97 | ||
98 | if (!header) { | |
500e1340 JK |
99 | if (!require_dev) |
100 | return 0; | |
041b1c5d MK |
101 | NL_SET_ERR_MSG(extack, "request header missing"); |
102 | return -EINVAL; | |
103 | } | |
a0de1cd3 JK |
104 | /* No validation here, command policy should have a nested policy set |
105 | * for the header, therefore validation should have already been done. | |
106 | */ | |
ff419afa | 107 | ret = nla_parse_nested(tb, ARRAY_SIZE(ethnl_header_policy) - 1, header, |
a0de1cd3 | 108 | NULL, extack); |
041b1c5d MK |
109 | if (ret < 0) |
110 | return ret; | |
a0de1cd3 | 111 | if (tb[ETHTOOL_A_HEADER_FLAGS]) |
2363d73a | 112 | flags = nla_get_u32(tb[ETHTOOL_A_HEADER_FLAGS]); |
041b1c5d | 113 | |
2363d73a | 114 | devname_attr = tb[ETHTOOL_A_HEADER_DEV_NAME]; |
041b1c5d MK |
115 | if (tb[ETHTOOL_A_HEADER_DEV_INDEX]) { |
116 | u32 ifindex = nla_get_u32(tb[ETHTOOL_A_HEADER_DEV_INDEX]); | |
117 | ||
70f7457a JK |
118 | dev = netdev_get_by_index(net, ifindex, &req_info->dev_tracker, |
119 | GFP_KERNEL); | |
041b1c5d MK |
120 | if (!dev) { |
121 | NL_SET_ERR_MSG_ATTR(extack, | |
122 | tb[ETHTOOL_A_HEADER_DEV_INDEX], | |
123 | "no device matches ifindex"); | |
124 | return -ENODEV; | |
125 | } | |
126 | /* if both ifindex and ifname are passed, they must match */ | |
127 | if (devname_attr && | |
128 | strncmp(dev->name, nla_data(devname_attr), IFNAMSIZ)) { | |
70f7457a | 129 | netdev_put(dev, &req_info->dev_tracker); |
041b1c5d MK |
130 | NL_SET_ERR_MSG_ATTR(extack, header, |
131 | "ifindex and name do not match"); | |
132 | return -ENODEV; | |
133 | } | |
134 | } else if (devname_attr) { | |
70f7457a JK |
135 | dev = netdev_get_by_name(net, nla_data(devname_attr), |
136 | &req_info->dev_tracker, GFP_KERNEL); | |
041b1c5d MK |
137 | if (!dev) { |
138 | NL_SET_ERR_MSG_ATTR(extack, devname_attr, | |
139 | "no device matches name"); | |
140 | return -ENODEV; | |
141 | } | |
142 | } else if (require_dev) { | |
143 | NL_SET_ERR_MSG_ATTR(extack, header, | |
144 | "neither ifindex nor name specified"); | |
145 | return -EINVAL; | |
146 | } | |
147 | ||
3bc14ea0 | 148 | req_info->dev = dev; |
2363d73a | 149 | req_info->flags = flags; |
041b1c5d MK |
150 | return 0; |
151 | } | |
152 | ||
153 | /** | |
154 | * ethnl_fill_reply_header() - Put common header into a reply message | |
155 | * @skb: skb with the message | |
156 | * @dev: network device to describe in header | |
157 | * @attrtype: attribute type to use for the nest | |
158 | * | |
159 | * Create a nested attribute with attributes describing given network device. | |
160 | * | |
161 | * Return: 0 on success, error value (-EMSGSIZE only) on error | |
162 | */ | |
163 | int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev, | |
164 | u16 attrtype) | |
165 | { | |
166 | struct nlattr *nest; | |
167 | ||
168 | if (!dev) | |
169 | return 0; | |
170 | nest = nla_nest_start(skb, attrtype); | |
171 | if (!nest) | |
172 | return -EMSGSIZE; | |
173 | ||
174 | if (nla_put_u32(skb, ETHTOOL_A_HEADER_DEV_INDEX, (u32)dev->ifindex) || | |
175 | nla_put_string(skb, ETHTOOL_A_HEADER_DEV_NAME, dev->name)) | |
176 | goto nla_put_failure; | |
177 | /* If more attributes are put into reply header, ethnl_header_size() | |
178 | * must be updated to account for them. | |
179 | */ | |
180 | ||
181 | nla_nest_end(skb, nest); | |
182 | return 0; | |
183 | ||
184 | nla_put_failure: | |
185 | nla_nest_cancel(skb, nest); | |
186 | return -EMSGSIZE; | |
187 | } | |
188 | ||
189 | /** | |
190 | * ethnl_reply_init() - Create skb for a reply and fill device identification | |
d2c4b444 MK |
191 | * @payload: payload length (without netlink and genetlink header) |
192 | * @dev: device the reply is about (may be null) | |
193 | * @cmd: ETHTOOL_MSG_* message type for reply | |
194 | * @hdr_attrtype: attribute type for common header | |
195 | * @info: genetlink info of the received packet we respond to | |
196 | * @ehdrp: place to store payload pointer returned by genlmsg_new() | |
041b1c5d MK |
197 | * |
198 | * Return: pointer to allocated skb on success, NULL on error | |
199 | */ | |
200 | struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd, | |
201 | u16 hdr_attrtype, struct genl_info *info, | |
202 | void **ehdrp) | |
203 | { | |
204 | struct sk_buff *skb; | |
205 | ||
206 | skb = genlmsg_new(payload, GFP_KERNEL); | |
207 | if (!skb) | |
208 | goto err; | |
209 | *ehdrp = genlmsg_put_reply(skb, info, ðtool_genl_family, 0, cmd); | |
210 | if (!*ehdrp) | |
211 | goto err_free; | |
212 | ||
213 | if (dev) { | |
214 | int ret; | |
215 | ||
216 | ret = ethnl_fill_reply_header(skb, dev, hdr_attrtype); | |
217 | if (ret < 0) | |
218 | goto err_free; | |
219 | } | |
220 | return skb; | |
221 | ||
222 | err_free: | |
223 | nlmsg_free(skb); | |
224 | err: | |
225 | if (info) | |
226 | GENL_SET_ERR_MSG(info, "failed to setup reply message"); | |
227 | return NULL; | |
228 | } | |
229 | ||
c7d759eb JK |
230 | void *ethnl_dump_put(struct sk_buff *skb, struct netlink_callback *cb, u8 cmd) |
231 | { | |
232 | return genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, | |
233 | ðtool_genl_family, 0, cmd); | |
234 | } | |
235 | ||
0df960f1 | 236 | void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd) |
5cf2a548 MK |
237 | { |
238 | return genlmsg_put(skb, 0, ++ethnl_bcast_seq, ðtool_genl_family, 0, | |
239 | cmd); | |
240 | } | |
241 | ||
0df960f1 | 242 | int ethnl_multicast(struct sk_buff *skb, struct net_device *dev) |
5cf2a548 MK |
243 | { |
244 | return genlmsg_multicast_netns(ðtool_genl_family, dev_net(dev), skb, | |
245 | 0, ETHNL_MCGRP_MONITOR, GFP_KERNEL); | |
246 | } | |
247 | ||
728480f1 MK |
248 | /* GET request helpers */ |
249 | ||
250 | /** | |
251 | * struct ethnl_dump_ctx - context structure for generic dumpit() callback | |
d2c4b444 MK |
252 | * @ops: request ops of currently processed message type |
253 | * @req_info: parsed request header of processed request | |
254 | * @reply_data: data needed to compose the reply | |
84e00d9b | 255 | * @pos_ifindex: saved iteration position - ifindex |
728480f1 MK |
256 | * |
257 | * These parameters are kept in struct netlink_callback as context preserved | |
258 | * between iterations. They are initialized by ethnl_default_start() and used | |
259 | * in ethnl_default_dumpit() and ethnl_default_done(). | |
260 | */ | |
261 | struct ethnl_dump_ctx { | |
262 | const struct ethnl_request_ops *ops; | |
263 | struct ethnl_req_info *req_info; | |
264 | struct ethnl_reply_data *reply_data; | |
84e00d9b | 265 | unsigned long pos_ifindex; |
728480f1 MK |
266 | }; |
267 | ||
268 | static const struct ethnl_request_ops * | |
269 | ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { | |
71921690 | 270 | [ETHTOOL_MSG_STRSET_GET] = ðnl_strset_request_ops, |
459e0b81 | 271 | [ETHTOOL_MSG_LINKINFO_GET] = ðnl_linkinfo_request_ops, |
04007961 | 272 | [ETHTOOL_MSG_LINKINFO_SET] = ðnl_linkinfo_request_ops, |
f625aa9b | 273 | [ETHTOOL_MSG_LINKMODES_GET] = ðnl_linkmodes_request_ops, |
04007961 | 274 | [ETHTOOL_MSG_LINKMODES_SET] = ðnl_linkmodes_request_ops, |
3d2b847f | 275 | [ETHTOOL_MSG_LINKSTATE_GET] = ðnl_linkstate_request_ops, |
6a94b8cc | 276 | [ETHTOOL_MSG_DEBUG_GET] = ðnl_debug_request_ops, |
04007961 | 277 | [ETHTOOL_MSG_DEBUG_SET] = ðnl_debug_request_ops, |
51ea22b0 | 278 | [ETHTOOL_MSG_WOL_GET] = ðnl_wol_request_ops, |
04007961 | 279 | [ETHTOOL_MSG_WOL_SET] = ðnl_wol_request_ops, |
0524399d | 280 | [ETHTOOL_MSG_FEATURES_GET] = ðnl_features_request_ops, |
e16c3386 | 281 | [ETHTOOL_MSG_PRIVFLAGS_GET] = ðnl_privflags_request_ops, |
04007961 | 282 | [ETHTOOL_MSG_PRIVFLAGS_SET] = ðnl_privflags_request_ops, |
e4a1717b | 283 | [ETHTOOL_MSG_RINGS_GET] = ðnl_rings_request_ops, |
04007961 | 284 | [ETHTOOL_MSG_RINGS_SET] = ðnl_rings_request_ops, |
0c84979c | 285 | [ETHTOOL_MSG_CHANNELS_GET] = ðnl_channels_request_ops, |
04007961 | 286 | [ETHTOOL_MSG_CHANNELS_SET] = ðnl_channels_request_ops, |
21727545 | 287 | [ETHTOOL_MSG_COALESCE_GET] = ðnl_coalesce_request_ops, |
04007961 | 288 | [ETHTOOL_MSG_COALESCE_SET] = ðnl_coalesce_request_ops, |
7f59fb32 | 289 | [ETHTOOL_MSG_PAUSE_GET] = ðnl_pause_request_ops, |
99132b6e | 290 | [ETHTOOL_MSG_PAUSE_SET] = ðnl_pause_request_ops, |
b7eeefe7 | 291 | [ETHTOOL_MSG_EEE_GET] = ðnl_eee_request_ops, |
04007961 | 292 | [ETHTOOL_MSG_EEE_SET] = ðnl_eee_request_ops, |
1e5d1f69 | 293 | [ETHTOOL_MSG_FEC_GET] = ðnl_fec_request_ops, |
04007961 | 294 | [ETHTOOL_MSG_FEC_SET] = ðnl_fec_request_ops, |
5b071c59 | 295 | [ETHTOOL_MSG_TSINFO_GET] = ðnl_tsinfo_request_ops, |
c781ff12 | 296 | [ETHTOOL_MSG_MODULE_EEPROM_GET] = ðnl_module_eeprom_request_ops, |
f09ea6fb | 297 | [ETHTOOL_MSG_STATS_GET] = ðnl_stats_request_ops, |
c156174a | 298 | [ETHTOOL_MSG_PHC_VCLOCKS_GET] = ðnl_phc_vclocks_request_ops, |
353407d9 | 299 | [ETHTOOL_MSG_MODULE_GET] = ðnl_module_request_ops, |
04007961 | 300 | [ETHTOOL_MSG_MODULE_SET] = ðnl_module_request_ops, |
18ff0bcd | 301 | [ETHTOOL_MSG_PSE_GET] = ðnl_pse_request_ops, |
04007961 | 302 | [ETHTOOL_MSG_PSE_SET] = ðnl_pse_request_ops, |
7112a046 | 303 | [ETHTOOL_MSG_RSS_GET] = ðnl_rss_request_ops, |
8580e16c | 304 | [ETHTOOL_MSG_PLCA_GET_CFG] = ðnl_plca_cfg_request_ops, |
04007961 | 305 | [ETHTOOL_MSG_PLCA_SET_CFG] = ðnl_plca_cfg_request_ops, |
8580e16c | 306 | [ETHTOOL_MSG_PLCA_GET_STATUS] = ðnl_plca_status_request_ops, |
2b30f829 | 307 | [ETHTOOL_MSG_MM_GET] = ðnl_mm_request_ops, |
04007961 | 308 | [ETHTOOL_MSG_MM_SET] = ðnl_mm_request_ops, |
728480f1 MK |
309 | }; |
310 | ||
311 | static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb) | |
312 | { | |
313 | return (struct ethnl_dump_ctx *)cb->ctx; | |
314 | } | |
315 | ||
316 | /** | |
317 | * ethnl_default_parse() - Parse request message | |
318 | * @req_info: pointer to structure to put data into | |
ec0e5b09 | 319 | * @info: genl_info from the request |
728480f1 | 320 | * @request_ops: struct request_ops for request type |
728480f1 MK |
321 | * @require_dev: fail if no device identified in header |
322 | * | |
323 | * Parse universal request header and call request specific ->parse_request() | |
324 | * callback (if defined) to parse the rest of the message. | |
325 | * | |
326 | * Return: 0 on success or negative error code | |
327 | */ | |
328 | static int ethnl_default_parse(struct ethnl_req_info *req_info, | |
ec0e5b09 | 329 | const struct genl_info *info, |
728480f1 | 330 | const struct ethnl_request_ops *request_ops, |
ec0e5b09 | 331 | bool require_dev) |
728480f1 | 332 | { |
ec0e5b09 | 333 | struct nlattr **tb = info->attrs; |
728480f1 MK |
334 | int ret; |
335 | ||
98130546 | 336 | ret = ethnl_parse_header_dev_get(req_info, tb[request_ops->hdr_attr], |
ec0e5b09 JK |
337 | genl_info_net(info), info->extack, |
338 | require_dev); | |
728480f1 | 339 | if (ret < 0) |
4f30974f | 340 | return ret; |
728480f1 MK |
341 | |
342 | if (request_ops->parse_request) { | |
ec0e5b09 | 343 | ret = request_ops->parse_request(req_info, tb, info->extack); |
728480f1 | 344 | if (ret < 0) |
4f30974f | 345 | return ret; |
728480f1 MK |
346 | } |
347 | ||
4f30974f | 348 | return 0; |
728480f1 MK |
349 | } |
350 | ||
351 | /** | |
352 | * ethnl_init_reply_data() - Initialize reply data for GET request | |
d2c4b444 MK |
353 | * @reply_data: pointer to embedded struct ethnl_reply_data |
354 | * @ops: instance of struct ethnl_request_ops describing the layout | |
355 | * @dev: network device to initialize the reply for | |
728480f1 MK |
356 | * |
357 | * Fills the reply data part with zeros and sets the dev member. Must be called | |
358 | * before calling the ->fill_reply() callback (for each iteration when handling | |
359 | * dump requests). | |
360 | */ | |
361 | static void ethnl_init_reply_data(struct ethnl_reply_data *reply_data, | |
362 | const struct ethnl_request_ops *ops, | |
363 | struct net_device *dev) | |
364 | { | |
365 | memset(reply_data, 0, ops->reply_data_size); | |
366 | reply_data->dev = dev; | |
367 | } | |
368 | ||
369 | /* default ->doit() handler for GET type requests */ | |
370 | static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info) | |
371 | { | |
372 | struct ethnl_reply_data *reply_data = NULL; | |
373 | struct ethnl_req_info *req_info = NULL; | |
374 | const u8 cmd = info->genlhdr->cmd; | |
375 | const struct ethnl_request_ops *ops; | |
4d1fb7cd | 376 | int hdr_len, reply_len; |
728480f1 MK |
377 | struct sk_buff *rskb; |
378 | void *reply_payload; | |
728480f1 MK |
379 | int ret; |
380 | ||
381 | ops = ethnl_default_requests[cmd]; | |
382 | if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", cmd)) | |
383 | return -EOPNOTSUPP; | |
4f5059e6 JK |
384 | if (GENL_REQ_ATTR_CHECK(info, ops->hdr_attr)) |
385 | return -EINVAL; | |
386 | ||
728480f1 MK |
387 | req_info = kzalloc(ops->req_info_size, GFP_KERNEL); |
388 | if (!req_info) | |
389 | return -ENOMEM; | |
390 | reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL); | |
391 | if (!reply_data) { | |
392 | kfree(req_info); | |
393 | return -ENOMEM; | |
394 | } | |
395 | ||
ec0e5b09 | 396 | ret = ethnl_default_parse(req_info, info, ops, !ops->allow_nodev_do); |
728480f1 MK |
397 | if (ret < 0) |
398 | goto err_dev; | |
399 | ethnl_init_reply_data(reply_data, ops, req_info->dev); | |
400 | ||
401 | rtnl_lock(); | |
402 | ret = ops->prepare_data(req_info, reply_data, info); | |
403 | rtnl_unlock(); | |
404 | if (ret < 0) | |
405 | goto err_cleanup; | |
d97772db | 406 | ret = ops->reply_size(req_info, reply_data); |
728480f1 MK |
407 | if (ret < 0) |
408 | goto err_cleanup; | |
4d1fb7cd | 409 | reply_len = ret; |
728480f1 | 410 | ret = -ENOMEM; |
4d1fb7cd JK |
411 | rskb = ethnl_reply_init(reply_len + ethnl_reply_header_size(), |
412 | req_info->dev, ops->reply_cmd, | |
728480f1 MK |
413 | ops->hdr_attr, info, &reply_payload); |
414 | if (!rskb) | |
415 | goto err_cleanup; | |
4d1fb7cd | 416 | hdr_len = rskb->len; |
728480f1 MK |
417 | ret = ops->fill_reply(rskb, req_info, reply_data); |
418 | if (ret < 0) | |
419 | goto err_msg; | |
4d1fb7cd JK |
420 | WARN_ONCE(rskb->len - hdr_len > reply_len, |
421 | "ethnl cmd %d: calculated reply length %d, but consumed %d\n", | |
422 | cmd, reply_len, rskb->len - hdr_len); | |
728480f1 MK |
423 | if (ops->cleanup_data) |
424 | ops->cleanup_data(reply_data); | |
425 | ||
426 | genlmsg_end(rskb, reply_payload); | |
d62607c3 | 427 | netdev_put(req_info->dev, &req_info->dev_tracker); |
728480f1 MK |
428 | kfree(reply_data); |
429 | kfree(req_info); | |
430 | return genlmsg_reply(rskb, info); | |
431 | ||
432 | err_msg: | |
433 | WARN_ONCE(ret == -EMSGSIZE, "calculated message payload length (%d) not sufficient\n", reply_len); | |
434 | nlmsg_free(rskb); | |
435 | err_cleanup: | |
436 | if (ops->cleanup_data) | |
437 | ops->cleanup_data(reply_data); | |
438 | err_dev: | |
d62607c3 | 439 | netdev_put(req_info->dev, &req_info->dev_tracker); |
728480f1 MK |
440 | kfree(reply_data); |
441 | kfree(req_info); | |
442 | return ret; | |
443 | } | |
444 | ||
445 | static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev, | |
365f9ae4 | 446 | const struct ethnl_dump_ctx *ctx, |
f946270d | 447 | const struct genl_info *info) |
728480f1 | 448 | { |
365f9ae4 | 449 | void *ehdr; |
728480f1 MK |
450 | int ret; |
451 | ||
f946270d | 452 | ehdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, |
cf754ae3 FFM |
453 | ðtool_genl_family, NLM_F_MULTI, |
454 | ctx->ops->reply_cmd); | |
365f9ae4 MK |
455 | if (!ehdr) |
456 | return -EMSGSIZE; | |
457 | ||
728480f1 MK |
458 | ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev); |
459 | rtnl_lock(); | |
f946270d | 460 | ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info); |
728480f1 MK |
461 | rtnl_unlock(); |
462 | if (ret < 0) | |
463 | goto out; | |
464 | ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr); | |
465 | if (ret < 0) | |
466 | goto out; | |
467 | ret = ctx->ops->fill_reply(skb, ctx->req_info, ctx->reply_data); | |
468 | ||
469 | out: | |
470 | if (ctx->ops->cleanup_data) | |
471 | ctx->ops->cleanup_data(ctx->reply_data); | |
472 | ctx->reply_data->dev = NULL; | |
365f9ae4 MK |
473 | if (ret < 0) |
474 | genlmsg_cancel(skb, ehdr); | |
475 | else | |
476 | genlmsg_end(skb, ehdr); | |
728480f1 MK |
477 | return ret; |
478 | } | |
479 | ||
e7689879 | 480 | /* Default ->dumpit() handler for GET requests. */ |
728480f1 MK |
481 | static int ethnl_default_dumpit(struct sk_buff *skb, |
482 | struct netlink_callback *cb) | |
483 | { | |
484 | struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb); | |
485 | struct net *net = sock_net(skb->sk); | |
84e00d9b | 486 | struct net_device *dev; |
728480f1 | 487 | int ret = 0; |
728480f1 | 488 | |
e7689879 | 489 | rcu_read_lock(); |
84e00d9b JK |
490 | for_each_netdev_dump(net, dev, ctx->pos_ifindex) { |
491 | dev_hold(dev); | |
e7689879 | 492 | rcu_read_unlock(); |
84e00d9b | 493 | |
f946270d | 494 | ret = ethnl_default_dump_one(skb, dev, ctx, genl_info_dump(cb)); |
84e00d9b | 495 | |
e7689879 | 496 | rcu_read_lock(); |
84e00d9b | 497 | dev_put(dev); |
728480f1 | 498 | |
84e00d9b JK |
499 | if (ret < 0 && ret != -EOPNOTSUPP) { |
500 | if (likely(skb->len)) | |
501 | ret = skb->len; | |
502 | break; | |
503 | } | |
cbeb989e | 504 | ret = 0; |
728480f1 | 505 | } |
e7689879 | 506 | rcu_read_unlock(); |
728480f1 | 507 | |
728480f1 MK |
508 | return ret; |
509 | } | |
510 | ||
511 | /* generic ->start() handler for GET requests */ | |
512 | static int ethnl_default_start(struct netlink_callback *cb) | |
513 | { | |
4f30974f | 514 | const struct genl_dumpit_info *info = genl_dumpit_info(cb); |
728480f1 MK |
515 | struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb); |
516 | struct ethnl_reply_data *reply_data; | |
517 | const struct ethnl_request_ops *ops; | |
518 | struct ethnl_req_info *req_info; | |
519 | struct genlmsghdr *ghdr; | |
520 | int ret; | |
521 | ||
522 | BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); | |
523 | ||
524 | ghdr = nlmsg_data(cb->nlh); | |
525 | ops = ethnl_default_requests[ghdr->cmd]; | |
526 | if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", ghdr->cmd)) | |
527 | return -EOPNOTSUPP; | |
528 | req_info = kzalloc(ops->req_info_size, GFP_KERNEL); | |
529 | if (!req_info) | |
530 | return -ENOMEM; | |
531 | reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL); | |
532 | if (!reply_data) { | |
a6dd0480 DC |
533 | ret = -ENOMEM; |
534 | goto free_req_info; | |
728480f1 MK |
535 | } |
536 | ||
ec0e5b09 | 537 | ret = ethnl_default_parse(req_info, &info->info, ops, false); |
728480f1 MK |
538 | if (req_info->dev) { |
539 | /* We ignore device specification in dump requests but as the | |
540 | * same parser as for non-dump (doit) requests is used, it | |
541 | * would take reference to the device if it finds one | |
542 | */ | |
d62607c3 | 543 | netdev_put(req_info->dev, &req_info->dev_tracker); |
728480f1 MK |
544 | req_info->dev = NULL; |
545 | } | |
546 | if (ret < 0) | |
a6dd0480 | 547 | goto free_reply_data; |
728480f1 MK |
548 | |
549 | ctx->ops = ops; | |
550 | ctx->req_info = req_info; | |
551 | ctx->reply_data = reply_data; | |
84e00d9b | 552 | ctx->pos_ifindex = 0; |
728480f1 MK |
553 | |
554 | return 0; | |
a6dd0480 DC |
555 | |
556 | free_reply_data: | |
557 | kfree(reply_data); | |
558 | free_req_info: | |
559 | kfree(req_info); | |
560 | ||
561 | return ret; | |
728480f1 MK |
562 | } |
563 | ||
564 | /* default ->done() handler for GET requests */ | |
565 | static int ethnl_default_done(struct netlink_callback *cb) | |
566 | { | |
567 | struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb); | |
568 | ||
569 | kfree(ctx->reply_data); | |
570 | kfree(ctx->req_info); | |
571 | ||
572 | return 0; | |
573 | } | |
574 | ||
99132b6e JK |
575 | static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info) |
576 | { | |
577 | const struct ethnl_request_ops *ops; | |
578 | struct ethnl_req_info req_info = {}; | |
579 | const u8 cmd = info->genlhdr->cmd; | |
580 | int ret; | |
581 | ||
582 | ops = ethnl_default_requests[cmd]; | |
583 | if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", cmd)) | |
584 | return -EOPNOTSUPP; | |
585 | if (GENL_REQ_ATTR_CHECK(info, ops->hdr_attr)) | |
586 | return -EINVAL; | |
587 | ||
588 | ret = ethnl_parse_header_dev_get(&req_info, info->attrs[ops->hdr_attr], | |
589 | genl_info_net(info), info->extack, | |
590 | true); | |
591 | if (ret < 0) | |
592 | return ret; | |
593 | ||
594 | if (ops->set_validate) { | |
595 | ret = ops->set_validate(&req_info, info); | |
596 | /* 0 means nothing to do */ | |
597 | if (ret <= 0) | |
598 | goto out_dev; | |
599 | } | |
600 | ||
601 | rtnl_lock(); | |
602 | ret = ethnl_ops_begin(req_info.dev); | |
603 | if (ret < 0) | |
604 | goto out_rtnl; | |
605 | ||
606 | ret = ops->set(&req_info, info); | |
607 | if (ret <= 0) | |
608 | goto out_ops; | |
609 | ethtool_notify(req_info.dev, ops->set_ntf_cmd, NULL); | |
610 | ||
611 | ret = 0; | |
612 | out_ops: | |
613 | ethnl_ops_complete(req_info.dev); | |
614 | out_rtnl: | |
615 | rtnl_unlock(); | |
616 | out_dev: | |
617 | ethnl_parse_header_dev_put(&req_info); | |
618 | return ret; | |
619 | } | |
620 | ||
5cf2a548 MK |
621 | static const struct ethnl_request_ops * |
622 | ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = { | |
73286734 | 623 | [ETHTOOL_MSG_LINKINFO_NTF] = ðnl_linkinfo_request_ops, |
1b1b1847 | 624 | [ETHTOOL_MSG_LINKMODES_NTF] = ðnl_linkmodes_request_ops, |
0bda7af3 | 625 | [ETHTOOL_MSG_DEBUG_NTF] = ðnl_debug_request_ops, |
67bffa79 | 626 | [ETHTOOL_MSG_WOL_NTF] = ðnl_wol_request_ops, |
9c6451ef | 627 | [ETHTOOL_MSG_FEATURES_NTF] = ðnl_features_request_ops, |
111dcba3 | 628 | [ETHTOOL_MSG_PRIVFLAGS_NTF] = ðnl_privflags_request_ops, |
bc9d1c99 | 629 | [ETHTOOL_MSG_RINGS_NTF] = ðnl_rings_request_ops, |
546379b9 | 630 | [ETHTOOL_MSG_CHANNELS_NTF] = ðnl_channels_request_ops, |
0cf3eac8 | 631 | [ETHTOOL_MSG_COALESCE_NTF] = ðnl_coalesce_request_ops, |
bf37faa3 | 632 | [ETHTOOL_MSG_PAUSE_NTF] = ðnl_pause_request_ops, |
6c5bc8fe | 633 | [ETHTOOL_MSG_EEE_NTF] = ðnl_eee_request_ops, |
1e5d1f69 | 634 | [ETHTOOL_MSG_FEC_NTF] = ðnl_fec_request_ops, |
353407d9 | 635 | [ETHTOOL_MSG_MODULE_NTF] = ðnl_module_request_ops, |
8580e16c | 636 | [ETHTOOL_MSG_PLCA_NTF] = ðnl_plca_cfg_request_ops, |
2b30f829 | 637 | [ETHTOOL_MSG_MM_NTF] = ðnl_mm_request_ops, |
5cf2a548 MK |
638 | }; |
639 | ||
640 | /* default notification handler */ | |
641 | static void ethnl_default_notify(struct net_device *dev, unsigned int cmd, | |
642 | const void *data) | |
643 | { | |
644 | struct ethnl_reply_data *reply_data; | |
645 | const struct ethnl_request_ops *ops; | |
646 | struct ethnl_req_info *req_info; | |
f946270d | 647 | struct genl_info info; |
5cf2a548 MK |
648 | struct sk_buff *skb; |
649 | void *reply_payload; | |
650 | int reply_len; | |
651 | int ret; | |
652 | ||
f946270d JK |
653 | genl_info_init_ntf(&info, ðtool_genl_family, cmd); |
654 | ||
5cf2a548 MK |
655 | if (WARN_ONCE(cmd > ETHTOOL_MSG_KERNEL_MAX || |
656 | !ethnl_default_notify_ops[cmd], | |
657 | "unexpected notification type %u\n", cmd)) | |
658 | return; | |
659 | ops = ethnl_default_notify_ops[cmd]; | |
660 | req_info = kzalloc(ops->req_info_size, GFP_KERNEL); | |
661 | if (!req_info) | |
662 | return; | |
663 | reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL); | |
664 | if (!reply_data) { | |
665 | kfree(req_info); | |
666 | return; | |
667 | } | |
668 | ||
669 | req_info->dev = dev; | |
670 | req_info->flags |= ETHTOOL_FLAG_COMPACT_BITSETS; | |
671 | ||
672 | ethnl_init_reply_data(reply_data, ops, dev); | |
f946270d | 673 | ret = ops->prepare_data(req_info, reply_data, &info); |
5cf2a548 MK |
674 | if (ret < 0) |
675 | goto err_cleanup; | |
d97772db | 676 | ret = ops->reply_size(req_info, reply_data); |
5cf2a548 MK |
677 | if (ret < 0) |
678 | goto err_cleanup; | |
7c87e32d | 679 | reply_len = ret + ethnl_reply_header_size(); |
5cf2a548 MK |
680 | skb = genlmsg_new(reply_len, GFP_KERNEL); |
681 | if (!skb) | |
682 | goto err_cleanup; | |
683 | reply_payload = ethnl_bcastmsg_put(skb, cmd); | |
684 | if (!reply_payload) | |
685 | goto err_skb; | |
686 | ret = ethnl_fill_reply_header(skb, dev, ops->hdr_attr); | |
687 | if (ret < 0) | |
688 | goto err_msg; | |
689 | ret = ops->fill_reply(skb, req_info, reply_data); | |
690 | if (ret < 0) | |
691 | goto err_msg; | |
692 | if (ops->cleanup_data) | |
693 | ops->cleanup_data(reply_data); | |
694 | ||
695 | genlmsg_end(skb, reply_payload); | |
696 | kfree(reply_data); | |
697 | kfree(req_info); | |
698 | ethnl_multicast(skb, dev); | |
699 | return; | |
700 | ||
701 | err_msg: | |
702 | WARN_ONCE(ret == -EMSGSIZE, | |
703 | "calculated message payload length (%d) not sufficient\n", | |
704 | reply_len); | |
705 | err_skb: | |
706 | nlmsg_free(skb); | |
707 | err_cleanup: | |
708 | if (ops->cleanup_data) | |
709 | ops->cleanup_data(reply_data); | |
710 | kfree(reply_data); | |
711 | kfree(req_info); | |
712 | return; | |
713 | } | |
714 | ||
6b08d6c1 MK |
715 | /* notifications */ |
716 | ||
717 | typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd, | |
718 | const void *data); | |
719 | ||
720 | static const ethnl_notify_handler_t ethnl_notify_handlers[] = { | |
73286734 | 721 | [ETHTOOL_MSG_LINKINFO_NTF] = ethnl_default_notify, |
1b1b1847 | 722 | [ETHTOOL_MSG_LINKMODES_NTF] = ethnl_default_notify, |
0bda7af3 | 723 | [ETHTOOL_MSG_DEBUG_NTF] = ethnl_default_notify, |
67bffa79 | 724 | [ETHTOOL_MSG_WOL_NTF] = ethnl_default_notify, |
9c6451ef | 725 | [ETHTOOL_MSG_FEATURES_NTF] = ethnl_default_notify, |
111dcba3 | 726 | [ETHTOOL_MSG_PRIVFLAGS_NTF] = ethnl_default_notify, |
bc9d1c99 | 727 | [ETHTOOL_MSG_RINGS_NTF] = ethnl_default_notify, |
546379b9 | 728 | [ETHTOOL_MSG_CHANNELS_NTF] = ethnl_default_notify, |
0cf3eac8 | 729 | [ETHTOOL_MSG_COALESCE_NTF] = ethnl_default_notify, |
bf37faa3 | 730 | [ETHTOOL_MSG_PAUSE_NTF] = ethnl_default_notify, |
6c5bc8fe | 731 | [ETHTOOL_MSG_EEE_NTF] = ethnl_default_notify, |
1e5d1f69 | 732 | [ETHTOOL_MSG_FEC_NTF] = ethnl_default_notify, |
353407d9 | 733 | [ETHTOOL_MSG_MODULE_NTF] = ethnl_default_notify, |
8580e16c | 734 | [ETHTOOL_MSG_PLCA_NTF] = ethnl_default_notify, |
2b30f829 | 735 | [ETHTOOL_MSG_MM_NTF] = ethnl_default_notify, |
6b08d6c1 MK |
736 | }; |
737 | ||
738 | void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data) | |
739 | { | |
740 | if (unlikely(!ethnl_ok)) | |
741 | return; | |
742 | ASSERT_RTNL(); | |
743 | ||
744 | if (likely(cmd < ARRAY_SIZE(ethnl_notify_handlers) && | |
745 | ethnl_notify_handlers[cmd])) | |
746 | ethnl_notify_handlers[cmd](dev, cmd, data); | |
747 | else | |
748 | WARN_ONCE(1, "notification %u not implemented (dev=%s)\n", | |
749 | cmd, netdev_name(dev)); | |
750 | } | |
751 | EXPORT_SYMBOL(ethtool_notify); | |
752 | ||
9c6451ef MK |
753 | static void ethnl_notify_features(struct netdev_notifier_info *info) |
754 | { | |
755 | struct net_device *dev = netdev_notifier_info_to_dev(info); | |
756 | ||
757 | ethtool_notify(dev, ETHTOOL_MSG_FEATURES_NTF, NULL); | |
758 | } | |
759 | ||
760 | static int ethnl_netdev_event(struct notifier_block *this, unsigned long event, | |
761 | void *ptr) | |
762 | { | |
763 | switch (event) { | |
764 | case NETDEV_FEAT_CHANGE: | |
765 | ethnl_notify_features(ptr); | |
766 | break; | |
767 | } | |
768 | ||
769 | return NOTIFY_DONE; | |
770 | } | |
771 | ||
772 | static struct notifier_block ethnl_netdev_notifier = { | |
773 | .notifier_call = ethnl_netdev_event, | |
774 | }; | |
775 | ||
2b4a8990 MK |
776 | /* genetlink setup */ |
777 | ||
778 | static const struct genl_ops ethtool_genl_ops[] = { | |
71921690 MK |
779 | { |
780 | .cmd = ETHTOOL_MSG_STRSET_GET, | |
781 | .doit = ethnl_default_doit, | |
782 | .start = ethnl_default_start, | |
783 | .dumpit = ethnl_default_dumpit, | |
784 | .done = ethnl_default_done, | |
4f30974f JK |
785 | .policy = ethnl_strset_get_policy, |
786 | .maxattr = ARRAY_SIZE(ethnl_strset_get_policy) - 1, | |
71921690 | 787 | }, |
459e0b81 MK |
788 | { |
789 | .cmd = ETHTOOL_MSG_LINKINFO_GET, | |
790 | .doit = ethnl_default_doit, | |
791 | .start = ethnl_default_start, | |
792 | .dumpit = ethnl_default_dumpit, | |
793 | .done = ethnl_default_done, | |
4f30974f JK |
794 | .policy = ethnl_linkinfo_get_policy, |
795 | .maxattr = ARRAY_SIZE(ethnl_linkinfo_get_policy) - 1, | |
459e0b81 | 796 | }, |
a53f3d41 MK |
797 | { |
798 | .cmd = ETHTOOL_MSG_LINKINFO_SET, | |
799 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 800 | .doit = ethnl_default_set_doit, |
5028588b JK |
801 | .policy = ethnl_linkinfo_set_policy, |
802 | .maxattr = ARRAY_SIZE(ethnl_linkinfo_set_policy) - 1, | |
a53f3d41 | 803 | }, |
f625aa9b MK |
804 | { |
805 | .cmd = ETHTOOL_MSG_LINKMODES_GET, | |
806 | .doit = ethnl_default_doit, | |
807 | .start = ethnl_default_start, | |
808 | .dumpit = ethnl_default_dumpit, | |
809 | .done = ethnl_default_done, | |
4f30974f JK |
810 | .policy = ethnl_linkmodes_get_policy, |
811 | .maxattr = ARRAY_SIZE(ethnl_linkmodes_get_policy) - 1, | |
f625aa9b | 812 | }, |
bfbcfe20 MK |
813 | { |
814 | .cmd = ETHTOOL_MSG_LINKMODES_SET, | |
815 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 816 | .doit = ethnl_default_set_doit, |
5028588b JK |
817 | .policy = ethnl_linkmodes_set_policy, |
818 | .maxattr = ARRAY_SIZE(ethnl_linkmodes_set_policy) - 1, | |
bfbcfe20 | 819 | }, |
3d2b847f MK |
820 | { |
821 | .cmd = ETHTOOL_MSG_LINKSTATE_GET, | |
822 | .doit = ethnl_default_doit, | |
823 | .start = ethnl_default_start, | |
824 | .dumpit = ethnl_default_dumpit, | |
825 | .done = ethnl_default_done, | |
4f30974f JK |
826 | .policy = ethnl_linkstate_get_policy, |
827 | .maxattr = ARRAY_SIZE(ethnl_linkstate_get_policy) - 1, | |
3d2b847f | 828 | }, |
6a94b8cc MK |
829 | { |
830 | .cmd = ETHTOOL_MSG_DEBUG_GET, | |
831 | .doit = ethnl_default_doit, | |
832 | .start = ethnl_default_start, | |
833 | .dumpit = ethnl_default_dumpit, | |
834 | .done = ethnl_default_done, | |
4f30974f JK |
835 | .policy = ethnl_debug_get_policy, |
836 | .maxattr = ARRAY_SIZE(ethnl_debug_get_policy) - 1, | |
6a94b8cc | 837 | }, |
e54d04e3 MK |
838 | { |
839 | .cmd = ETHTOOL_MSG_DEBUG_SET, | |
840 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 841 | .doit = ethnl_default_set_doit, |
5028588b JK |
842 | .policy = ethnl_debug_set_policy, |
843 | .maxattr = ARRAY_SIZE(ethnl_debug_set_policy) - 1, | |
e54d04e3 | 844 | }, |
51ea22b0 MK |
845 | { |
846 | .cmd = ETHTOOL_MSG_WOL_GET, | |
847 | .flags = GENL_UNS_ADMIN_PERM, | |
848 | .doit = ethnl_default_doit, | |
849 | .start = ethnl_default_start, | |
850 | .dumpit = ethnl_default_dumpit, | |
851 | .done = ethnl_default_done, | |
4f30974f JK |
852 | .policy = ethnl_wol_get_policy, |
853 | .maxattr = ARRAY_SIZE(ethnl_wol_get_policy) - 1, | |
51ea22b0 | 854 | }, |
8d425b19 MK |
855 | { |
856 | .cmd = ETHTOOL_MSG_WOL_SET, | |
857 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 858 | .doit = ethnl_default_set_doit, |
5028588b JK |
859 | .policy = ethnl_wol_set_policy, |
860 | .maxattr = ARRAY_SIZE(ethnl_wol_set_policy) - 1, | |
8d425b19 | 861 | }, |
0524399d MK |
862 | { |
863 | .cmd = ETHTOOL_MSG_FEATURES_GET, | |
864 | .doit = ethnl_default_doit, | |
865 | .start = ethnl_default_start, | |
866 | .dumpit = ethnl_default_dumpit, | |
867 | .done = ethnl_default_done, | |
4f30974f JK |
868 | .policy = ethnl_features_get_policy, |
869 | .maxattr = ARRAY_SIZE(ethnl_features_get_policy) - 1, | |
0524399d | 870 | }, |
0980bfcd MK |
871 | { |
872 | .cmd = ETHTOOL_MSG_FEATURES_SET, | |
873 | .flags = GENL_UNS_ADMIN_PERM, | |
874 | .doit = ethnl_set_features, | |
5028588b JK |
875 | .policy = ethnl_features_set_policy, |
876 | .maxattr = ARRAY_SIZE(ethnl_features_set_policy) - 1, | |
0980bfcd | 877 | }, |
e16c3386 MK |
878 | { |
879 | .cmd = ETHTOOL_MSG_PRIVFLAGS_GET, | |
880 | .doit = ethnl_default_doit, | |
881 | .start = ethnl_default_start, | |
882 | .dumpit = ethnl_default_dumpit, | |
883 | .done = ethnl_default_done, | |
4f30974f JK |
884 | .policy = ethnl_privflags_get_policy, |
885 | .maxattr = ARRAY_SIZE(ethnl_privflags_get_policy) - 1, | |
e16c3386 | 886 | }, |
f265d799 MK |
887 | { |
888 | .cmd = ETHTOOL_MSG_PRIVFLAGS_SET, | |
889 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 890 | .doit = ethnl_default_set_doit, |
5028588b JK |
891 | .policy = ethnl_privflags_set_policy, |
892 | .maxattr = ARRAY_SIZE(ethnl_privflags_set_policy) - 1, | |
f265d799 | 893 | }, |
e4a1717b MK |
894 | { |
895 | .cmd = ETHTOOL_MSG_RINGS_GET, | |
896 | .doit = ethnl_default_doit, | |
897 | .start = ethnl_default_start, | |
898 | .dumpit = ethnl_default_dumpit, | |
899 | .done = ethnl_default_done, | |
4f30974f JK |
900 | .policy = ethnl_rings_get_policy, |
901 | .maxattr = ARRAY_SIZE(ethnl_rings_get_policy) - 1, | |
e4a1717b | 902 | }, |
2fc2929e MK |
903 | { |
904 | .cmd = ETHTOOL_MSG_RINGS_SET, | |
905 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 906 | .doit = ethnl_default_set_doit, |
5028588b JK |
907 | .policy = ethnl_rings_set_policy, |
908 | .maxattr = ARRAY_SIZE(ethnl_rings_set_policy) - 1, | |
2fc2929e | 909 | }, |
0c84979c MK |
910 | { |
911 | .cmd = ETHTOOL_MSG_CHANNELS_GET, | |
912 | .doit = ethnl_default_doit, | |
913 | .start = ethnl_default_start, | |
914 | .dumpit = ethnl_default_dumpit, | |
915 | .done = ethnl_default_done, | |
4f30974f JK |
916 | .policy = ethnl_channels_get_policy, |
917 | .maxattr = ARRAY_SIZE(ethnl_channels_get_policy) - 1, | |
0c84979c | 918 | }, |
e19c591e MK |
919 | { |
920 | .cmd = ETHTOOL_MSG_CHANNELS_SET, | |
921 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 922 | .doit = ethnl_default_set_doit, |
fd15dd05 JB |
923 | .policy = ethnl_channels_set_policy, |
924 | .maxattr = ARRAY_SIZE(ethnl_channels_set_policy) - 1, | |
e19c591e | 925 | }, |
21727545 MK |
926 | { |
927 | .cmd = ETHTOOL_MSG_COALESCE_GET, | |
928 | .doit = ethnl_default_doit, | |
929 | .start = ethnl_default_start, | |
930 | .dumpit = ethnl_default_dumpit, | |
931 | .done = ethnl_default_done, | |
4f30974f JK |
932 | .policy = ethnl_coalesce_get_policy, |
933 | .maxattr = ARRAY_SIZE(ethnl_coalesce_get_policy) - 1, | |
21727545 | 934 | }, |
9881418c MK |
935 | { |
936 | .cmd = ETHTOOL_MSG_COALESCE_SET, | |
937 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 938 | .doit = ethnl_default_set_doit, |
5028588b JK |
939 | .policy = ethnl_coalesce_set_policy, |
940 | .maxattr = ARRAY_SIZE(ethnl_coalesce_set_policy) - 1, | |
9881418c | 941 | }, |
7f59fb32 MK |
942 | { |
943 | .cmd = ETHTOOL_MSG_PAUSE_GET, | |
944 | .doit = ethnl_default_doit, | |
945 | .start = ethnl_default_start, | |
946 | .dumpit = ethnl_default_dumpit, | |
947 | .done = ethnl_default_done, | |
4f30974f JK |
948 | .policy = ethnl_pause_get_policy, |
949 | .maxattr = ARRAY_SIZE(ethnl_pause_get_policy) - 1, | |
7f59fb32 | 950 | }, |
3ab87993 MK |
951 | { |
952 | .cmd = ETHTOOL_MSG_PAUSE_SET, | |
953 | .flags = GENL_UNS_ADMIN_PERM, | |
99132b6e | 954 | .doit = ethnl_default_set_doit, |
5028588b JK |
955 | .policy = ethnl_pause_set_policy, |
956 | .maxattr = ARRAY_SIZE(ethnl_pause_set_policy) - 1, | |
3ab87993 | 957 | }, |
b7eeefe7 MK |
958 | { |
959 | .cmd = ETHTOOL_MSG_EEE_GET, | |
960 | .doit = ethnl_default_doit, | |
961 | .start = ethnl_default_start, | |
962 | .dumpit = ethnl_default_dumpit, | |
963 | .done = ethnl_default_done, | |
4f30974f JK |
964 | .policy = ethnl_eee_get_policy, |
965 | .maxattr = ARRAY_SIZE(ethnl_eee_get_policy) - 1, | |
b7eeefe7 | 966 | }, |
fd77be7b MK |
967 | { |
968 | .cmd = ETHTOOL_MSG_EEE_SET, | |
969 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 970 | .doit = ethnl_default_set_doit, |
5028588b JK |
971 | .policy = ethnl_eee_set_policy, |
972 | .maxattr = ARRAY_SIZE(ethnl_eee_set_policy) - 1, | |
fd77be7b | 973 | }, |
5b071c59 MK |
974 | { |
975 | .cmd = ETHTOOL_MSG_TSINFO_GET, | |
976 | .doit = ethnl_default_doit, | |
977 | .start = ethnl_default_start, | |
978 | .dumpit = ethnl_default_dumpit, | |
979 | .done = ethnl_default_done, | |
4f30974f JK |
980 | .policy = ethnl_tsinfo_get_policy, |
981 | .maxattr = ARRAY_SIZE(ethnl_tsinfo_get_policy) - 1, | |
5b071c59 | 982 | }, |
11ca3c42 AL |
983 | { |
984 | .cmd = ETHTOOL_MSG_CABLE_TEST_ACT, | |
985 | .flags = GENL_UNS_ADMIN_PERM, | |
986 | .doit = ethnl_act_cable_test, | |
5028588b JK |
987 | .policy = ethnl_cable_test_act_policy, |
988 | .maxattr = ARRAY_SIZE(ethnl_cable_test_act_policy) - 1, | |
11ca3c42 | 989 | }, |
1a644de2 AL |
990 | { |
991 | .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_ACT, | |
992 | .flags = GENL_UNS_ADMIN_PERM, | |
993 | .doit = ethnl_act_cable_test_tdr, | |
5028588b JK |
994 | .policy = ethnl_cable_test_tdr_act_policy, |
995 | .maxattr = ARRAY_SIZE(ethnl_cable_test_tdr_act_policy) - 1, | |
1a644de2 | 996 | }, |
c7d759eb JK |
997 | { |
998 | .cmd = ETHTOOL_MSG_TUNNEL_INFO_GET, | |
999 | .doit = ethnl_tunnel_info_doit, | |
1000 | .start = ethnl_tunnel_info_start, | |
1001 | .dumpit = ethnl_tunnel_info_dumpit, | |
4f30974f JK |
1002 | .policy = ethnl_tunnel_info_get_policy, |
1003 | .maxattr = ARRAY_SIZE(ethnl_tunnel_info_get_policy) - 1, | |
c7d759eb | 1004 | }, |
1e5d1f69 JK |
1005 | { |
1006 | .cmd = ETHTOOL_MSG_FEC_GET, | |
1007 | .doit = ethnl_default_doit, | |
1008 | .start = ethnl_default_start, | |
1009 | .dumpit = ethnl_default_dumpit, | |
1010 | .done = ethnl_default_done, | |
1011 | .policy = ethnl_fec_get_policy, | |
1012 | .maxattr = ARRAY_SIZE(ethnl_fec_get_policy) - 1, | |
1013 | }, | |
1014 | { | |
1015 | .cmd = ETHTOOL_MSG_FEC_SET, | |
1016 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 1017 | .doit = ethnl_default_set_doit, |
1e5d1f69 JK |
1018 | .policy = ethnl_fec_set_policy, |
1019 | .maxattr = ARRAY_SIZE(ethnl_fec_set_policy) - 1, | |
1020 | }, | |
c781ff12 VT |
1021 | { |
1022 | .cmd = ETHTOOL_MSG_MODULE_EEPROM_GET, | |
1023 | .flags = GENL_UNS_ADMIN_PERM, | |
1024 | .doit = ethnl_default_doit, | |
1025 | .start = ethnl_default_start, | |
1026 | .dumpit = ethnl_default_dumpit, | |
1027 | .done = ethnl_default_done, | |
1028 | .policy = ethnl_module_eeprom_get_policy, | |
1029 | .maxattr = ARRAY_SIZE(ethnl_module_eeprom_get_policy) - 1, | |
1030 | }, | |
f09ea6fb JK |
1031 | { |
1032 | .cmd = ETHTOOL_MSG_STATS_GET, | |
1033 | .doit = ethnl_default_doit, | |
1034 | .start = ethnl_default_start, | |
1035 | .dumpit = ethnl_default_dumpit, | |
1036 | .done = ethnl_default_done, | |
1037 | .policy = ethnl_stats_get_policy, | |
1038 | .maxattr = ARRAY_SIZE(ethnl_stats_get_policy) - 1, | |
1039 | }, | |
c156174a YL |
1040 | { |
1041 | .cmd = ETHTOOL_MSG_PHC_VCLOCKS_GET, | |
1042 | .doit = ethnl_default_doit, | |
1043 | .start = ethnl_default_start, | |
1044 | .dumpit = ethnl_default_dumpit, | |
1045 | .done = ethnl_default_done, | |
1046 | .policy = ethnl_phc_vclocks_get_policy, | |
1047 | .maxattr = ARRAY_SIZE(ethnl_phc_vclocks_get_policy) - 1, | |
1048 | }, | |
353407d9 IS |
1049 | { |
1050 | .cmd = ETHTOOL_MSG_MODULE_GET, | |
1051 | .doit = ethnl_default_doit, | |
1052 | .start = ethnl_default_start, | |
1053 | .dumpit = ethnl_default_dumpit, | |
1054 | .done = ethnl_default_done, | |
1055 | .policy = ethnl_module_get_policy, | |
1056 | .maxattr = ARRAY_SIZE(ethnl_module_get_policy) - 1, | |
1057 | }, | |
1058 | { | |
1059 | .cmd = ETHTOOL_MSG_MODULE_SET, | |
1060 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 1061 | .doit = ethnl_default_set_doit, |
353407d9 IS |
1062 | .policy = ethnl_module_set_policy, |
1063 | .maxattr = ARRAY_SIZE(ethnl_module_set_policy) - 1, | |
1064 | }, | |
18ff0bcd OR |
1065 | { |
1066 | .cmd = ETHTOOL_MSG_PSE_GET, | |
1067 | .doit = ethnl_default_doit, | |
1068 | .start = ethnl_default_start, | |
1069 | .dumpit = ethnl_default_dumpit, | |
1070 | .done = ethnl_default_done, | |
1071 | .policy = ethnl_pse_get_policy, | |
1072 | .maxattr = ARRAY_SIZE(ethnl_pse_get_policy) - 1, | |
1073 | }, | |
1074 | { | |
1075 | .cmd = ETHTOOL_MSG_PSE_SET, | |
1076 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 1077 | .doit = ethnl_default_set_doit, |
18ff0bcd OR |
1078 | .policy = ethnl_pse_set_policy, |
1079 | .maxattr = ARRAY_SIZE(ethnl_pse_set_policy) - 1, | |
1080 | }, | |
7112a046 SM |
1081 | { |
1082 | .cmd = ETHTOOL_MSG_RSS_GET, | |
1083 | .doit = ethnl_default_doit, | |
1084 | .policy = ethnl_rss_get_policy, | |
1085 | .maxattr = ARRAY_SIZE(ethnl_rss_get_policy) - 1, | |
1086 | }, | |
8580e16c PB |
1087 | { |
1088 | .cmd = ETHTOOL_MSG_PLCA_GET_CFG, | |
1089 | .doit = ethnl_default_doit, | |
1090 | .start = ethnl_default_start, | |
1091 | .dumpit = ethnl_default_dumpit, | |
1092 | .done = ethnl_default_done, | |
1093 | .policy = ethnl_plca_get_cfg_policy, | |
1094 | .maxattr = ARRAY_SIZE(ethnl_plca_get_cfg_policy) - 1, | |
1095 | }, | |
1096 | { | |
1097 | .cmd = ETHTOOL_MSG_PLCA_SET_CFG, | |
1098 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 1099 | .doit = ethnl_default_set_doit, |
8580e16c PB |
1100 | .policy = ethnl_plca_set_cfg_policy, |
1101 | .maxattr = ARRAY_SIZE(ethnl_plca_set_cfg_policy) - 1, | |
1102 | }, | |
1103 | { | |
1104 | .cmd = ETHTOOL_MSG_PLCA_GET_STATUS, | |
1105 | .doit = ethnl_default_doit, | |
1106 | .start = ethnl_default_start, | |
1107 | .dumpit = ethnl_default_dumpit, | |
1108 | .done = ethnl_default_done, | |
1109 | .policy = ethnl_plca_get_status_policy, | |
1110 | .maxattr = ARRAY_SIZE(ethnl_plca_get_status_policy) - 1, | |
1111 | }, | |
2b30f829 VO |
1112 | { |
1113 | .cmd = ETHTOOL_MSG_MM_GET, | |
1114 | .doit = ethnl_default_doit, | |
1115 | .start = ethnl_default_start, | |
1116 | .dumpit = ethnl_default_dumpit, | |
1117 | .done = ethnl_default_done, | |
1118 | .policy = ethnl_mm_get_policy, | |
1119 | .maxattr = ARRAY_SIZE(ethnl_mm_get_policy) - 1, | |
1120 | }, | |
1121 | { | |
1122 | .cmd = ETHTOOL_MSG_MM_SET, | |
1123 | .flags = GENL_UNS_ADMIN_PERM, | |
04007961 | 1124 | .doit = ethnl_default_set_doit, |
2b30f829 VO |
1125 | .policy = ethnl_mm_set_policy, |
1126 | .maxattr = ARRAY_SIZE(ethnl_mm_set_policy) - 1, | |
1127 | }, | |
2b4a8990 MK |
1128 | }; |
1129 | ||
6b08d6c1 MK |
1130 | static const struct genl_multicast_group ethtool_nl_mcgrps[] = { |
1131 | [ETHNL_MCGRP_MONITOR] = { .name = ETHTOOL_MCGRP_MONITOR_NAME }, | |
1132 | }; | |
1133 | ||
78b70155 | 1134 | static struct genl_family ethtool_genl_family __ro_after_init = { |
2b4a8990 MK |
1135 | .name = ETHTOOL_GENL_NAME, |
1136 | .version = ETHTOOL_GENL_VERSION, | |
1137 | .netnsok = true, | |
1138 | .parallel_ops = true, | |
1139 | .ops = ethtool_genl_ops, | |
1140 | .n_ops = ARRAY_SIZE(ethtool_genl_ops), | |
9c5d03d3 | 1141 | .resv_start_op = ETHTOOL_MSG_MODULE_GET + 1, |
6b08d6c1 MK |
1142 | .mcgrps = ethtool_nl_mcgrps, |
1143 | .n_mcgrps = ARRAY_SIZE(ethtool_nl_mcgrps), | |
2b4a8990 MK |
1144 | }; |
1145 | ||
1146 | /* module setup */ | |
1147 | ||
1148 | static int __init ethnl_init(void) | |
1149 | { | |
1150 | int ret; | |
1151 | ||
1152 | ret = genl_register_family(ðtool_genl_family); | |
1153 | if (WARN(ret < 0, "ethtool: genetlink family registration failed")) | |
1154 | return ret; | |
6b08d6c1 | 1155 | ethnl_ok = true; |
2b4a8990 | 1156 | |
9c6451ef MK |
1157 | ret = register_netdevice_notifier(ðnl_netdev_notifier); |
1158 | WARN(ret < 0, "ethtool: net device notifier registration failed"); | |
1159 | return ret; | |
2b4a8990 MK |
1160 | } |
1161 | ||
1162 | subsys_initcall(ethnl_init); |