Commit | Line | Data |
---|---|---|
955dc68c SMJ |
1 | /* |
2 | * Copyright Samuel Mendoza-Jonas, IBM Corporation 2018. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | */ | |
9 | ||
10 | #include <linux/module.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/if_arp.h> | |
13 | #include <linux/rtnetlink.h> | |
14 | #include <linux/etherdevice.h> | |
955dc68c SMJ |
15 | #include <net/genetlink.h> |
16 | #include <net/ncsi.h> | |
17 | #include <linux/skbuff.h> | |
18 | #include <net/sock.h> | |
19 | #include <uapi/linux/ncsi.h> | |
20 | ||
21 | #include "internal.h" | |
9771b8cc | 22 | #include "ncsi-pkt.h" |
955dc68c SMJ |
23 | #include "ncsi-netlink.h" |
24 | ||
25 | static struct genl_family ncsi_genl_family; | |
26 | ||
27 | static const struct nla_policy ncsi_genl_policy[NCSI_ATTR_MAX + 1] = { | |
28 | [NCSI_ATTR_IFINDEX] = { .type = NLA_U32 }, | |
29 | [NCSI_ATTR_PACKAGE_LIST] = { .type = NLA_NESTED }, | |
30 | [NCSI_ATTR_PACKAGE_ID] = { .type = NLA_U32 }, | |
31 | [NCSI_ATTR_CHANNEL_ID] = { .type = NLA_U32 }, | |
9771b8cc | 32 | [NCSI_ATTR_DATA] = { .type = NLA_BINARY, .len = 2048 }, |
8d951a75 SMJ |
33 | [NCSI_ATTR_MULTI_FLAG] = { .type = NLA_FLAG }, |
34 | [NCSI_ATTR_PACKAGE_MASK] = { .type = NLA_U32 }, | |
35 | [NCSI_ATTR_CHANNEL_MASK] = { .type = NLA_U32 }, | |
955dc68c SMJ |
36 | }; |
37 | ||
38 | static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex) | |
39 | { | |
40 | struct ncsi_dev_priv *ndp; | |
41 | struct net_device *dev; | |
42 | struct ncsi_dev *nd; | |
43 | struct ncsi_dev; | |
44 | ||
45 | if (!net) | |
46 | return NULL; | |
47 | ||
48 | dev = dev_get_by_index(net, ifindex); | |
49 | if (!dev) { | |
50 | pr_err("NCSI netlink: No device for ifindex %u\n", ifindex); | |
51 | return NULL; | |
52 | } | |
53 | ||
54 | nd = ncsi_find_dev(dev); | |
55 | ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; | |
56 | ||
57 | dev_put(dev); | |
58 | return ndp; | |
59 | } | |
60 | ||
61 | static int ncsi_write_channel_info(struct sk_buff *skb, | |
62 | struct ncsi_dev_priv *ndp, | |
63 | struct ncsi_channel *nc) | |
64 | { | |
062b3e1b | 65 | struct ncsi_channel_vlan_filter *ncf; |
955dc68c | 66 | struct ncsi_channel_mode *m; |
062b3e1b | 67 | struct nlattr *vid_nest; |
955dc68c SMJ |
68 | int i; |
69 | ||
70 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_ID, nc->id); | |
71 | m = &nc->modes[NCSI_MODE_LINK]; | |
72 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_LINK_STATE, m->data[2]); | |
73 | if (nc->state == NCSI_CHANNEL_ACTIVE) | |
74 | nla_put_flag(skb, NCSI_CHANNEL_ATTR_ACTIVE); | |
8d951a75 | 75 | if (nc == nc->package->preferred_channel) |
955dc68c SMJ |
76 | nla_put_flag(skb, NCSI_CHANNEL_ATTR_FORCED); |
77 | ||
78 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.version); | |
79 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MINOR, nc->version.alpha2); | |
80 | nla_put_string(skb, NCSI_CHANNEL_ATTR_VERSION_STR, nc->version.fw_name); | |
81 | ||
ae0be8de | 82 | vid_nest = nla_nest_start_noflag(skb, NCSI_CHANNEL_ATTR_VLAN_LIST); |
955dc68c SMJ |
83 | if (!vid_nest) |
84 | return -ENOMEM; | |
062b3e1b | 85 | ncf = &nc->vlan_filter; |
955dc68c | 86 | i = -1; |
062b3e1b SMJ |
87 | while ((i = find_next_bit((void *)&ncf->bitmap, ncf->n_vids, |
88 | i + 1)) < ncf->n_vids) { | |
89 | if (ncf->vids[i]) | |
955dc68c | 90 | nla_put_u16(skb, NCSI_CHANNEL_ATTR_VLAN_ID, |
062b3e1b | 91 | ncf->vids[i]); |
955dc68c SMJ |
92 | } |
93 | nla_nest_end(skb, vid_nest); | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | static int ncsi_write_package_info(struct sk_buff *skb, | |
99 | struct ncsi_dev_priv *ndp, unsigned int id) | |
100 | { | |
101 | struct nlattr *pnest, *cnest, *nest; | |
102 | struct ncsi_package *np; | |
103 | struct ncsi_channel *nc; | |
104 | bool found; | |
105 | int rc; | |
106 | ||
3d0371b3 | 107 | if (id > ndp->package_num - 1) { |
955dc68c SMJ |
108 | netdev_info(ndp->ndev.dev, "NCSI: No package with id %u\n", id); |
109 | return -ENODEV; | |
110 | } | |
111 | ||
112 | found = false; | |
113 | NCSI_FOR_EACH_PACKAGE(ndp, np) { | |
114 | if (np->id != id) | |
115 | continue; | |
ae0be8de | 116 | pnest = nla_nest_start_noflag(skb, NCSI_PKG_ATTR); |
955dc68c SMJ |
117 | if (!pnest) |
118 | return -ENOMEM; | |
119 | nla_put_u32(skb, NCSI_PKG_ATTR_ID, np->id); | |
8d951a75 | 120 | if ((0x1 << np->id) == ndp->package_whitelist) |
955dc68c | 121 | nla_put_flag(skb, NCSI_PKG_ATTR_FORCED); |
ae0be8de | 122 | cnest = nla_nest_start_noflag(skb, NCSI_PKG_ATTR_CHANNEL_LIST); |
955dc68c SMJ |
123 | if (!cnest) { |
124 | nla_nest_cancel(skb, pnest); | |
125 | return -ENOMEM; | |
126 | } | |
127 | NCSI_FOR_EACH_CHANNEL(np, nc) { | |
ae0be8de | 128 | nest = nla_nest_start_noflag(skb, NCSI_CHANNEL_ATTR); |
955dc68c SMJ |
129 | if (!nest) { |
130 | nla_nest_cancel(skb, cnest); | |
131 | nla_nest_cancel(skb, pnest); | |
132 | return -ENOMEM; | |
133 | } | |
134 | rc = ncsi_write_channel_info(skb, ndp, nc); | |
135 | if (rc) { | |
136 | nla_nest_cancel(skb, nest); | |
137 | nla_nest_cancel(skb, cnest); | |
138 | nla_nest_cancel(skb, pnest); | |
139 | return rc; | |
140 | } | |
141 | nla_nest_end(skb, nest); | |
142 | } | |
143 | nla_nest_end(skb, cnest); | |
144 | nla_nest_end(skb, pnest); | |
145 | found = true; | |
146 | } | |
147 | ||
148 | if (!found) | |
149 | return -ENODEV; | |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
154 | static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info) | |
155 | { | |
156 | struct ncsi_dev_priv *ndp; | |
157 | unsigned int package_id; | |
158 | struct sk_buff *skb; | |
159 | struct nlattr *attr; | |
160 | void *hdr; | |
161 | int rc; | |
162 | ||
163 | if (!info || !info->attrs) | |
164 | return -EINVAL; | |
165 | ||
166 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
167 | return -EINVAL; | |
168 | ||
169 | if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) | |
170 | return -EINVAL; | |
171 | ||
172 | ndp = ndp_from_ifindex(genl_info_net(info), | |
173 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
174 | if (!ndp) | |
175 | return -ENODEV; | |
176 | ||
177 | skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | |
178 | if (!skb) | |
179 | return -ENOMEM; | |
180 | ||
181 | hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, | |
182 | &ncsi_genl_family, 0, NCSI_CMD_PKG_INFO); | |
183 | if (!hdr) { | |
50db64b0 | 184 | kfree_skb(skb); |
955dc68c SMJ |
185 | return -EMSGSIZE; |
186 | } | |
187 | ||
188 | package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); | |
189 | ||
ae0be8de | 190 | attr = nla_nest_start_noflag(skb, NCSI_ATTR_PACKAGE_LIST); |
8daf1a2d CIK |
191 | if (!attr) { |
192 | kfree_skb(skb); | |
193 | return -EMSGSIZE; | |
194 | } | |
955dc68c SMJ |
195 | rc = ncsi_write_package_info(skb, ndp, package_id); |
196 | ||
197 | if (rc) { | |
198 | nla_nest_cancel(skb, attr); | |
199 | goto err; | |
200 | } | |
201 | ||
202 | nla_nest_end(skb, attr); | |
203 | ||
204 | genlmsg_end(skb, hdr); | |
205 | return genlmsg_reply(skb, info); | |
206 | ||
207 | err: | |
50db64b0 | 208 | kfree_skb(skb); |
955dc68c SMJ |
209 | return rc; |
210 | } | |
211 | ||
212 | static int ncsi_pkg_info_all_nl(struct sk_buff *skb, | |
213 | struct netlink_callback *cb) | |
214 | { | |
0f51f358 | 215 | struct nlattr *attrs[NCSI_ATTR_MAX + 1]; |
955dc68c SMJ |
216 | struct ncsi_package *np, *package; |
217 | struct ncsi_dev_priv *ndp; | |
218 | unsigned int package_id; | |
219 | struct nlattr *attr; | |
220 | void *hdr; | |
221 | int rc; | |
222 | ||
223 | rc = genlmsg_parse(cb->nlh, &ncsi_genl_family, attrs, NCSI_ATTR_MAX, | |
224 | ncsi_genl_policy, NULL); | |
225 | if (rc) | |
226 | return rc; | |
227 | ||
228 | if (!attrs[NCSI_ATTR_IFINDEX]) | |
229 | return -EINVAL; | |
230 | ||
231 | ndp = ndp_from_ifindex(get_net(sock_net(skb->sk)), | |
232 | nla_get_u32(attrs[NCSI_ATTR_IFINDEX])); | |
233 | ||
234 | if (!ndp) | |
235 | return -ENODEV; | |
236 | ||
237 | package_id = cb->args[0]; | |
238 | package = NULL; | |
239 | NCSI_FOR_EACH_PACKAGE(ndp, np) | |
240 | if (np->id == package_id) | |
241 | package = np; | |
242 | ||
243 | if (!package) | |
244 | return 0; /* done */ | |
245 | ||
246 | hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, | |
3d0371b3 | 247 | &ncsi_genl_family, NLM_F_MULTI, NCSI_CMD_PKG_INFO); |
955dc68c SMJ |
248 | if (!hdr) { |
249 | rc = -EMSGSIZE; | |
250 | goto err; | |
251 | } | |
252 | ||
ae0be8de | 253 | attr = nla_nest_start_noflag(skb, NCSI_ATTR_PACKAGE_LIST); |
07660ca6 KL |
254 | if (!attr) { |
255 | rc = -EMSGSIZE; | |
256 | goto err; | |
257 | } | |
955dc68c SMJ |
258 | rc = ncsi_write_package_info(skb, ndp, package->id); |
259 | if (rc) { | |
260 | nla_nest_cancel(skb, attr); | |
261 | goto err; | |
262 | } | |
263 | ||
264 | nla_nest_end(skb, attr); | |
265 | genlmsg_end(skb, hdr); | |
266 | ||
267 | cb->args[0] = package_id + 1; | |
268 | ||
269 | return skb->len; | |
270 | err: | |
271 | genlmsg_cancel(skb, hdr); | |
272 | return rc; | |
273 | } | |
274 | ||
275 | static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) | |
276 | { | |
277 | struct ncsi_package *np, *package; | |
278 | struct ncsi_channel *nc, *channel; | |
279 | u32 package_id, channel_id; | |
280 | struct ncsi_dev_priv *ndp; | |
281 | unsigned long flags; | |
282 | ||
283 | if (!info || !info->attrs) | |
284 | return -EINVAL; | |
285 | ||
286 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
287 | return -EINVAL; | |
288 | ||
289 | if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) | |
290 | return -EINVAL; | |
291 | ||
292 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
293 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
294 | if (!ndp) | |
295 | return -ENODEV; | |
296 | ||
297 | package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); | |
298 | package = NULL; | |
299 | ||
955dc68c SMJ |
300 | NCSI_FOR_EACH_PACKAGE(ndp, np) |
301 | if (np->id == package_id) | |
302 | package = np; | |
303 | if (!package) { | |
304 | /* The user has set a package that does not exist */ | |
305 | return -ERANGE; | |
306 | } | |
307 | ||
308 | channel = NULL; | |
8d951a75 | 309 | if (info->attrs[NCSI_ATTR_CHANNEL_ID]) { |
955dc68c SMJ |
310 | channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); |
311 | NCSI_FOR_EACH_CHANNEL(package, nc) | |
8d951a75 | 312 | if (nc->id == channel_id) { |
955dc68c | 313 | channel = nc; |
8d951a75 SMJ |
314 | break; |
315 | } | |
316 | if (!channel) { | |
317 | netdev_info(ndp->ndev.dev, | |
318 | "NCSI: Channel %u does not exist!\n", | |
319 | channel_id); | |
320 | return -ERANGE; | |
321 | } | |
955dc68c SMJ |
322 | } |
323 | ||
8d951a75 SMJ |
324 | spin_lock_irqsave(&ndp->lock, flags); |
325 | ndp->package_whitelist = 0x1 << package->id; | |
326 | ndp->multi_package = false; | |
955dc68c SMJ |
327 | spin_unlock_irqrestore(&ndp->lock, flags); |
328 | ||
8d951a75 SMJ |
329 | spin_lock_irqsave(&package->lock, flags); |
330 | package->multi_channel = false; | |
331 | if (channel) { | |
332 | package->channel_whitelist = 0x1 << channel->id; | |
333 | package->preferred_channel = channel; | |
334 | } else { | |
335 | /* Allow any channel */ | |
336 | package->channel_whitelist = UINT_MAX; | |
337 | package->preferred_channel = NULL; | |
338 | } | |
339 | spin_unlock_irqrestore(&package->lock, flags); | |
340 | ||
341 | if (channel) | |
342 | netdev_info(ndp->ndev.dev, | |
343 | "Set package 0x%x, channel 0x%x as preferred\n", | |
344 | package_id, channel_id); | |
345 | else | |
346 | netdev_info(ndp->ndev.dev, "Set package 0x%x as preferred\n", | |
347 | package_id); | |
955dc68c | 348 | |
2878a2cf SMJ |
349 | /* Update channel configuration */ |
350 | if (!(ndp->flags & NCSI_DEV_RESET)) | |
351 | ncsi_reset_dev(&ndp->ndev); | |
955dc68c SMJ |
352 | |
353 | return 0; | |
354 | } | |
355 | ||
356 | static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) | |
357 | { | |
358 | struct ncsi_dev_priv *ndp; | |
8d951a75 | 359 | struct ncsi_package *np; |
955dc68c SMJ |
360 | unsigned long flags; |
361 | ||
362 | if (!info || !info->attrs) | |
363 | return -EINVAL; | |
364 | ||
365 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
366 | return -EINVAL; | |
367 | ||
368 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
369 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
370 | if (!ndp) | |
371 | return -ENODEV; | |
372 | ||
8d951a75 | 373 | /* Reset any whitelists and disable multi mode */ |
955dc68c | 374 | spin_lock_irqsave(&ndp->lock, flags); |
8d951a75 SMJ |
375 | ndp->package_whitelist = UINT_MAX; |
376 | ndp->multi_package = false; | |
955dc68c | 377 | spin_unlock_irqrestore(&ndp->lock, flags); |
8d951a75 SMJ |
378 | |
379 | NCSI_FOR_EACH_PACKAGE(ndp, np) { | |
380 | spin_lock_irqsave(&np->lock, flags); | |
381 | np->multi_channel = false; | |
382 | np->channel_whitelist = UINT_MAX; | |
383 | np->preferred_channel = NULL; | |
384 | spin_unlock_irqrestore(&np->lock, flags); | |
385 | } | |
955dc68c SMJ |
386 | netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n"); |
387 | ||
2878a2cf SMJ |
388 | /* Update channel configuration */ |
389 | if (!(ndp->flags & NCSI_DEV_RESET)) | |
390 | ncsi_reset_dev(&ndp->ndev); | |
955dc68c SMJ |
391 | |
392 | return 0; | |
393 | } | |
394 | ||
9771b8cc JLD |
395 | static int ncsi_send_cmd_nl(struct sk_buff *msg, struct genl_info *info) |
396 | { | |
397 | struct ncsi_dev_priv *ndp; | |
398 | struct ncsi_pkt_hdr *hdr; | |
399 | struct ncsi_cmd_arg nca; | |
400 | unsigned char *data; | |
401 | u32 package_id; | |
402 | u32 channel_id; | |
403 | int len, ret; | |
404 | ||
405 | if (!info || !info->attrs) { | |
406 | ret = -EINVAL; | |
407 | goto out; | |
408 | } | |
409 | ||
410 | if (!info->attrs[NCSI_ATTR_IFINDEX]) { | |
411 | ret = -EINVAL; | |
412 | goto out; | |
413 | } | |
414 | ||
415 | if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) { | |
416 | ret = -EINVAL; | |
417 | goto out; | |
418 | } | |
419 | ||
420 | if (!info->attrs[NCSI_ATTR_CHANNEL_ID]) { | |
421 | ret = -EINVAL; | |
422 | goto out; | |
423 | } | |
424 | ||
425 | if (!info->attrs[NCSI_ATTR_DATA]) { | |
426 | ret = -EINVAL; | |
427 | goto out; | |
428 | } | |
429 | ||
430 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
431 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
432 | if (!ndp) { | |
433 | ret = -ENODEV; | |
434 | goto out; | |
435 | } | |
436 | ||
437 | package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); | |
438 | channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); | |
439 | ||
440 | if (package_id >= NCSI_MAX_PACKAGE || channel_id >= NCSI_MAX_CHANNEL) { | |
441 | ret = -ERANGE; | |
442 | goto out_netlink; | |
443 | } | |
444 | ||
445 | len = nla_len(info->attrs[NCSI_ATTR_DATA]); | |
446 | if (len < sizeof(struct ncsi_pkt_hdr)) { | |
447 | netdev_info(ndp->ndev.dev, "NCSI: no command to send %u\n", | |
448 | package_id); | |
449 | ret = -EINVAL; | |
450 | goto out_netlink; | |
451 | } else { | |
452 | data = (unsigned char *)nla_data(info->attrs[NCSI_ATTR_DATA]); | |
453 | } | |
454 | ||
455 | hdr = (struct ncsi_pkt_hdr *)data; | |
456 | ||
457 | nca.ndp = ndp; | |
458 | nca.package = (unsigned char)package_id; | |
459 | nca.channel = (unsigned char)channel_id; | |
460 | nca.type = hdr->type; | |
461 | nca.req_flags = NCSI_REQ_FLAG_NETLINK_DRIVEN; | |
462 | nca.info = info; | |
463 | nca.payload = ntohs(hdr->length); | |
464 | nca.data = data + sizeof(*hdr); | |
465 | ||
466 | ret = ncsi_xmit_cmd(&nca); | |
467 | out_netlink: | |
468 | if (ret != 0) { | |
469 | netdev_err(ndp->ndev.dev, | |
470 | "NCSI: Error %d sending command\n", | |
471 | ret); | |
472 | ncsi_send_netlink_err(ndp->ndev.dev, | |
473 | info->snd_seq, | |
474 | info->snd_portid, | |
475 | info->nlhdr, | |
476 | ret); | |
477 | } | |
478 | out: | |
479 | return ret; | |
480 | } | |
481 | ||
482 | int ncsi_send_netlink_rsp(struct ncsi_request *nr, | |
483 | struct ncsi_package *np, | |
484 | struct ncsi_channel *nc) | |
485 | { | |
486 | struct sk_buff *skb; | |
487 | struct net *net; | |
488 | void *hdr; | |
489 | int rc; | |
490 | ||
491 | net = dev_net(nr->rsp->dev); | |
492 | ||
493 | skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); | |
494 | if (!skb) | |
495 | return -ENOMEM; | |
496 | ||
497 | hdr = genlmsg_put(skb, nr->snd_portid, nr->snd_seq, | |
498 | &ncsi_genl_family, 0, NCSI_CMD_SEND_CMD); | |
499 | if (!hdr) { | |
500 | kfree_skb(skb); | |
501 | return -EMSGSIZE; | |
502 | } | |
503 | ||
504 | nla_put_u32(skb, NCSI_ATTR_IFINDEX, nr->rsp->dev->ifindex); | |
505 | if (np) | |
506 | nla_put_u32(skb, NCSI_ATTR_PACKAGE_ID, np->id); | |
507 | if (nc) | |
508 | nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, nc->id); | |
509 | else | |
510 | nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, NCSI_RESERVED_CHANNEL); | |
511 | ||
512 | rc = nla_put(skb, NCSI_ATTR_DATA, nr->rsp->len, (void *)nr->rsp->data); | |
513 | if (rc) | |
514 | goto err; | |
515 | ||
516 | genlmsg_end(skb, hdr); | |
517 | return genlmsg_unicast(net, skb, nr->snd_portid); | |
518 | ||
519 | err: | |
520 | kfree_skb(skb); | |
521 | return rc; | |
522 | } | |
523 | ||
524 | int ncsi_send_netlink_timeout(struct ncsi_request *nr, | |
525 | struct ncsi_package *np, | |
526 | struct ncsi_channel *nc) | |
527 | { | |
528 | struct sk_buff *skb; | |
529 | struct net *net; | |
530 | void *hdr; | |
531 | ||
532 | skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); | |
533 | if (!skb) | |
534 | return -ENOMEM; | |
535 | ||
536 | hdr = genlmsg_put(skb, nr->snd_portid, nr->snd_seq, | |
537 | &ncsi_genl_family, 0, NCSI_CMD_SEND_CMD); | |
538 | if (!hdr) { | |
539 | kfree_skb(skb); | |
540 | return -EMSGSIZE; | |
541 | } | |
542 | ||
543 | net = dev_net(nr->cmd->dev); | |
544 | ||
545 | nla_put_u32(skb, NCSI_ATTR_IFINDEX, nr->cmd->dev->ifindex); | |
546 | ||
547 | if (np) | |
548 | nla_put_u32(skb, NCSI_ATTR_PACKAGE_ID, np->id); | |
549 | else | |
550 | nla_put_u32(skb, NCSI_ATTR_PACKAGE_ID, | |
551 | NCSI_PACKAGE_INDEX((((struct ncsi_pkt_hdr *) | |
552 | nr->cmd->data)->channel))); | |
553 | ||
554 | if (nc) | |
555 | nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, nc->id); | |
556 | else | |
557 | nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, NCSI_RESERVED_CHANNEL); | |
558 | ||
559 | genlmsg_end(skb, hdr); | |
560 | return genlmsg_unicast(net, skb, nr->snd_portid); | |
561 | } | |
562 | ||
563 | int ncsi_send_netlink_err(struct net_device *dev, | |
564 | u32 snd_seq, | |
565 | u32 snd_portid, | |
566 | struct nlmsghdr *nlhdr, | |
567 | int err) | |
568 | { | |
569 | struct nlmsghdr *nlh; | |
570 | struct nlmsgerr *nle; | |
571 | struct sk_buff *skb; | |
572 | struct net *net; | |
573 | ||
574 | skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); | |
575 | if (!skb) | |
576 | return -ENOMEM; | |
577 | ||
578 | net = dev_net(dev); | |
579 | ||
580 | nlh = nlmsg_put(skb, snd_portid, snd_seq, | |
581 | NLMSG_ERROR, sizeof(*nle), 0); | |
582 | nle = (struct nlmsgerr *)nlmsg_data(nlh); | |
583 | nle->error = err; | |
584 | memcpy(&nle->msg, nlhdr, sizeof(*nlh)); | |
585 | ||
586 | nlmsg_end(skb, nlh); | |
587 | ||
588 | return nlmsg_unicast(net->genl_sock, skb, snd_portid); | |
589 | } | |
590 | ||
8d951a75 SMJ |
591 | static int ncsi_set_package_mask_nl(struct sk_buff *msg, |
592 | struct genl_info *info) | |
593 | { | |
594 | struct ncsi_dev_priv *ndp; | |
595 | unsigned long flags; | |
596 | int rc; | |
597 | ||
598 | if (!info || !info->attrs) | |
599 | return -EINVAL; | |
600 | ||
601 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
602 | return -EINVAL; | |
603 | ||
604 | if (!info->attrs[NCSI_ATTR_PACKAGE_MASK]) | |
605 | return -EINVAL; | |
606 | ||
607 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
608 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
609 | if (!ndp) | |
610 | return -ENODEV; | |
611 | ||
612 | spin_lock_irqsave(&ndp->lock, flags); | |
613 | if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) { | |
614 | if (ndp->flags & NCSI_DEV_HWA) { | |
615 | ndp->multi_package = true; | |
616 | rc = 0; | |
617 | } else { | |
618 | netdev_err(ndp->ndev.dev, | |
619 | "NCSI: Can't use multiple packages without HWA\n"); | |
620 | rc = -EPERM; | |
621 | } | |
622 | } else { | |
623 | ndp->multi_package = false; | |
624 | rc = 0; | |
625 | } | |
626 | ||
627 | if (!rc) | |
628 | ndp->package_whitelist = | |
629 | nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_MASK]); | |
630 | spin_unlock_irqrestore(&ndp->lock, flags); | |
631 | ||
632 | if (!rc) { | |
633 | /* Update channel configuration */ | |
634 | if (!(ndp->flags & NCSI_DEV_RESET)) | |
635 | ncsi_reset_dev(&ndp->ndev); | |
636 | } | |
637 | ||
638 | return rc; | |
639 | } | |
640 | ||
641 | static int ncsi_set_channel_mask_nl(struct sk_buff *msg, | |
642 | struct genl_info *info) | |
643 | { | |
644 | struct ncsi_package *np, *package; | |
645 | struct ncsi_channel *nc, *channel; | |
646 | u32 package_id, channel_id; | |
647 | struct ncsi_dev_priv *ndp; | |
648 | unsigned long flags; | |
649 | ||
650 | if (!info || !info->attrs) | |
651 | return -EINVAL; | |
652 | ||
653 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
654 | return -EINVAL; | |
655 | ||
656 | if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) | |
657 | return -EINVAL; | |
658 | ||
659 | if (!info->attrs[NCSI_ATTR_CHANNEL_MASK]) | |
660 | return -EINVAL; | |
661 | ||
662 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
663 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
664 | if (!ndp) | |
665 | return -ENODEV; | |
666 | ||
667 | package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); | |
668 | package = NULL; | |
669 | NCSI_FOR_EACH_PACKAGE(ndp, np) | |
670 | if (np->id == package_id) { | |
671 | package = np; | |
672 | break; | |
673 | } | |
674 | if (!package) | |
675 | return -ERANGE; | |
676 | ||
677 | spin_lock_irqsave(&package->lock, flags); | |
678 | ||
679 | channel = NULL; | |
680 | if (info->attrs[NCSI_ATTR_CHANNEL_ID]) { | |
681 | channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); | |
682 | NCSI_FOR_EACH_CHANNEL(np, nc) | |
683 | if (nc->id == channel_id) { | |
684 | channel = nc; | |
685 | break; | |
686 | } | |
687 | if (!channel) { | |
688 | spin_unlock_irqrestore(&package->lock, flags); | |
689 | return -ERANGE; | |
690 | } | |
691 | netdev_dbg(ndp->ndev.dev, | |
692 | "NCSI: Channel %u set as preferred channel\n", | |
693 | channel->id); | |
694 | } | |
695 | ||
696 | package->channel_whitelist = | |
697 | nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_MASK]); | |
698 | if (package->channel_whitelist == 0) | |
699 | netdev_dbg(ndp->ndev.dev, | |
700 | "NCSI: Package %u set to all channels disabled\n", | |
701 | package->id); | |
702 | ||
703 | package->preferred_channel = channel; | |
704 | ||
705 | if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) { | |
706 | package->multi_channel = true; | |
707 | netdev_info(ndp->ndev.dev, | |
708 | "NCSI: Multi-channel enabled on package %u\n", | |
709 | package_id); | |
710 | } else { | |
711 | package->multi_channel = false; | |
712 | } | |
713 | ||
714 | spin_unlock_irqrestore(&package->lock, flags); | |
715 | ||
716 | /* Update channel configuration */ | |
717 | if (!(ndp->flags & NCSI_DEV_RESET)) | |
718 | ncsi_reset_dev(&ndp->ndev); | |
719 | ||
720 | return 0; | |
721 | } | |
722 | ||
955dc68c SMJ |
723 | static const struct genl_ops ncsi_ops[] = { |
724 | { | |
725 | .cmd = NCSI_CMD_PKG_INFO, | |
955dc68c SMJ |
726 | .doit = ncsi_pkg_info_nl, |
727 | .dumpit = ncsi_pkg_info_all_nl, | |
728 | .flags = 0, | |
729 | }, | |
730 | { | |
731 | .cmd = NCSI_CMD_SET_INTERFACE, | |
955dc68c SMJ |
732 | .doit = ncsi_set_interface_nl, |
733 | .flags = GENL_ADMIN_PERM, | |
734 | }, | |
735 | { | |
736 | .cmd = NCSI_CMD_CLEAR_INTERFACE, | |
955dc68c SMJ |
737 | .doit = ncsi_clear_interface_nl, |
738 | .flags = GENL_ADMIN_PERM, | |
739 | }, | |
9771b8cc JLD |
740 | { |
741 | .cmd = NCSI_CMD_SEND_CMD, | |
9771b8cc JLD |
742 | .doit = ncsi_send_cmd_nl, |
743 | .flags = GENL_ADMIN_PERM, | |
744 | }, | |
8d951a75 SMJ |
745 | { |
746 | .cmd = NCSI_CMD_SET_PACKAGE_MASK, | |
8d951a75 SMJ |
747 | .doit = ncsi_set_package_mask_nl, |
748 | .flags = GENL_ADMIN_PERM, | |
749 | }, | |
750 | { | |
751 | .cmd = NCSI_CMD_SET_CHANNEL_MASK, | |
8d951a75 SMJ |
752 | .doit = ncsi_set_channel_mask_nl, |
753 | .flags = GENL_ADMIN_PERM, | |
754 | }, | |
955dc68c SMJ |
755 | }; |
756 | ||
757 | static struct genl_family ncsi_genl_family __ro_after_init = { | |
758 | .name = "NCSI", | |
759 | .version = 0, | |
760 | .maxattr = NCSI_ATTR_MAX, | |
3b0f31f2 | 761 | .policy = ncsi_genl_policy, |
955dc68c SMJ |
762 | .module = THIS_MODULE, |
763 | .ops = ncsi_ops, | |
764 | .n_ops = ARRAY_SIZE(ncsi_ops), | |
765 | }; | |
766 | ||
767 | int ncsi_init_netlink(struct net_device *dev) | |
768 | { | |
769 | int rc; | |
770 | ||
771 | rc = genl_register_family(&ncsi_genl_family); | |
772 | if (rc) | |
773 | netdev_err(dev, "ncsi: failed to register netlink family\n"); | |
774 | ||
775 | return rc; | |
776 | } | |
777 | ||
778 | int ncsi_unregister_netlink(struct net_device *dev) | |
779 | { | |
780 | int rc; | |
781 | ||
782 | rc = genl_unregister_family(&ncsi_genl_family); | |
783 | if (rc) | |
784 | netdev_err(dev, "ncsi: failed to unregister netlink family\n"); | |
785 | ||
786 | return rc; | |
787 | } |