]> git.karo-electronics.de Git - karo-tx-linux.git/blob - net/ieee802154/nl802154.c
ieee802154: add wpan_phy dump support
[karo-tx-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 }