blob: 32e884732eb16428dc4a9dc09bb3d9752550d086 [file] [log] [blame]
Alexander Aring79fe1a22014-11-09 08:36:53 +01001/* 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
28static int nl802154_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
29 struct genl_info *info);
30
31static void nl802154_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
32 struct genl_info *info);
33
34/* the netlink family */
35static 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 */
47enum nl802154_multicast_groups {
48 NL802154_MCGRP_CONFIG,
49};
50
51static const struct genl_multicast_group nl802154_mcgrps[] = {
52 [NL802154_MCGRP_CONFIG] = { .name = "config", },
53};
54
55/* returns ERR_PTR values */
56static 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
110static 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 */
188static struct cfg802154_registered_device *
189cfg802154_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 */
195static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
Alexander Aringca20ce202014-11-09 08:36:54 +0100196 [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, },
Alexander Aring79fe1a22014-11-09 08:36:53 +0100212};
213
214/* message building helper */
215static 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
Alexander Aringca20ce202014-11-09 08:36:54 +0100222static int
223nl802154_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
243static 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
286finish:
287 return genlmsg_end(msg, hdr);
288
289nla_put_failure:
290 genlmsg_cancel(msg, hdr);
291 return -EMSGSIZE;
292}
293
294struct nl802154_dump_wpan_phy_state {
295 s64 filter_wpan_phy;
296 long start;
297
298};
299
300static 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
337static int
338nl802154_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
393static int nl802154_dump_wpan_phy_done(struct netlink_callback *cb)
394{
395 kfree((void *)cb->args[0]);
396 return 0;
397}
398
399static 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
Alexander Aring79fe1a22014-11-09 08:36:53 +0100417#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
427static 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
489static 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
507static const struct genl_ops nl802154_ops[] = {
Alexander Aringca20ce202014-11-09 08:36:54 +0100508 {
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 },
Alexander Aring79fe1a22014-11-09 08:36:53 +0100518};
519
520/* initialisation/exit functions */
521int nl802154_init(void)
522{
523 return genl_register_family_with_ops_groups(&nl802154_fam, nl802154_ops,
524 nl802154_mcgrps);
525}
526
527void nl802154_exit(void)
528{
529 genl_unregister_family(&nl802154_fam);
530}