diff options
Diffstat (limited to 'net/bluetooth/mgmt.c')
-rw-r--r-- | net/bluetooth/mgmt.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c new file mode 100644 index 000000000000..f827fd908380 --- /dev/null +++ b/net/bluetooth/mgmt.c | |||
@@ -0,0 +1,308 @@ | |||
1 | /* | ||
2 | BlueZ - Bluetooth protocol stack for Linux | ||
3 | Copyright (C) 2010 Nokia Corporation | ||
4 | |||
5 | This program is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License version 2 as | ||
7 | published by the Free Software Foundation; | ||
8 | |||
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||
10 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
11 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. | ||
12 | IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY | ||
13 | CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES | ||
14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
17 | |||
18 | ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, | ||
19 | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS | ||
20 | SOFTWARE IS DISCLAIMED. | ||
21 | */ | ||
22 | |||
23 | /* Bluetooth HCI Management interface */ | ||
24 | |||
25 | #include <asm/uaccess.h> | ||
26 | #include <asm/unaligned.h> | ||
27 | |||
28 | #include <net/bluetooth/bluetooth.h> | ||
29 | #include <net/bluetooth/hci_core.h> | ||
30 | #include <net/bluetooth/mgmt.h> | ||
31 | |||
32 | #define MGMT_VERSION 0 | ||
33 | #define MGMT_REVISION 1 | ||
34 | |||
35 | static int cmd_status(struct sock *sk, u16 cmd, u8 status) | ||
36 | { | ||
37 | struct sk_buff *skb; | ||
38 | struct mgmt_hdr *hdr; | ||
39 | struct mgmt_ev_cmd_status *ev; | ||
40 | |||
41 | BT_DBG("sock %p", sk); | ||
42 | |||
43 | skb = alloc_skb(sizeof(*hdr) + sizeof(*ev), GFP_ATOMIC); | ||
44 | if (!skb) | ||
45 | return -ENOMEM; | ||
46 | |||
47 | hdr = (void *) skb_put(skb, sizeof(*hdr)); | ||
48 | |||
49 | hdr->opcode = cpu_to_le16(MGMT_EV_CMD_STATUS); | ||
50 | hdr->len = cpu_to_le16(sizeof(*ev)); | ||
51 | |||
52 | ev = (void *) skb_put(skb, sizeof(*ev)); | ||
53 | ev->status = status; | ||
54 | put_unaligned_le16(cmd, &ev->opcode); | ||
55 | |||
56 | if (sock_queue_rcv_skb(sk, skb) < 0) | ||
57 | kfree_skb(skb); | ||
58 | |||
59 | return 0; | ||
60 | } | ||
61 | |||
62 | static int read_version(struct sock *sk) | ||
63 | { | ||
64 | struct sk_buff *skb; | ||
65 | struct mgmt_hdr *hdr; | ||
66 | struct mgmt_ev_cmd_complete *ev; | ||
67 | struct mgmt_rp_read_version *rp; | ||
68 | |||
69 | BT_DBG("sock %p", sk); | ||
70 | |||
71 | skb = alloc_skb(sizeof(*hdr) + sizeof(*ev) + sizeof(*rp), GFP_ATOMIC); | ||
72 | if (!skb) | ||
73 | return -ENOMEM; | ||
74 | |||
75 | hdr = (void *) skb_put(skb, sizeof(*hdr)); | ||
76 | hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE); | ||
77 | hdr->len = cpu_to_le16(sizeof(*ev) + sizeof(*rp)); | ||
78 | |||
79 | ev = (void *) skb_put(skb, sizeof(*ev)); | ||
80 | put_unaligned_le16(MGMT_OP_READ_VERSION, &ev->opcode); | ||
81 | |||
82 | rp = (void *) skb_put(skb, sizeof(*rp)); | ||
83 | rp->version = MGMT_VERSION; | ||
84 | put_unaligned_le16(MGMT_REVISION, &rp->revision); | ||
85 | |||
86 | if (sock_queue_rcv_skb(sk, skb) < 0) | ||
87 | kfree_skb(skb); | ||
88 | |||
89 | return 0; | ||
90 | } | ||
91 | |||
92 | static int read_index_list(struct sock *sk) | ||
93 | { | ||
94 | struct sk_buff *skb; | ||
95 | struct mgmt_hdr *hdr; | ||
96 | struct mgmt_ev_cmd_complete *ev; | ||
97 | struct mgmt_rp_read_index_list *rp; | ||
98 | struct list_head *p; | ||
99 | size_t body_len; | ||
100 | u16 count; | ||
101 | int i; | ||
102 | |||
103 | BT_DBG("sock %p", sk); | ||
104 | |||
105 | read_lock(&hci_dev_list_lock); | ||
106 | |||
107 | count = 0; | ||
108 | list_for_each(p, &hci_dev_list) { | ||
109 | count++; | ||
110 | } | ||
111 | |||
112 | body_len = sizeof(*ev) + sizeof(*rp) + (2 * count); | ||
113 | skb = alloc_skb(sizeof(*hdr) + body_len, GFP_ATOMIC); | ||
114 | if (!skb) | ||
115 | return -ENOMEM; | ||
116 | |||
117 | hdr = (void *) skb_put(skb, sizeof(*hdr)); | ||
118 | hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE); | ||
119 | hdr->len = cpu_to_le16(body_len); | ||
120 | |||
121 | ev = (void *) skb_put(skb, sizeof(*ev)); | ||
122 | put_unaligned_le16(MGMT_OP_READ_INDEX_LIST, &ev->opcode); | ||
123 | |||
124 | rp = (void *) skb_put(skb, sizeof(*rp) + (2 * count)); | ||
125 | put_unaligned_le16(count, &rp->num_controllers); | ||
126 | |||
127 | i = 0; | ||
128 | list_for_each(p, &hci_dev_list) { | ||
129 | struct hci_dev *d = list_entry(p, struct hci_dev, list); | ||
130 | put_unaligned_le16(d->id, &rp->index[i++]); | ||
131 | BT_DBG("Added hci%u", d->id); | ||
132 | } | ||
133 | |||
134 | read_unlock(&hci_dev_list_lock); | ||
135 | |||
136 | if (sock_queue_rcv_skb(sk, skb) < 0) | ||
137 | kfree_skb(skb); | ||
138 | |||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | static int read_controller_info(struct sock *sk, unsigned char *data, u16 len) | ||
143 | { | ||
144 | struct sk_buff *skb; | ||
145 | struct mgmt_hdr *hdr; | ||
146 | struct mgmt_ev_cmd_complete *ev; | ||
147 | struct mgmt_rp_read_info *rp; | ||
148 | struct mgmt_cp_read_info *cp; | ||
149 | struct hci_dev *hdev; | ||
150 | u16 dev_id; | ||
151 | |||
152 | BT_DBG("sock %p", sk); | ||
153 | |||
154 | if (len != 2) | ||
155 | return cmd_status(sk, MGMT_OP_READ_INFO, EINVAL); | ||
156 | |||
157 | skb = alloc_skb(sizeof(*hdr) + sizeof(*ev) + sizeof(*rp), GFP_ATOMIC); | ||
158 | if (!skb) | ||
159 | return -ENOMEM; | ||
160 | |||
161 | hdr = (void *) skb_put(skb, sizeof(*hdr)); | ||
162 | hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE); | ||
163 | hdr->len = cpu_to_le16(sizeof(*ev) + sizeof(*rp)); | ||
164 | |||
165 | ev = (void *) skb_put(skb, sizeof(*ev)); | ||
166 | put_unaligned_le16(MGMT_OP_READ_INFO, &ev->opcode); | ||
167 | |||
168 | rp = (void *) skb_put(skb, sizeof(*rp)); | ||
169 | |||
170 | cp = (void *) data; | ||
171 | dev_id = get_unaligned_le16(&cp->index); | ||
172 | |||
173 | BT_DBG("request for hci%u", dev_id); | ||
174 | |||
175 | hdev = hci_dev_get(dev_id); | ||
176 | if (!hdev) { | ||
177 | kfree_skb(skb); | ||
178 | return cmd_status(sk, MGMT_OP_READ_INFO, ENODEV); | ||
179 | } | ||
180 | |||
181 | hci_dev_lock_bh(hdev); | ||
182 | |||
183 | put_unaligned_le16(hdev->id, &rp->index); | ||
184 | rp->type = hdev->dev_type; | ||
185 | |||
186 | rp->powered = test_bit(HCI_UP, &hdev->flags); | ||
187 | rp->discoverable = test_bit(HCI_ISCAN, &hdev->flags); | ||
188 | rp->pairable = test_bit(HCI_PSCAN, &hdev->flags); | ||
189 | |||
190 | if (test_bit(HCI_AUTH, &hdev->flags)) | ||
191 | rp->sec_mode = 3; | ||
192 | else if (hdev->ssp_mode > 0) | ||
193 | rp->sec_mode = 4; | ||
194 | else | ||
195 | rp->sec_mode = 2; | ||
196 | |||
197 | bacpy(&rp->bdaddr, &hdev->bdaddr); | ||
198 | memcpy(rp->features, hdev->features, 8); | ||
199 | memcpy(rp->dev_class, hdev->dev_class, 3); | ||
200 | put_unaligned_le16(hdev->manufacturer, &rp->manufacturer); | ||
201 | rp->hci_ver = hdev->hci_ver; | ||
202 | put_unaligned_le16(hdev->hci_rev, &rp->hci_rev); | ||
203 | |||
204 | hci_dev_unlock_bh(hdev); | ||
205 | hci_dev_put(hdev); | ||
206 | |||
207 | if (sock_queue_rcv_skb(sk, skb) < 0) | ||
208 | kfree_skb(skb); | ||
209 | |||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) | ||
214 | { | ||
215 | unsigned char *buf; | ||
216 | struct mgmt_hdr *hdr; | ||
217 | u16 opcode, len; | ||
218 | int err; | ||
219 | |||
220 | BT_DBG("got %zu bytes", msglen); | ||
221 | |||
222 | if (msglen < sizeof(*hdr)) | ||
223 | return -EINVAL; | ||
224 | |||
225 | buf = kmalloc(msglen, GFP_ATOMIC); | ||
226 | if (!buf) | ||
227 | return -ENOMEM; | ||
228 | |||
229 | if (memcpy_fromiovec(buf, msg->msg_iov, msglen)) { | ||
230 | err = -EFAULT; | ||
231 | goto done; | ||
232 | } | ||
233 | |||
234 | hdr = (struct mgmt_hdr *) buf; | ||
235 | opcode = get_unaligned_le16(&hdr->opcode); | ||
236 | len = get_unaligned_le16(&hdr->len); | ||
237 | |||
238 | if (len != msglen - sizeof(*hdr)) { | ||
239 | err = -EINVAL; | ||
240 | goto done; | ||
241 | } | ||
242 | |||
243 | switch (opcode) { | ||
244 | case MGMT_OP_READ_VERSION: | ||
245 | err = read_version(sk); | ||
246 | break; | ||
247 | case MGMT_OP_READ_INDEX_LIST: | ||
248 | err = read_index_list(sk); | ||
249 | break; | ||
250 | case MGMT_OP_READ_INFO: | ||
251 | err = read_controller_info(sk, buf + sizeof(*hdr), len); | ||
252 | break; | ||
253 | default: | ||
254 | BT_DBG("Unknown op %u", opcode); | ||
255 | err = cmd_status(sk, opcode, 0x01); | ||
256 | break; | ||
257 | } | ||
258 | |||
259 | if (err < 0) | ||
260 | goto done; | ||
261 | |||
262 | err = msglen; | ||
263 | |||
264 | done: | ||
265 | kfree(buf); | ||
266 | return err; | ||
267 | } | ||
268 | |||
269 | static int mgmt_event(u16 event, void *data, u16 data_len) | ||
270 | { | ||
271 | struct sk_buff *skb; | ||
272 | struct mgmt_hdr *hdr; | ||
273 | |||
274 | skb = alloc_skb(sizeof(*hdr) + data_len, GFP_ATOMIC); | ||
275 | if (!skb) | ||
276 | return -ENOMEM; | ||
277 | |||
278 | bt_cb(skb)->channel = HCI_CHANNEL_CONTROL; | ||
279 | |||
280 | hdr = (void *) skb_put(skb, sizeof(*hdr)); | ||
281 | hdr->opcode = cpu_to_le16(event); | ||
282 | hdr->len = cpu_to_le16(data_len); | ||
283 | |||
284 | memcpy(skb_put(skb, data_len), data, data_len); | ||
285 | |||
286 | hci_send_to_sock(NULL, skb); | ||
287 | kfree_skb(skb); | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | int mgmt_index_added(u16 index) | ||
293 | { | ||
294 | struct mgmt_ev_index_added ev; | ||
295 | |||
296 | put_unaligned_le16(index, &ev.index); | ||
297 | |||
298 | return mgmt_event(MGMT_EV_INDEX_ADDED, &ev, sizeof(ev)); | ||
299 | } | ||
300 | |||
301 | int mgmt_index_removed(u16 index) | ||
302 | { | ||
303 | struct mgmt_ev_index_added ev; | ||
304 | |||
305 | put_unaligned_le16(index, &ev.index); | ||
306 | |||
307 | return mgmt_event(MGMT_EV_INDEX_REMOVED, &ev, sizeof(ev)); | ||
308 | } | ||