1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <net/sock.h> |
4 | #include <linux/ethtool_netlink.h> |
5 | #include <linux/pm_runtime.h> |
6 | #include "netlink.h" |
7 | |
8 | static struct genl_family ethtool_genl_family; |
9 | |
10 | static bool ethnl_ok __read_mostly; |
11 | static u32 ethnl_bcast_seq; |
12 | |
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 | |
17 | const struct nla_policy [] = { |
18 | [ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 }, |
19 | [ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING, |
20 | .len = ALTIFNAMSIZ - 1 }, |
21 | [ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32, |
22 | ETHTOOL_FLAGS_BASIC), |
23 | }; |
24 | |
25 | const struct nla_policy [] = { |
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), |
31 | }; |
32 | |
33 | int ethnl_ops_begin(struct net_device *dev) |
34 | { |
35 | int ret; |
36 | |
37 | if (!dev) |
38 | return -ENODEV; |
39 | |
40 | if (dev->dev.parent) |
41 | pm_runtime_get_sync(dev: dev->dev.parent); |
42 | |
43 | if (!netif_device_present(dev) || |
44 | dev->reg_state == NETREG_UNREGISTERING) { |
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->dev.parent); |
59 | |
60 | return ret; |
61 | } |
62 | |
63 | void ethnl_ops_complete(struct net_device *dev) |
64 | { |
65 | if (dev->ethtool_ops->complete) |
66 | dev->ethtool_ops->complete(dev); |
67 | |
68 | if (dev->dev.parent) |
69 | pm_runtime_put(dev: dev->dev.parent); |
70 | } |
71 | |
72 | /** |
73 | * ethnl_parse_header_dev_get() - parse request header |
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 | */ |
88 | int (struct ethnl_req_info *req_info, |
89 | const struct nlattr *, struct net *net, |
90 | struct netlink_ext_ack *extack, bool require_dev) |
91 | { |
92 | struct nlattr *tb[ARRAY_SIZE(ethnl_header_policy)]; |
93 | const struct nlattr *devname_attr; |
94 | struct net_device *dev = NULL; |
95 | u32 flags = 0; |
96 | int ret; |
97 | |
98 | if (!header) { |
99 | if (!require_dev) |
100 | return 0; |
101 | NL_SET_ERR_MSG(extack, "request header missing" ); |
102 | return -EINVAL; |
103 | } |
104 | /* No validation here, command policy should have a nested policy set |
105 | * for the header, therefore validation should have already been done. |
106 | */ |
107 | ret = nla_parse_nested(tb, ARRAY_SIZE(ethnl_header_policy) - 1, nla: header, |
108 | NULL, extack); |
109 | if (ret < 0) |
110 | return ret; |
111 | if (tb[ETHTOOL_A_HEADER_FLAGS]) |
112 | flags = nla_get_u32(nla: tb[ETHTOOL_A_HEADER_FLAGS]); |
113 | |
114 | devname_attr = tb[ETHTOOL_A_HEADER_DEV_NAME]; |
115 | if (tb[ETHTOOL_A_HEADER_DEV_INDEX]) { |
116 | u32 ifindex = nla_get_u32(nla: tb[ETHTOOL_A_HEADER_DEV_INDEX]); |
117 | |
118 | dev = netdev_get_by_index(net, ifindex, tracker: &req_info->dev_tracker, |
119 | GFP_KERNEL); |
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(nla: devname_attr), IFNAMSIZ)) { |
129 | netdev_put(dev, tracker: &req_info->dev_tracker); |
130 | NL_SET_ERR_MSG_ATTR(extack, header, |
131 | "ifindex and name do not match" ); |
132 | return -ENODEV; |
133 | } |
134 | } else if (devname_attr) { |
135 | dev = netdev_get_by_name(net, name: nla_data(nla: devname_attr), |
136 | tracker: &req_info->dev_tracker, GFP_KERNEL); |
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 | |
148 | req_info->dev = dev; |
149 | req_info->flags = flags; |
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 (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, attrtype: ETHTOOL_A_HEADER_DEV_INDEX, value: (u32)dev->ifindex) || |
175 | nla_put_string(skb, attrtype: ETHTOOL_A_HEADER_DEV_NAME, str: 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, start: nest); |
182 | return 0; |
183 | |
184 | nla_put_failure: |
185 | nla_nest_cancel(skb, start: nest); |
186 | return -EMSGSIZE; |
187 | } |
188 | |
189 | /** |
190 | * ethnl_reply_init() - Create skb for a reply and fill device identification |
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() |
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, family: ðtool_genl_family, flags: 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, attrtype: 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 | |
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, seq: cb->nlh->nlmsg_seq, |
233 | family: ðtool_genl_family, flags: 0, cmd); |
234 | } |
235 | |
236 | void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd) |
237 | { |
238 | return genlmsg_put(skb, portid: 0, seq: ++ethnl_bcast_seq, family: ðtool_genl_family, flags: 0, |
239 | cmd); |
240 | } |
241 | |
242 | int ethnl_multicast(struct sk_buff *skb, struct net_device *dev) |
243 | { |
244 | return genlmsg_multicast_netns(family: ðtool_genl_family, net: dev_net(dev), skb, |
245 | portid: 0, group: ETHNL_MCGRP_MONITOR, GFP_KERNEL); |
246 | } |
247 | |
248 | /* GET request helpers */ |
249 | |
250 | /** |
251 | * struct ethnl_dump_ctx - context structure for generic dumpit() callback |
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 |
255 | * @pos_ifindex: saved iteration position - ifindex |
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; |
265 | unsigned long pos_ifindex; |
266 | }; |
267 | |
268 | static const struct ethnl_request_ops * |
269 | ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { |
270 | [ETHTOOL_MSG_STRSET_GET] = ðnl_strset_request_ops, |
271 | [ETHTOOL_MSG_LINKINFO_GET] = ðnl_linkinfo_request_ops, |
272 | [ETHTOOL_MSG_LINKINFO_SET] = ðnl_linkinfo_request_ops, |
273 | [ETHTOOL_MSG_LINKMODES_GET] = ðnl_linkmodes_request_ops, |
274 | [ETHTOOL_MSG_LINKMODES_SET] = ðnl_linkmodes_request_ops, |
275 | [ETHTOOL_MSG_LINKSTATE_GET] = ðnl_linkstate_request_ops, |
276 | [ETHTOOL_MSG_DEBUG_GET] = ðnl_debug_request_ops, |
277 | [ETHTOOL_MSG_DEBUG_SET] = ðnl_debug_request_ops, |
278 | [ETHTOOL_MSG_WOL_GET] = ðnl_wol_request_ops, |
279 | [ETHTOOL_MSG_WOL_SET] = ðnl_wol_request_ops, |
280 | [ETHTOOL_MSG_FEATURES_GET] = ðnl_features_request_ops, |
281 | [ETHTOOL_MSG_PRIVFLAGS_GET] = ðnl_privflags_request_ops, |
282 | [ETHTOOL_MSG_PRIVFLAGS_SET] = ðnl_privflags_request_ops, |
283 | [ETHTOOL_MSG_RINGS_GET] = ðnl_rings_request_ops, |
284 | [ETHTOOL_MSG_RINGS_SET] = ðnl_rings_request_ops, |
285 | [ETHTOOL_MSG_CHANNELS_GET] = ðnl_channels_request_ops, |
286 | [ETHTOOL_MSG_CHANNELS_SET] = ðnl_channels_request_ops, |
287 | [ETHTOOL_MSG_COALESCE_GET] = ðnl_coalesce_request_ops, |
288 | [ETHTOOL_MSG_COALESCE_SET] = ðnl_coalesce_request_ops, |
289 | [ETHTOOL_MSG_PAUSE_GET] = ðnl_pause_request_ops, |
290 | [ETHTOOL_MSG_PAUSE_SET] = ðnl_pause_request_ops, |
291 | [ETHTOOL_MSG_EEE_GET] = ðnl_eee_request_ops, |
292 | [ETHTOOL_MSG_EEE_SET] = ðnl_eee_request_ops, |
293 | [ETHTOOL_MSG_FEC_GET] = ðnl_fec_request_ops, |
294 | [ETHTOOL_MSG_FEC_SET] = ðnl_fec_request_ops, |
295 | [ETHTOOL_MSG_TSINFO_GET] = ðnl_tsinfo_request_ops, |
296 | [ETHTOOL_MSG_MODULE_EEPROM_GET] = ðnl_module_eeprom_request_ops, |
297 | [ETHTOOL_MSG_STATS_GET] = ðnl_stats_request_ops, |
298 | [ETHTOOL_MSG_PHC_VCLOCKS_GET] = ðnl_phc_vclocks_request_ops, |
299 | [ETHTOOL_MSG_MODULE_GET] = ðnl_module_request_ops, |
300 | [ETHTOOL_MSG_MODULE_SET] = ðnl_module_request_ops, |
301 | [ETHTOOL_MSG_PSE_GET] = ðnl_pse_request_ops, |
302 | [ETHTOOL_MSG_PSE_SET] = ðnl_pse_request_ops, |
303 | [ETHTOOL_MSG_RSS_GET] = ðnl_rss_request_ops, |
304 | [ETHTOOL_MSG_PLCA_GET_CFG] = ðnl_plca_cfg_request_ops, |
305 | [ETHTOOL_MSG_PLCA_SET_CFG] = ðnl_plca_cfg_request_ops, |
306 | [ETHTOOL_MSG_PLCA_GET_STATUS] = ðnl_plca_status_request_ops, |
307 | [ETHTOOL_MSG_MM_GET] = ðnl_mm_request_ops, |
308 | [ETHTOOL_MSG_MM_SET] = ðnl_mm_request_ops, |
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 |
319 | * @info: genl_info from the request |
320 | * @request_ops: struct request_ops for request type |
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, |
329 | const struct genl_info *info, |
330 | const struct ethnl_request_ops *request_ops, |
331 | bool require_dev) |
332 | { |
333 | struct nlattr **tb = info->attrs; |
334 | int ret; |
335 | |
336 | ret = ethnl_parse_header_dev_get(req_info, header: tb[request_ops->hdr_attr], |
337 | net: genl_info_net(info), extack: info->extack, |
338 | require_dev); |
339 | if (ret < 0) |
340 | return ret; |
341 | |
342 | if (request_ops->parse_request) { |
343 | ret = request_ops->parse_request(req_info, tb, info->extack); |
344 | if (ret < 0) |
345 | return ret; |
346 | } |
347 | |
348 | return 0; |
349 | } |
350 | |
351 | /** |
352 | * ethnl_init_reply_data() - Initialize reply data for GET request |
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 |
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; |
376 | int hdr_len, reply_len; |
377 | struct sk_buff *rskb; |
378 | void *reply_payload; |
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; |
384 | if (GENL_REQ_ATTR_CHECK(info, ops->hdr_attr)) |
385 | return -EINVAL; |
386 | |
387 | req_info = kzalloc(size: ops->req_info_size, GFP_KERNEL); |
388 | if (!req_info) |
389 | return -ENOMEM; |
390 | reply_data = kmalloc(size: ops->reply_data_size, GFP_KERNEL); |
391 | if (!reply_data) { |
392 | kfree(objp: req_info); |
393 | return -ENOMEM; |
394 | } |
395 | |
396 | ret = ethnl_default_parse(req_info, info, request_ops: ops, require_dev: !ops->allow_nodev_do); |
397 | if (ret < 0) |
398 | goto err_dev; |
399 | ethnl_init_reply_data(reply_data, ops, dev: 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; |
406 | ret = ops->reply_size(req_info, reply_data); |
407 | if (ret < 0) |
408 | goto err_cleanup; |
409 | reply_len = ret; |
410 | ret = -ENOMEM; |
411 | rskb = ethnl_reply_init(payload: reply_len + ethnl_reply_header_size(), |
412 | dev: req_info->dev, cmd: ops->reply_cmd, |
413 | hdr_attrtype: ops->hdr_attr, info, ehdrp: &reply_payload); |
414 | if (!rskb) |
415 | goto err_cleanup; |
416 | hdr_len = rskb->len; |
417 | ret = ops->fill_reply(rskb, req_info, reply_data); |
418 | if (ret < 0) |
419 | goto err_msg; |
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); |
423 | if (ops->cleanup_data) |
424 | ops->cleanup_data(reply_data); |
425 | |
426 | genlmsg_end(skb: rskb, hdr: reply_payload); |
427 | netdev_put(dev: req_info->dev, tracker: &req_info->dev_tracker); |
428 | kfree(objp: reply_data); |
429 | kfree(objp: req_info); |
430 | return genlmsg_reply(skb: rskb, info); |
431 | |
432 | err_msg: |
433 | WARN_ONCE(ret == -EMSGSIZE, "calculated message payload length (%d) not sufficient\n" , reply_len); |
434 | nlmsg_free(skb: rskb); |
435 | err_cleanup: |
436 | if (ops->cleanup_data) |
437 | ops->cleanup_data(reply_data); |
438 | err_dev: |
439 | netdev_put(dev: req_info->dev, tracker: &req_info->dev_tracker); |
440 | kfree(objp: reply_data); |
441 | kfree(objp: req_info); |
442 | return ret; |
443 | } |
444 | |
445 | static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev, |
446 | const struct ethnl_dump_ctx *ctx, |
447 | const struct genl_info *info) |
448 | { |
449 | void *ehdr; |
450 | int ret; |
451 | |
452 | ehdr = genlmsg_put(skb, portid: info->snd_portid, seq: info->snd_seq, |
453 | family: ðtool_genl_family, NLM_F_MULTI, |
454 | cmd: ctx->ops->reply_cmd); |
455 | if (!ehdr) |
456 | return -EMSGSIZE; |
457 | |
458 | ethnl_init_reply_data(reply_data: ctx->reply_data, ops: ctx->ops, dev); |
459 | rtnl_lock(); |
460 | ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info); |
461 | rtnl_unlock(); |
462 | if (ret < 0) |
463 | goto out; |
464 | ret = ethnl_fill_reply_header(skb, dev, attrtype: 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; |
473 | if (ret < 0) |
474 | genlmsg_cancel(skb, hdr: ehdr); |
475 | else |
476 | genlmsg_end(skb, hdr: ehdr); |
477 | return ret; |
478 | } |
479 | |
480 | /* Default ->dumpit() handler for GET requests. */ |
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(sk: skb->sk); |
486 | struct net_device *dev; |
487 | int ret = 0; |
488 | |
489 | rcu_read_lock(); |
490 | for_each_netdev_dump(net, dev, ctx->pos_ifindex) { |
491 | dev_hold(dev); |
492 | rcu_read_unlock(); |
493 | |
494 | ret = ethnl_default_dump_one(skb, dev, ctx, info: genl_info_dump(cb)); |
495 | |
496 | rcu_read_lock(); |
497 | dev_put(dev); |
498 | |
499 | if (ret < 0 && ret != -EOPNOTSUPP) { |
500 | if (likely(skb->len)) |
501 | ret = skb->len; |
502 | break; |
503 | } |
504 | ret = 0; |
505 | } |
506 | rcu_read_unlock(); |
507 | |
508 | return ret; |
509 | } |
510 | |
511 | /* generic ->start() handler for GET requests */ |
512 | static int ethnl_default_start(struct netlink_callback *cb) |
513 | { |
514 | const struct genl_dumpit_info *info = genl_dumpit_info(cb); |
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(nlh: 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(size: ops->req_info_size, GFP_KERNEL); |
529 | if (!req_info) |
530 | return -ENOMEM; |
531 | reply_data = kmalloc(size: ops->reply_data_size, GFP_KERNEL); |
532 | if (!reply_data) { |
533 | ret = -ENOMEM; |
534 | goto free_req_info; |
535 | } |
536 | |
537 | ret = ethnl_default_parse(req_info, info: &info->info, request_ops: ops, require_dev: false); |
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 | */ |
543 | netdev_put(dev: req_info->dev, tracker: &req_info->dev_tracker); |
544 | req_info->dev = NULL; |
545 | } |
546 | if (ret < 0) |
547 | goto free_reply_data; |
548 | |
549 | ctx->ops = ops; |
550 | ctx->req_info = req_info; |
551 | ctx->reply_data = reply_data; |
552 | ctx->pos_ifindex = 0; |
553 | |
554 | return 0; |
555 | |
556 | free_reply_data: |
557 | kfree(objp: reply_data); |
558 | free_req_info: |
559 | kfree(objp: req_info); |
560 | |
561 | return ret; |
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(objp: ctx->reply_data); |
570 | kfree(objp: ctx->req_info); |
571 | |
572 | return 0; |
573 | } |
574 | |
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: &req_info, header: info->attrs[ops->hdr_attr], |
589 | net: genl_info_net(info), extack: info->extack, |
590 | require_dev: 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(dev: 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(dev: req_info.dev, cmd: ops->set_ntf_cmd, NULL); |
610 | |
611 | ret = 0; |
612 | out_ops: |
613 | ethnl_ops_complete(dev: req_info.dev); |
614 | out_rtnl: |
615 | rtnl_unlock(); |
616 | out_dev: |
617 | ethnl_parse_header_dev_put(req_info: &req_info); |
618 | return ret; |
619 | } |
620 | |
621 | static const struct ethnl_request_ops * |
622 | ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = { |
623 | [ETHTOOL_MSG_LINKINFO_NTF] = ðnl_linkinfo_request_ops, |
624 | [ETHTOOL_MSG_LINKMODES_NTF] = ðnl_linkmodes_request_ops, |
625 | [ETHTOOL_MSG_DEBUG_NTF] = ðnl_debug_request_ops, |
626 | [ETHTOOL_MSG_WOL_NTF] = ðnl_wol_request_ops, |
627 | [ETHTOOL_MSG_FEATURES_NTF] = ðnl_features_request_ops, |
628 | [ETHTOOL_MSG_PRIVFLAGS_NTF] = ðnl_privflags_request_ops, |
629 | [ETHTOOL_MSG_RINGS_NTF] = ðnl_rings_request_ops, |
630 | [ETHTOOL_MSG_CHANNELS_NTF] = ðnl_channels_request_ops, |
631 | [ETHTOOL_MSG_COALESCE_NTF] = ðnl_coalesce_request_ops, |
632 | [ETHTOOL_MSG_PAUSE_NTF] = ðnl_pause_request_ops, |
633 | [ETHTOOL_MSG_EEE_NTF] = ðnl_eee_request_ops, |
634 | [ETHTOOL_MSG_FEC_NTF] = ðnl_fec_request_ops, |
635 | [ETHTOOL_MSG_MODULE_NTF] = ðnl_module_request_ops, |
636 | [ETHTOOL_MSG_PLCA_NTF] = ðnl_plca_cfg_request_ops, |
637 | [ETHTOOL_MSG_MM_NTF] = ðnl_mm_request_ops, |
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; |
647 | struct genl_info info; |
648 | struct sk_buff *skb; |
649 | void *reply_payload; |
650 | int reply_len; |
651 | int ret; |
652 | |
653 | genl_info_init_ntf(info: &info, family: ðtool_genl_family, cmd); |
654 | |
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(size: ops->req_info_size, GFP_KERNEL); |
661 | if (!req_info) |
662 | return; |
663 | reply_data = kmalloc(size: ops->reply_data_size, GFP_KERNEL); |
664 | if (!reply_data) { |
665 | kfree(objp: 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); |
673 | ret = ops->prepare_data(req_info, reply_data, &info); |
674 | if (ret < 0) |
675 | goto err_cleanup; |
676 | ret = ops->reply_size(req_info, reply_data); |
677 | if (ret < 0) |
678 | goto err_cleanup; |
679 | reply_len = ret + ethnl_reply_header_size(); |
680 | skb = genlmsg_new(payload: 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, attrtype: 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, hdr: reply_payload); |
696 | kfree(objp: reply_data); |
697 | kfree(objp: 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(objp: reply_data); |
711 | kfree(objp: req_info); |
712 | return; |
713 | } |
714 | |
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[] = { |
721 | [ETHTOOL_MSG_LINKINFO_NTF] = ethnl_default_notify, |
722 | [ETHTOOL_MSG_LINKMODES_NTF] = ethnl_default_notify, |
723 | [ETHTOOL_MSG_DEBUG_NTF] = ethnl_default_notify, |
724 | [ETHTOOL_MSG_WOL_NTF] = ethnl_default_notify, |
725 | [ETHTOOL_MSG_FEATURES_NTF] = ethnl_default_notify, |
726 | [ETHTOOL_MSG_PRIVFLAGS_NTF] = ethnl_default_notify, |
727 | [ETHTOOL_MSG_RINGS_NTF] = ethnl_default_notify, |
728 | [ETHTOOL_MSG_CHANNELS_NTF] = ethnl_default_notify, |
729 | [ETHTOOL_MSG_COALESCE_NTF] = ethnl_default_notify, |
730 | [ETHTOOL_MSG_PAUSE_NTF] = ethnl_default_notify, |
731 | [ETHTOOL_MSG_EEE_NTF] = ethnl_default_notify, |
732 | [ETHTOOL_MSG_FEC_NTF] = ethnl_default_notify, |
733 | [ETHTOOL_MSG_MODULE_NTF] = ethnl_default_notify, |
734 | [ETHTOOL_MSG_PLCA_NTF] = ethnl_default_notify, |
735 | [ETHTOOL_MSG_MM_NTF] = ethnl_default_notify, |
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 | |
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(info: 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 | |
776 | /* genetlink setup */ |
777 | |
778 | static const struct genl_ops ethtool_genl_ops[] = { |
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, |
785 | .policy = ethnl_strset_get_policy, |
786 | .maxattr = ARRAY_SIZE(ethnl_strset_get_policy) - 1, |
787 | }, |
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, |
794 | .policy = ethnl_linkinfo_get_policy, |
795 | .maxattr = ARRAY_SIZE(ethnl_linkinfo_get_policy) - 1, |
796 | }, |
797 | { |
798 | .cmd = ETHTOOL_MSG_LINKINFO_SET, |
799 | .flags = GENL_UNS_ADMIN_PERM, |
800 | .doit = ethnl_default_set_doit, |
801 | .policy = ethnl_linkinfo_set_policy, |
802 | .maxattr = ARRAY_SIZE(ethnl_linkinfo_set_policy) - 1, |
803 | }, |
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, |
810 | .policy = ethnl_linkmodes_get_policy, |
811 | .maxattr = ARRAY_SIZE(ethnl_linkmodes_get_policy) - 1, |
812 | }, |
813 | { |
814 | .cmd = ETHTOOL_MSG_LINKMODES_SET, |
815 | .flags = GENL_UNS_ADMIN_PERM, |
816 | .doit = ethnl_default_set_doit, |
817 | .policy = ethnl_linkmodes_set_policy, |
818 | .maxattr = ARRAY_SIZE(ethnl_linkmodes_set_policy) - 1, |
819 | }, |
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, |
826 | .policy = ethnl_linkstate_get_policy, |
827 | .maxattr = ARRAY_SIZE(ethnl_linkstate_get_policy) - 1, |
828 | }, |
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, |
835 | .policy = ethnl_debug_get_policy, |
836 | .maxattr = ARRAY_SIZE(ethnl_debug_get_policy) - 1, |
837 | }, |
838 | { |
839 | .cmd = ETHTOOL_MSG_DEBUG_SET, |
840 | .flags = GENL_UNS_ADMIN_PERM, |
841 | .doit = ethnl_default_set_doit, |
842 | .policy = ethnl_debug_set_policy, |
843 | .maxattr = ARRAY_SIZE(ethnl_debug_set_policy) - 1, |
844 | }, |
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, |
852 | .policy = ethnl_wol_get_policy, |
853 | .maxattr = ARRAY_SIZE(ethnl_wol_get_policy) - 1, |
854 | }, |
855 | { |
856 | .cmd = ETHTOOL_MSG_WOL_SET, |
857 | .flags = GENL_UNS_ADMIN_PERM, |
858 | .doit = ethnl_default_set_doit, |
859 | .policy = ethnl_wol_set_policy, |
860 | .maxattr = ARRAY_SIZE(ethnl_wol_set_policy) - 1, |
861 | }, |
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, |
868 | .policy = ethnl_features_get_policy, |
869 | .maxattr = ARRAY_SIZE(ethnl_features_get_policy) - 1, |
870 | }, |
871 | { |
872 | .cmd = ETHTOOL_MSG_FEATURES_SET, |
873 | .flags = GENL_UNS_ADMIN_PERM, |
874 | .doit = ethnl_set_features, |
875 | .policy = ethnl_features_set_policy, |
876 | .maxattr = ARRAY_SIZE(ethnl_features_set_policy) - 1, |
877 | }, |
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, |
884 | .policy = ethnl_privflags_get_policy, |
885 | .maxattr = ARRAY_SIZE(ethnl_privflags_get_policy) - 1, |
886 | }, |
887 | { |
888 | .cmd = ETHTOOL_MSG_PRIVFLAGS_SET, |
889 | .flags = GENL_UNS_ADMIN_PERM, |
890 | .doit = ethnl_default_set_doit, |
891 | .policy = ethnl_privflags_set_policy, |
892 | .maxattr = ARRAY_SIZE(ethnl_privflags_set_policy) - 1, |
893 | }, |
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, |
900 | .policy = ethnl_rings_get_policy, |
901 | .maxattr = ARRAY_SIZE(ethnl_rings_get_policy) - 1, |
902 | }, |
903 | { |
904 | .cmd = ETHTOOL_MSG_RINGS_SET, |
905 | .flags = GENL_UNS_ADMIN_PERM, |
906 | .doit = ethnl_default_set_doit, |
907 | .policy = ethnl_rings_set_policy, |
908 | .maxattr = ARRAY_SIZE(ethnl_rings_set_policy) - 1, |
909 | }, |
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, |
916 | .policy = ethnl_channels_get_policy, |
917 | .maxattr = ARRAY_SIZE(ethnl_channels_get_policy) - 1, |
918 | }, |
919 | { |
920 | .cmd = ETHTOOL_MSG_CHANNELS_SET, |
921 | .flags = GENL_UNS_ADMIN_PERM, |
922 | .doit = ethnl_default_set_doit, |
923 | .policy = ethnl_channels_set_policy, |
924 | .maxattr = ARRAY_SIZE(ethnl_channels_set_policy) - 1, |
925 | }, |
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, |
932 | .policy = ethnl_coalesce_get_policy, |
933 | .maxattr = ARRAY_SIZE(ethnl_coalesce_get_policy) - 1, |
934 | }, |
935 | { |
936 | .cmd = ETHTOOL_MSG_COALESCE_SET, |
937 | .flags = GENL_UNS_ADMIN_PERM, |
938 | .doit = ethnl_default_set_doit, |
939 | .policy = ethnl_coalesce_set_policy, |
940 | .maxattr = ARRAY_SIZE(ethnl_coalesce_set_policy) - 1, |
941 | }, |
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, |
948 | .policy = ethnl_pause_get_policy, |
949 | .maxattr = ARRAY_SIZE(ethnl_pause_get_policy) - 1, |
950 | }, |
951 | { |
952 | .cmd = ETHTOOL_MSG_PAUSE_SET, |
953 | .flags = GENL_UNS_ADMIN_PERM, |
954 | .doit = ethnl_default_set_doit, |
955 | .policy = ethnl_pause_set_policy, |
956 | .maxattr = ARRAY_SIZE(ethnl_pause_set_policy) - 1, |
957 | }, |
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, |
964 | .policy = ethnl_eee_get_policy, |
965 | .maxattr = ARRAY_SIZE(ethnl_eee_get_policy) - 1, |
966 | }, |
967 | { |
968 | .cmd = ETHTOOL_MSG_EEE_SET, |
969 | .flags = GENL_UNS_ADMIN_PERM, |
970 | .doit = ethnl_default_set_doit, |
971 | .policy = ethnl_eee_set_policy, |
972 | .maxattr = ARRAY_SIZE(ethnl_eee_set_policy) - 1, |
973 | }, |
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, |
980 | .policy = ethnl_tsinfo_get_policy, |
981 | .maxattr = ARRAY_SIZE(ethnl_tsinfo_get_policy) - 1, |
982 | }, |
983 | { |
984 | .cmd = ETHTOOL_MSG_CABLE_TEST_ACT, |
985 | .flags = GENL_UNS_ADMIN_PERM, |
986 | .doit = ethnl_act_cable_test, |
987 | .policy = ethnl_cable_test_act_policy, |
988 | .maxattr = ARRAY_SIZE(ethnl_cable_test_act_policy) - 1, |
989 | }, |
990 | { |
991 | .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_ACT, |
992 | .flags = GENL_UNS_ADMIN_PERM, |
993 | .doit = ethnl_act_cable_test_tdr, |
994 | .policy = ethnl_cable_test_tdr_act_policy, |
995 | .maxattr = ARRAY_SIZE(ethnl_cable_test_tdr_act_policy) - 1, |
996 | }, |
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, |
1002 | .policy = ethnl_tunnel_info_get_policy, |
1003 | .maxattr = ARRAY_SIZE(ethnl_tunnel_info_get_policy) - 1, |
1004 | }, |
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, |
1017 | .doit = ethnl_default_set_doit, |
1018 | .policy = ethnl_fec_set_policy, |
1019 | .maxattr = ARRAY_SIZE(ethnl_fec_set_policy) - 1, |
1020 | }, |
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 | }, |
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 | }, |
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 | }, |
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, |
1061 | .doit = ethnl_default_set_doit, |
1062 | .policy = ethnl_module_set_policy, |
1063 | .maxattr = ARRAY_SIZE(ethnl_module_set_policy) - 1, |
1064 | }, |
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, |
1077 | .doit = ethnl_default_set_doit, |
1078 | .policy = ethnl_pse_set_policy, |
1079 | .maxattr = ARRAY_SIZE(ethnl_pse_set_policy) - 1, |
1080 | }, |
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 | }, |
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, |
1099 | .doit = ethnl_default_set_doit, |
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 | }, |
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, |
1124 | .doit = ethnl_default_set_doit, |
1125 | .policy = ethnl_mm_set_policy, |
1126 | .maxattr = ARRAY_SIZE(ethnl_mm_set_policy) - 1, |
1127 | }, |
1128 | }; |
1129 | |
1130 | static const struct genl_multicast_group ethtool_nl_mcgrps[] = { |
1131 | [ETHNL_MCGRP_MONITOR] = { .name = ETHTOOL_MCGRP_MONITOR_NAME }, |
1132 | }; |
1133 | |
1134 | static struct genl_family ethtool_genl_family __ro_after_init = { |
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), |
1141 | .resv_start_op = ETHTOOL_MSG_MODULE_GET + 1, |
1142 | .mcgrps = ethtool_nl_mcgrps, |
1143 | .n_mcgrps = ARRAY_SIZE(ethtool_nl_mcgrps), |
1144 | }; |
1145 | |
1146 | /* module setup */ |
1147 | |
1148 | static int __init ethnl_init(void) |
1149 | { |
1150 | int ret; |
1151 | |
1152 | ret = genl_register_family(family: ðtool_genl_family); |
1153 | if (WARN(ret < 0, "ethtool: genetlink family registration failed" )) |
1154 | return ret; |
1155 | ethnl_ok = true; |
1156 | |
1157 | ret = register_netdevice_notifier(nb: ðnl_netdev_notifier); |
1158 | WARN(ret < 0, "ethtool: net device notifier registration failed" ); |
1159 | return ret; |
1160 | } |
1161 | |
1162 | subsys_initcall(ethnl_init); |
1163 | |