32e884732eb16428dc4a9dc09bb3d9752550d086
[deliverable/linux.git] / net / ieee802154 / nl802154.c
1 /* This program is free software; you can redistribute it and/or modify
2 * it under the terms of the GNU General Public License version 2
3 * as published by the Free Software Foundation.
4 *
5 * This program is distributed in the hope that it will be useful,
6 * but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 * GNU General Public License for more details.
9 *
10 * Authors:
11 * Alexander Aring <aar@pengutronix.de>
12 *
13 * Based on: net/wireless/nl80211.c
14 */
15
16 #include <linux/rtnetlink.h>
17
18 #include <net/cfg802154.h>
19 #include <net/genetlink.h>
20 #include <net/mac802154.h>
21 #include <net/netlink.h>
22 #include <net/nl802154.h>
23 #include <net/sock.h>
24
25 #include "nl802154.h"
26 #include "core.h"
27
28 static int nl802154_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
29 struct genl_info *info);
30
31 static void nl802154_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
32 struct genl_info *info);
33
34 /* the netlink family */
35 static struct genl_family nl802154_fam = {
36 .id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */
37 .name = NL802154_GENL_NAME, /* have users key off the name instead */
38 .hdrsize = 0, /* no private header */
39 .version = 1, /* no particular meaning now */
40 .maxattr = NL802154_ATTR_MAX,
41 .netnsok = true,
42 .pre_doit = nl802154_pre_doit,
43 .post_doit = nl802154_post_doit,
44 };
45
46 /* multicast groups */
47 enum nl802154_multicast_groups {
48 NL802154_MCGRP_CONFIG,
49 };
50
51 static const struct genl_multicast_group nl802154_mcgrps[] = {
52 [NL802154_MCGRP_CONFIG] = { .name = "config", },
53 };
54
55 /* returns ERR_PTR values */
56 static struct wpan_dev *
57 __cfg802154_wpan_dev_from_attrs(struct net *netns, struct nlattr **attrs)
58 {
59 struct cfg802154_registered_device *rdev;
60 struct wpan_dev *result = NULL;
61 bool have_ifidx = attrs[NL802154_ATTR_IFINDEX];
62 bool have_wpan_dev_id = attrs[NL802154_ATTR_WPAN_DEV];
63 u64 wpan_dev_id;
64 int wpan_phy_idx = -1;
65 int ifidx = -1;
66
67 ASSERT_RTNL();
68
69 if (!have_ifidx && !have_wpan_dev_id)
70 return ERR_PTR(-EINVAL);
71
72 if (have_ifidx)
73 ifidx = nla_get_u32(attrs[NL802154_ATTR_IFINDEX]);
74 if (have_wpan_dev_id) {
75 wpan_dev_id = nla_get_u64(attrs[NL802154_ATTR_WPAN_DEV]);
76 wpan_phy_idx = wpan_dev_id >> 32;
77 }
78
79 list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
80 struct wpan_dev *wpan_dev;
81
82 /* TODO netns compare */
83
84 if (have_wpan_dev_id && rdev->wpan_phy_idx != wpan_phy_idx)
85 continue;
86
87 list_for_each_entry(wpan_dev, &rdev->wpan_dev_list, list) {
88 if (have_ifidx && wpan_dev->netdev &&
89 wpan_dev->netdev->ifindex == ifidx) {
90 result = wpan_dev;
91 break;
92 }
93 if (have_wpan_dev_id &&
94 wpan_dev->identifier == (u32)wpan_dev_id) {
95 result = wpan_dev;
96 break;
97 }
98 }
99
100 if (result)
101 break;
102 }
103
104 if (result)
105 return result;
106
107 return ERR_PTR(-ENODEV);
108 }
109
110 static struct cfg802154_registered_device *
111 __cfg802154_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
112 {
113 struct cfg802154_registered_device *rdev = NULL, *tmp;
114 struct net_device *netdev;
115
116 ASSERT_RTNL();
117
118 if (!attrs[NL802154_ATTR_WPAN_PHY] &&
119 !attrs[NL802154_ATTR_IFINDEX] &&
120 !attrs[NL802154_ATTR_WPAN_DEV])
121 return ERR_PTR(-EINVAL);
122
123 if (attrs[NL802154_ATTR_WPAN_PHY])
124 rdev = cfg802154_rdev_by_wpan_phy_idx(
125 nla_get_u32(attrs[NL802154_ATTR_WPAN_PHY]));
126
127 if (attrs[NL802154_ATTR_WPAN_DEV]) {
128 u64 wpan_dev_id = nla_get_u64(attrs[NL802154_ATTR_WPAN_DEV]);
129 struct wpan_dev *wpan_dev;
130 bool found = false;
131
132 tmp = cfg802154_rdev_by_wpan_phy_idx(wpan_dev_id >> 32);
133 if (tmp) {
134 /* make sure wpan_dev exists */
135 list_for_each_entry(wpan_dev, &tmp->wpan_dev_list, list) {
136 if (wpan_dev->identifier != (u32)wpan_dev_id)
137 continue;
138 found = true;
139 break;
140 }
141
142 if (!found)
143 tmp = NULL;
144
145 if (rdev && tmp != rdev)
146 return ERR_PTR(-EINVAL);
147 rdev = tmp;
148 }
149 }
150
151 if (attrs[NL802154_ATTR_IFINDEX]) {
152 int ifindex = nla_get_u32(attrs[NL802154_ATTR_IFINDEX]);
153
154 netdev = __dev_get_by_index(netns, ifindex);
155 if (netdev) {
156 if (netdev->ieee802154_ptr)
157 tmp = wpan_phy_to_rdev(
158 netdev->ieee802154_ptr->wpan_phy);
159 else
160 tmp = NULL;
161
162 /* not wireless device -- return error */
163 if (!tmp)
164 return ERR_PTR(-EINVAL);
165
166 /* mismatch -- return error */
167 if (rdev && tmp != rdev)
168 return ERR_PTR(-EINVAL);
169
170 rdev = tmp;
171 }
172 }
173
174 if (!rdev)
175 return ERR_PTR(-ENODEV);
176
177 /* TODO netns compare */
178
179 return rdev;
180 }
181
182 /* This function returns a pointer to the driver
183 * that the genl_info item that is passed refers to.
184 *
185 * The result of this can be a PTR_ERR and hence must
186 * be checked with IS_ERR() for errors.
187 */
188 static struct cfg802154_registered_device *
189 cfg802154_get_dev_from_info(struct net *netns, struct genl_info *info)
190 {
191 return __cfg802154_rdev_from_attrs(netns, info->attrs);
192 }
193
194 /* policy for the attributes */
195 static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
196 [NL802154_ATTR_WPAN_PHY] = { .type = NLA_U32 },
197 [NL802154_ATTR_WPAN_PHY_NAME] = { .type = NLA_NUL_STRING,
198 .len = 20-1 },
199
200 [NL802154_ATTR_IFINDEX] = { .type = NLA_U32 },
201
202 [NL802154_ATTR_WPAN_DEV] = { .type = NLA_U64 },
203
204 [NL802154_ATTR_PAGE] = { .type = NLA_U8, },
205 [NL802154_ATTR_CHANNEL] = { .type = NLA_U8, },
206
207 [NL802154_ATTR_TX_POWER] = { .type = NLA_S8, },
208
209 [NL802154_ATTR_CCA_MODE] = { .type = NLA_U8, },
210
211 [NL802154_ATTR_SUPPORTED_CHANNEL] = { .type = NLA_U32, },
212 };
213
214 /* message building helper */
215 static inline void *nl802154hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
216 int flags, u8 cmd)
217 {
218 /* since there is no private header just add the generic one */
219 return genlmsg_put(skb, portid, seq, &nl802154_fam, flags, cmd);
220 }
221
222 static int
223 nl802154_send_wpan_phy_channels(struct cfg802154_registered_device *rdev,
224 struct sk_buff *msg)
225 {
226 struct nlattr *nl_page;
227 unsigned long page;
228
229 nl_page = nla_nest_start(msg, NL802154_ATTR_CHANNELS_SUPPORTED);
230 if (!nl_page)
231 return -ENOBUFS;
232
233 for (page = 0; page < WPAN_NUM_PAGES; page++) {
234 if (nla_put_u32(msg, NL802154_ATTR_SUPPORTED_CHANNEL,
235 rdev->wpan_phy.channels_supported[page]))
236 return -ENOBUFS;
237 }
238 nla_nest_end(msg, nl_page);
239
240 return 0;
241 }
242
243 static int nl802154_send_wpan_phy(struct cfg802154_registered_device *rdev,
244 enum nl802154_commands cmd,
245 struct sk_buff *msg, u32 portid, u32 seq,
246 int flags)
247 {
248 void *hdr;
249
250 hdr = nl802154hdr_put(msg, portid, seq, flags, cmd);
251 if (!hdr)
252 return -ENOBUFS;
253
254 if (nla_put_u32(msg, NL802154_ATTR_WPAN_PHY, rdev->wpan_phy_idx) ||
255 nla_put_string(msg, NL802154_ATTR_WPAN_PHY_NAME,
256 wpan_phy_name(&rdev->wpan_phy)) ||
257 nla_put_u32(msg, NL802154_ATTR_GENERATION,
258 cfg802154_rdev_list_generation))
259 goto nla_put_failure;
260
261 if (cmd != NL802154_CMD_NEW_WPAN_PHY)
262 goto finish;
263
264 /* DUMP PHY PIB */
265
266 /* current channel settings */
267 if (nla_put_u8(msg, NL802154_ATTR_PAGE,
268 rdev->wpan_phy.current_page) ||
269 nla_put_u8(msg, NL802154_ATTR_CHANNEL,
270 rdev->wpan_phy.current_channel))
271 goto nla_put_failure;
272
273 /* supported channels array */
274 if (nl802154_send_wpan_phy_channels(rdev, msg))
275 goto nla_put_failure;
276
277 /* cca mode */
278 if (nla_put_u8(msg, NL802154_ATTR_CCA_MODE,
279 rdev->wpan_phy.cca_mode))
280 goto nla_put_failure;
281
282 if (nla_put_s8(msg, NL802154_ATTR_TX_POWER,
283 rdev->wpan_phy.transmit_power))
284 goto nla_put_failure;
285
286 finish:
287 return genlmsg_end(msg, hdr);
288
289 nla_put_failure:
290 genlmsg_cancel(msg, hdr);
291 return -EMSGSIZE;
292 }
293
294 struct nl802154_dump_wpan_phy_state {
295 s64 filter_wpan_phy;
296 long start;
297
298 };
299
300 static int nl802154_dump_wpan_phy_parse(struct sk_buff *skb,
301 struct netlink_callback *cb,
302 struct nl802154_dump_wpan_phy_state *state)
303 {
304 struct nlattr **tb = nl802154_fam.attrbuf;
305 int ret = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl802154_fam.hdrsize,
306 tb, nl802154_fam.maxattr, nl802154_policy);
307
308 /* TODO check if we can handle error here,
309 * we have no backward compatibility
310 */
311 if (ret)
312 return 0;
313
314 if (tb[NL802154_ATTR_WPAN_PHY])
315 state->filter_wpan_phy = nla_get_u32(tb[NL802154_ATTR_WPAN_PHY]);
316 if (tb[NL802154_ATTR_WPAN_DEV])
317 state->filter_wpan_phy = nla_get_u64(tb[NL802154_ATTR_WPAN_DEV]) >> 32;
318 if (tb[NL802154_ATTR_IFINDEX]) {
319 struct net_device *netdev;
320 struct cfg802154_registered_device *rdev;
321 int ifidx = nla_get_u32(tb[NL802154_ATTR_IFINDEX]);
322
323 /* TODO netns */
324 netdev = __dev_get_by_index(&init_net, ifidx);
325 if (!netdev)
326 return -ENODEV;
327 if (netdev->ieee802154_ptr) {
328 rdev = wpan_phy_to_rdev(
329 netdev->ieee802154_ptr->wpan_phy);
330 state->filter_wpan_phy = rdev->wpan_phy_idx;
331 }
332 }
333
334 return 0;
335 }
336
337 static int
338 nl802154_dump_wpan_phy(struct sk_buff *skb, struct netlink_callback *cb)
339 {
340 int idx = 0, ret;
341 struct nl802154_dump_wpan_phy_state *state = (void *)cb->args[0];
342 struct cfg802154_registered_device *rdev;
343
344 rtnl_lock();
345 if (!state) {
346 state = kzalloc(sizeof(*state), GFP_KERNEL);
347 if (!state) {
348 rtnl_unlock();
349 return -ENOMEM;
350 }
351 state->filter_wpan_phy = -1;
352 ret = nl802154_dump_wpan_phy_parse(skb, cb, state);
353 if (ret) {
354 kfree(state);
355 rtnl_unlock();
356 return ret;
357 }
358 cb->args[0] = (long)state;
359 }
360
361 list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
362 /* TODO net ns compare */
363 if (++idx <= state->start)
364 continue;
365 if (state->filter_wpan_phy != -1 &&
366 state->filter_wpan_phy != rdev->wpan_phy_idx)
367 continue;
368 /* attempt to fit multiple wpan_phy data chunks into the skb */
369 ret = nl802154_send_wpan_phy(rdev,
370 NL802154_CMD_NEW_WPAN_PHY,
371 skb,
372 NETLINK_CB(cb->skb).portid,
373 cb->nlh->nlmsg_seq, NLM_F_MULTI);
374 if (ret < 0) {
375 if ((ret == -ENOBUFS || ret == -EMSGSIZE) &&
376 !skb->len && cb->min_dump_alloc < 4096) {
377 cb->min_dump_alloc = 4096;
378 rtnl_unlock();
379 return 1;
380 }
381 idx--;
382 break;
383 }
384 break;
385 }
386 rtnl_unlock();
387
388 state->start = idx;
389
390 return skb->len;
391 }
392
393 static int nl802154_dump_wpan_phy_done(struct netlink_callback *cb)
394 {
395 kfree((void *)cb->args[0]);
396 return 0;
397 }
398
399 static int nl802154_get_wpan_phy(struct sk_buff *skb, struct genl_info *info)
400 {
401 struct sk_buff *msg;
402 struct cfg802154_registered_device *rdev = info->user_ptr[0];
403
404 msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
405 if (!msg)
406 return -ENOMEM;
407
408 if (nl802154_send_wpan_phy(rdev, NL802154_CMD_NEW_WPAN_PHY, msg,
409 info->snd_portid, info->snd_seq, 0) < 0) {
410 nlmsg_free(msg);
411 return -ENOBUFS;
412 }
413
414 return genlmsg_reply(msg, info);
415 }
416
417 #define NL802154_FLAG_NEED_WPAN_PHY 0x01
418 #define NL802154_FLAG_NEED_NETDEV 0x02
419 #define NL802154_FLAG_NEED_RTNL 0x04
420 #define NL802154_FLAG_CHECK_NETDEV_UP 0x08
421 #define NL802154_FLAG_NEED_NETDEV_UP (NL802154_FLAG_NEED_NETDEV |\
422 NL802154_FLAG_CHECK_NETDEV_UP)
423 #define NL802154_FLAG_NEED_WPAN_DEV 0x10
424 #define NL802154_FLAG_NEED_WPAN_DEV_UP (NL802154_FLAG_NEED_WPAN_DEV |\
425 NL802154_FLAG_CHECK_NETDEV_UP)
426
427 static int nl802154_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
428 struct genl_info *info)
429 {
430 struct cfg802154_registered_device *rdev;
431 struct wpan_dev *wpan_dev;
432 struct net_device *dev;
433 bool rtnl = ops->internal_flags & NL802154_FLAG_NEED_RTNL;
434
435 if (rtnl)
436 rtnl_lock();
437
438 if (ops->internal_flags & NL802154_FLAG_NEED_WPAN_PHY) {
439 rdev = cfg802154_get_dev_from_info(genl_info_net(info), info);
440 if (IS_ERR(rdev)) {
441 if (rtnl)
442 rtnl_unlock();
443 return PTR_ERR(rdev);
444 }
445 info->user_ptr[0] = rdev;
446 } else if (ops->internal_flags & NL802154_FLAG_NEED_NETDEV ||
447 ops->internal_flags & NL802154_FLAG_NEED_WPAN_DEV) {
448 ASSERT_RTNL();
449 wpan_dev = __cfg802154_wpan_dev_from_attrs(genl_info_net(info),
450 info->attrs);
451 if (IS_ERR(wpan_dev)) {
452 if (rtnl)
453 rtnl_unlock();
454 return PTR_ERR(wpan_dev);
455 }
456
457 dev = wpan_dev->netdev;
458 rdev = wpan_phy_to_rdev(wpan_dev->wpan_phy);
459
460 if (ops->internal_flags & NL802154_FLAG_NEED_NETDEV) {
461 if (!dev) {
462 if (rtnl)
463 rtnl_unlock();
464 return -EINVAL;
465 }
466
467 info->user_ptr[1] = dev;
468 } else {
469 info->user_ptr[1] = wpan_dev;
470 }
471
472 if (dev) {
473 if (ops->internal_flags & NL802154_FLAG_CHECK_NETDEV_UP &&
474 !netif_running(dev)) {
475 if (rtnl)
476 rtnl_unlock();
477 return -ENETDOWN;
478 }
479
480 dev_hold(dev);
481 }
482
483 info->user_ptr[0] = rdev;
484 }
485
486 return 0;
487 }
488
489 static void nl802154_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
490 struct genl_info *info)
491 {
492 if (info->user_ptr[1]) {
493 if (ops->internal_flags & NL802154_FLAG_NEED_WPAN_DEV) {
494 struct wpan_dev *wpan_dev = info->user_ptr[1];
495
496 if (wpan_dev->netdev)
497 dev_put(wpan_dev->netdev);
498 } else {
499 dev_put(info->user_ptr[1]);
500 }
501 }
502
503 if (ops->internal_flags & NL802154_FLAG_NEED_RTNL)
504 rtnl_unlock();
505 }
506
507 static const struct genl_ops nl802154_ops[] = {
508 {
509 .cmd = NL802154_CMD_GET_WPAN_PHY,
510 .doit = nl802154_get_wpan_phy,
511 .dumpit = nl802154_dump_wpan_phy,
512 .done = nl802154_dump_wpan_phy_done,
513 .policy = nl802154_policy,
514 /* can be retrieved by unprivileged users */
515 .internal_flags = NL802154_FLAG_NEED_WPAN_PHY |
516 NL802154_FLAG_NEED_RTNL,
517 },
518 };
519
520 /* initialisation/exit functions */
521 int nl802154_init(void)
522 {
523 return genl_register_family_with_ops_groups(&nl802154_fam, nl802154_ops,
524 nl802154_mcgrps);
525 }
526
527 void nl802154_exit(void)
528 {
529 genl_unregister_family(&nl802154_fam);
530 }
This page took 0.041193 seconds and 4 git commands to generate.