Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1eaa9d03 | 2 | /* |
69bb631e | 3 | * Netlink interface for IEEE 802.15.4 stack |
1eaa9d03 DES |
4 | * |
5 | * Copyright 2007, 2008 Siemens AG | |
6 | * | |
1eaa9d03 DES |
7 | * Written by: |
8 | * Sergey Lapin <slapin@ossfans.org> | |
9 | * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com> | |
10 | * Maxim Osipov <maxim.osipov@siemens.com> | |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
5a0e3ad6 | 14 | #include <linux/slab.h> |
060e4179 | 15 | #include <linux/if_arp.h> |
1eaa9d03 DES |
16 | #include <net/netlink.h> |
17 | #include <net/genetlink.h> | |
5ad60d36 | 18 | #include <net/cfg802154.h> |
bb1cafb8 DES |
19 | #include <net/af_ieee802154.h> |
20 | #include <net/ieee802154_netdev.h> | |
21 | #include <net/rtnetlink.h> /* for rtnl_{un,}lock */ | |
1eaa9d03 DES |
22 | #include <linux/nl802154.h> |
23 | ||
24 | #include "ieee802154.h" | |
4a9a816a AA |
25 | #include "rdev-ops.h" |
26 | #include "core.h" | |
1eaa9d03 | 27 | |
15e47304 | 28 | static int ieee802154_nl_fill_phy(struct sk_buff *msg, u32 portid, |
4710d806 | 29 | u32 seq, int flags, struct wpan_phy *phy) |
1eaa9d03 DES |
30 | { |
31 | void *hdr; | |
32 | int i, pages = 0; | |
6396bb22 | 33 | uint32_t *buf = kcalloc(32, sizeof(uint32_t), GFP_KERNEL); |
1eaa9d03 DES |
34 | |
35 | pr_debug("%s\n", __func__); | |
36 | ||
37 | if (!buf) | |
b9cabe52 | 38 | return -EMSGSIZE; |
1eaa9d03 DES |
39 | |
40 | hdr = genlmsg_put(msg, 0, seq, &nl802154_family, flags, | |
4710d806 | 41 | IEEE802154_LIST_PHY); |
1eaa9d03 DES |
42 | if (!hdr) |
43 | goto out; | |
44 | ||
4a3a8c0c | 45 | rtnl_lock(); |
be51da0f DM |
46 | if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) || |
47 | nla_put_u8(msg, IEEE802154_ATTR_PAGE, phy->current_page) || | |
48 | nla_put_u8(msg, IEEE802154_ATTR_CHANNEL, phy->current_channel)) | |
49 | goto nla_put_failure; | |
1eaa9d03 | 50 | for (i = 0; i < 32; i++) { |
72f655e4 AA |
51 | if (phy->supported.channels[i]) |
52 | buf[pages++] = phy->supported.channels[i] | (i << 27); | |
1eaa9d03 | 53 | } |
be51da0f DM |
54 | if (pages && |
55 | nla_put(msg, IEEE802154_ATTR_CHANNEL_PAGE_LIST, | |
56 | pages * sizeof(uint32_t), buf)) | |
57 | goto nla_put_failure; | |
4a3a8c0c | 58 | rtnl_unlock(); |
b9cabe52 | 59 | kfree(buf); |
053c095a JB |
60 | genlmsg_end(msg, hdr); |
61 | return 0; | |
1eaa9d03 DES |
62 | |
63 | nla_put_failure: | |
4a3a8c0c | 64 | rtnl_unlock(); |
1eaa9d03 DES |
65 | genlmsg_cancel(msg, hdr); |
66 | out: | |
67 | kfree(buf); | |
68 | return -EMSGSIZE; | |
69 | } | |
70 | ||
1c582d91 | 71 | int ieee802154_list_phy(struct sk_buff *skb, struct genl_info *info) |
1eaa9d03 DES |
72 | { |
73 | /* Request for interface name, index, type, IEEE address, | |
4710d806 VB |
74 | * PAN Id, short address |
75 | */ | |
1eaa9d03 DES |
76 | struct sk_buff *msg; |
77 | struct wpan_phy *phy; | |
78 | const char *name; | |
79 | int rc = -ENOBUFS; | |
80 | ||
81 | pr_debug("%s\n", __func__); | |
82 | ||
83 | if (!info->attrs[IEEE802154_ATTR_PHY_NAME]) | |
84 | return -EINVAL; | |
85 | ||
86 | name = nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]); | |
87 | if (name[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1] != '\0') | |
88 | return -EINVAL; /* phy name should be null-terminated */ | |
89 | ||
1eaa9d03 DES |
90 | phy = wpan_phy_find(name); |
91 | if (!phy) | |
92 | return -ENODEV; | |
93 | ||
58050fce | 94 | msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
1eaa9d03 DES |
95 | if (!msg) |
96 | goto out_dev; | |
97 | ||
15e47304 | 98 | rc = ieee802154_nl_fill_phy(msg, info->snd_portid, info->snd_seq, |
4710d806 | 99 | 0, phy); |
1eaa9d03 DES |
100 | if (rc < 0) |
101 | goto out_free; | |
102 | ||
103 | wpan_phy_put(phy); | |
104 | ||
105 | return genlmsg_reply(msg, info); | |
106 | out_free: | |
107 | nlmsg_free(msg); | |
108 | out_dev: | |
109 | wpan_phy_put(phy); | |
110 | return rc; | |
1eaa9d03 DES |
111 | } |
112 | ||
113 | struct dump_phy_data { | |
114 | struct sk_buff *skb; | |
115 | struct netlink_callback *cb; | |
116 | int idx, s_idx; | |
117 | }; | |
118 | ||
119 | static int ieee802154_dump_phy_iter(struct wpan_phy *phy, void *_data) | |
120 | { | |
121 | int rc; | |
122 | struct dump_phy_data *data = _data; | |
123 | ||
124 | pr_debug("%s\n", __func__); | |
125 | ||
126 | if (data->idx++ < data->s_idx) | |
127 | return 0; | |
128 | ||
129 | rc = ieee802154_nl_fill_phy(data->skb, | |
4710d806 VB |
130 | NETLINK_CB(data->cb->skb).portid, |
131 | data->cb->nlh->nlmsg_seq, | |
132 | NLM_F_MULTI, | |
133 | phy); | |
1eaa9d03 DES |
134 | |
135 | if (rc < 0) { | |
136 | data->idx--; | |
137 | return rc; | |
138 | } | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
1c582d91 | 143 | int ieee802154_dump_phy(struct sk_buff *skb, struct netlink_callback *cb) |
1eaa9d03 DES |
144 | { |
145 | struct dump_phy_data data = { | |
146 | .cb = cb, | |
147 | .skb = skb, | |
148 | .s_idx = cb->args[0], | |
149 | .idx = 0, | |
150 | }; | |
151 | ||
152 | pr_debug("%s\n", __func__); | |
153 | ||
154 | wpan_phy_for_each(ieee802154_dump_phy_iter, &data); | |
155 | ||
156 | cb->args[0] = data.idx; | |
157 | ||
158 | return skb->len; | |
159 | } | |
160 | ||
1c582d91 | 161 | int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info) |
bb1cafb8 DES |
162 | { |
163 | struct sk_buff *msg; | |
164 | struct wpan_phy *phy; | |
165 | const char *name; | |
166 | const char *devname; | |
167 | int rc = -ENOBUFS; | |
168 | struct net_device *dev; | |
90c049b2 | 169 | int type = __IEEE802154_DEV_INVALID; |
5b4a1039 | 170 | unsigned char name_assign_type; |
bb1cafb8 DES |
171 | |
172 | pr_debug("%s\n", __func__); | |
173 | ||
174 | if (!info->attrs[IEEE802154_ATTR_PHY_NAME]) | |
175 | return -EINVAL; | |
176 | ||
177 | name = nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]); | |
178 | if (name[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1] != '\0') | |
179 | return -EINVAL; /* phy name should be null-terminated */ | |
180 | ||
181 | if (info->attrs[IEEE802154_ATTR_DEV_NAME]) { | |
182 | devname = nla_data(info->attrs[IEEE802154_ATTR_DEV_NAME]); | |
183 | if (devname[nla_len(info->attrs[IEEE802154_ATTR_DEV_NAME]) - 1] | |
184 | != '\0') | |
185 | return -EINVAL; /* phy name should be null-terminated */ | |
5b4a1039 | 186 | name_assign_type = NET_NAME_USER; |
bb1cafb8 DES |
187 | } else { |
188 | devname = "wpan%d"; | |
5b4a1039 | 189 | name_assign_type = NET_NAME_ENUM; |
bb1cafb8 DES |
190 | } |
191 | ||
192 | if (strlen(devname) >= IFNAMSIZ) | |
193 | return -ENAMETOOLONG; | |
194 | ||
195 | phy = wpan_phy_find(name); | |
196 | if (!phy) | |
197 | return -ENODEV; | |
198 | ||
199 | msg = ieee802154_nl_new_reply(info, 0, IEEE802154_ADD_IFACE); | |
200 | if (!msg) | |
201 | goto out_dev; | |
202 | ||
060e4179 DES |
203 | if (info->attrs[IEEE802154_ATTR_HW_ADDR] && |
204 | nla_len(info->attrs[IEEE802154_ATTR_HW_ADDR]) != | |
205 | IEEE802154_ADDR_LEN) { | |
206 | rc = -EINVAL; | |
207 | goto nla_put_failure; | |
208 | } | |
209 | ||
90c049b2 | 210 | if (info->attrs[IEEE802154_ATTR_DEV_TYPE]) { |
211 | type = nla_get_u8(info->attrs[IEEE802154_ATTR_DEV_TYPE]); | |
267d29a6 CE |
212 | if (type >= __IEEE802154_DEV_MAX) { |
213 | rc = -EINVAL; | |
214 | goto nla_put_failure; | |
215 | } | |
90c049b2 | 216 | } |
217 | ||
4a9a816a | 218 | dev = rdev_add_virtual_intf_deprecated(wpan_phy_to_rdev(phy), devname, |
5b4a1039 | 219 | name_assign_type, type); |
bb1cafb8 DES |
220 | if (IS_ERR(dev)) { |
221 | rc = PTR_ERR(dev); | |
222 | goto nla_put_failure; | |
223 | } | |
12cb56c2 | 224 | dev_hold(dev); |
bb1cafb8 | 225 | |
060e4179 DES |
226 | if (info->attrs[IEEE802154_ATTR_HW_ADDR]) { |
227 | struct sockaddr addr; | |
228 | ||
229 | addr.sa_family = ARPHRD_IEEE802154; | |
230 | nla_memcpy(&addr.sa_data, info->attrs[IEEE802154_ATTR_HW_ADDR], | |
4710d806 | 231 | IEEE802154_ADDR_LEN); |
060e4179 | 232 | |
4710d806 | 233 | /* strangely enough, some callbacks (inetdev_event) from |
060e4179 DES |
234 | * dev_set_mac_address require RTNL_LOCK |
235 | */ | |
236 | rtnl_lock(); | |
3a37a963 | 237 | rc = dev_set_mac_address(dev, &addr, NULL); |
060e4179 DES |
238 | rtnl_unlock(); |
239 | if (rc) | |
240 | goto dev_unregister; | |
241 | } | |
242 | ||
be51da0f | 243 | if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) || |
79c6b8ed ZL |
244 | nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name)) { |
245 | rc = -EMSGSIZE; | |
be51da0f | 246 | goto nla_put_failure; |
79c6b8ed | 247 | } |
bb1cafb8 DES |
248 | dev_put(dev); |
249 | ||
250 | wpan_phy_put(phy); | |
251 | ||
252 | return ieee802154_nl_reply(msg, info); | |
253 | ||
060e4179 DES |
254 | dev_unregister: |
255 | rtnl_lock(); /* del_iface must be called with RTNL lock */ | |
4a9a816a | 256 | rdev_del_virtual_intf_deprecated(wpan_phy_to_rdev(phy), dev); |
060e4179 DES |
257 | dev_put(dev); |
258 | rtnl_unlock(); | |
bb1cafb8 DES |
259 | nla_put_failure: |
260 | nlmsg_free(msg); | |
261 | out_dev: | |
262 | wpan_phy_put(phy); | |
263 | return rc; | |
264 | } | |
265 | ||
1c582d91 | 266 | int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info) |
bb1cafb8 DES |
267 | { |
268 | struct sk_buff *msg; | |
269 | struct wpan_phy *phy; | |
270 | const char *name; | |
271 | int rc; | |
272 | struct net_device *dev; | |
273 | ||
274 | pr_debug("%s\n", __func__); | |
275 | ||
276 | if (!info->attrs[IEEE802154_ATTR_DEV_NAME]) | |
277 | return -EINVAL; | |
278 | ||
279 | name = nla_data(info->attrs[IEEE802154_ATTR_DEV_NAME]); | |
280 | if (name[nla_len(info->attrs[IEEE802154_ATTR_DEV_NAME]) - 1] != '\0') | |
281 | return -EINVAL; /* name should be null-terminated */ | |
282 | ||
5b3211dc | 283 | rc = -ENODEV; |
bb1cafb8 DES |
284 | dev = dev_get_by_name(genl_info_net(info), name); |
285 | if (!dev) | |
5b3211dc | 286 | return rc; |
287 | if (dev->type != ARPHRD_IEEE802154) | |
288 | goto out; | |
bb1cafb8 | 289 | |
bd28a11f | 290 | phy = dev->ieee802154_ptr->wpan_phy; |
bb1cafb8 | 291 | BUG_ON(!phy); |
bd28a11f | 292 | get_device(&phy->dev); |
bb1cafb8 DES |
293 | |
294 | rc = -EINVAL; | |
295 | /* phy name is optional, but should be checked if it's given */ | |
296 | if (info->attrs[IEEE802154_ATTR_PHY_NAME]) { | |
297 | struct wpan_phy *phy2; | |
298 | ||
299 | const char *pname = | |
300 | nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]); | |
301 | if (pname[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1] | |
302 | != '\0') | |
303 | /* name should be null-terminated */ | |
304 | goto out_dev; | |
305 | ||
306 | phy2 = wpan_phy_find(pname); | |
307 | if (!phy2) | |
308 | goto out_dev; | |
309 | ||
310 | if (phy != phy2) { | |
311 | wpan_phy_put(phy2); | |
312 | goto out_dev; | |
313 | } | |
314 | } | |
315 | ||
316 | rc = -ENOBUFS; | |
317 | ||
318 | msg = ieee802154_nl_new_reply(info, 0, IEEE802154_DEL_IFACE); | |
319 | if (!msg) | |
320 | goto out_dev; | |
321 | ||
bb1cafb8 | 322 | rtnl_lock(); |
4a9a816a | 323 | rdev_del_virtual_intf_deprecated(wpan_phy_to_rdev(phy), dev); |
bb1cafb8 DES |
324 | |
325 | /* We don't have device anymore */ | |
326 | dev_put(dev); | |
327 | dev = NULL; | |
328 | ||
329 | rtnl_unlock(); | |
330 | ||
be51da0f DM |
331 | if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) || |
332 | nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, name)) | |
333 | goto nla_put_failure; | |
bb1cafb8 DES |
334 | wpan_phy_put(phy); |
335 | ||
336 | return ieee802154_nl_reply(msg, info); | |
337 | ||
338 | nla_put_failure: | |
339 | nlmsg_free(msg); | |
340 | out_dev: | |
341 | wpan_phy_put(phy); | |
5b3211dc | 342 | out: |
1160dfa1 | 343 | dev_put(dev); |
bb1cafb8 DES |
344 | |
345 | return rc; | |
346 | } |