root / hw / bt-hid.c @ e590ecbe
History | View | Annotate | Download (15.1 kB)
1 | 47e699dc | balrog | /*
|
---|---|---|---|
2 | 47e699dc | balrog | * QEMU Bluetooth HID Profile wrapper for USB HID.
|
3 | 47e699dc | balrog | *
|
4 | 47e699dc | balrog | * Copyright (C) 2007-2008 OpenMoko, Inc.
|
5 | 47e699dc | balrog | * Written by Andrzej Zaborowski <andrew@openedhand.com>
|
6 | 47e699dc | balrog | *
|
7 | 47e699dc | balrog | * This program is free software; you can redistribute it and/or
|
8 | 47e699dc | balrog | * modify it under the terms of the GNU General Public License as
|
9 | 47e699dc | balrog | * published by the Free Software Foundation; either version 2 or
|
10 | 47e699dc | balrog | * (at your option) version 3 of the License.
|
11 | 47e699dc | balrog | *
|
12 | 47e699dc | balrog | * This program is distributed in the hope that it will be useful,
|
13 | 47e699dc | balrog | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14 | 47e699dc | balrog | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15 | 47e699dc | balrog | * GNU General Public License for more details.
|
16 | 47e699dc | balrog | *
|
17 | fad6cb1a | aurel32 | * You should have received a copy of the GNU General Public License along
|
18 | 8167ee88 | Blue Swirl | * with this program; if not, if not, see <http://www.gnu.org/licenses/>.
|
19 | 47e699dc | balrog | */
|
20 | 47e699dc | balrog | |
21 | 47e699dc | balrog | #include "qemu-common.h" |
22 | fb8f4cee | Gerd Hoffmann | #include "qemu-timer.h" |
23 | fb8f4cee | Gerd Hoffmann | #include "console.h" |
24 | fb8f4cee | Gerd Hoffmann | #include "hid.h" |
25 | 47e699dc | balrog | #include "bt.h" |
26 | 47e699dc | balrog | |
27 | 47e699dc | balrog | enum hid_transaction_req {
|
28 | 47e699dc | balrog | BT_HANDSHAKE = 0x0,
|
29 | 47e699dc | balrog | BT_HID_CONTROL = 0x1,
|
30 | 47e699dc | balrog | BT_GET_REPORT = 0x4,
|
31 | 47e699dc | balrog | BT_SET_REPORT = 0x5,
|
32 | 47e699dc | balrog | BT_GET_PROTOCOL = 0x6,
|
33 | 47e699dc | balrog | BT_SET_PROTOCOL = 0x7,
|
34 | 47e699dc | balrog | BT_GET_IDLE = 0x8,
|
35 | 47e699dc | balrog | BT_SET_IDLE = 0x9,
|
36 | 47e699dc | balrog | BT_DATA = 0xa,
|
37 | 47e699dc | balrog | BT_DATC = 0xb,
|
38 | 47e699dc | balrog | }; |
39 | 47e699dc | balrog | |
40 | 47e699dc | balrog | enum hid_transaction_handshake {
|
41 | 47e699dc | balrog | BT_HS_SUCCESSFUL = 0x0,
|
42 | 47e699dc | balrog | BT_HS_NOT_READY = 0x1,
|
43 | 47e699dc | balrog | BT_HS_ERR_INVALID_REPORT_ID = 0x2,
|
44 | 47e699dc | balrog | BT_HS_ERR_UNSUPPORTED_REQUEST = 0x3,
|
45 | 47e699dc | balrog | BT_HS_ERR_INVALID_PARAMETER = 0x4,
|
46 | 47e699dc | balrog | BT_HS_ERR_UNKNOWN = 0xe,
|
47 | 47e699dc | balrog | BT_HS_ERR_FATAL = 0xf,
|
48 | 47e699dc | balrog | }; |
49 | 47e699dc | balrog | |
50 | 47e699dc | balrog | enum hid_transaction_control {
|
51 | 47e699dc | balrog | BT_HC_NOP = 0x0,
|
52 | 47e699dc | balrog | BT_HC_HARD_RESET = 0x1,
|
53 | 47e699dc | balrog | BT_HC_SOFT_RESET = 0x2,
|
54 | 47e699dc | balrog | BT_HC_SUSPEND = 0x3,
|
55 | 47e699dc | balrog | BT_HC_EXIT_SUSPEND = 0x4,
|
56 | 47e699dc | balrog | BT_HC_VIRTUAL_CABLE_UNPLUG = 0x5,
|
57 | 47e699dc | balrog | }; |
58 | 47e699dc | balrog | |
59 | 47e699dc | balrog | enum hid_protocol {
|
60 | 47e699dc | balrog | BT_HID_PROTO_BOOT = 0,
|
61 | 47e699dc | balrog | BT_HID_PROTO_REPORT = 1,
|
62 | 47e699dc | balrog | }; |
63 | 47e699dc | balrog | |
64 | 47e699dc | balrog | enum hid_boot_reportid {
|
65 | 47e699dc | balrog | BT_HID_BOOT_INVALID = 0,
|
66 | 47e699dc | balrog | BT_HID_BOOT_KEYBOARD, |
67 | 47e699dc | balrog | BT_HID_BOOT_MOUSE, |
68 | 47e699dc | balrog | }; |
69 | 47e699dc | balrog | |
70 | 47e699dc | balrog | enum hid_data_pkt {
|
71 | 47e699dc | balrog | BT_DATA_OTHER = 0,
|
72 | 47e699dc | balrog | BT_DATA_INPUT, |
73 | 47e699dc | balrog | BT_DATA_OUTPUT, |
74 | 47e699dc | balrog | BT_DATA_FEATURE, |
75 | 47e699dc | balrog | }; |
76 | 47e699dc | balrog | |
77 | 47e699dc | balrog | #define BT_HID_MTU 48 |
78 | 47e699dc | balrog | |
79 | 47e699dc | balrog | /* HID interface requests */
|
80 | 47e699dc | balrog | #define GET_REPORT 0xa101 |
81 | 47e699dc | balrog | #define GET_IDLE 0xa102 |
82 | 47e699dc | balrog | #define GET_PROTOCOL 0xa103 |
83 | 47e699dc | balrog | #define SET_REPORT 0x2109 |
84 | 47e699dc | balrog | #define SET_IDLE 0x210a |
85 | 47e699dc | balrog | #define SET_PROTOCOL 0x210b |
86 | 47e699dc | balrog | |
87 | 47e699dc | balrog | struct bt_hid_device_s {
|
88 | 47e699dc | balrog | struct bt_l2cap_device_s btdev;
|
89 | 47e699dc | balrog | struct bt_l2cap_conn_params_s *control;
|
90 | 47e699dc | balrog | struct bt_l2cap_conn_params_s *interrupt;
|
91 | fb8f4cee | Gerd Hoffmann | HIDState hid; |
92 | 47e699dc | balrog | |
93 | 47e699dc | balrog | int proto;
|
94 | 47e699dc | balrog | int connected;
|
95 | 47e699dc | balrog | int data_type;
|
96 | 47e699dc | balrog | int intr_state;
|
97 | 47e699dc | balrog | struct {
|
98 | 47e699dc | balrog | int len;
|
99 | 47e699dc | balrog | uint8_t buffer[1024];
|
100 | 47e699dc | balrog | } dataother, datain, dataout, feature, intrdataout; |
101 | 47e699dc | balrog | enum {
|
102 | 47e699dc | balrog | bt_state_ready, |
103 | 47e699dc | balrog | bt_state_transaction, |
104 | 47e699dc | balrog | bt_state_suspend, |
105 | 47e699dc | balrog | } state; |
106 | 47e699dc | balrog | }; |
107 | 47e699dc | balrog | |
108 | 47e699dc | balrog | static void bt_hid_reset(struct bt_hid_device_s *s) |
109 | 47e699dc | balrog | { |
110 | 47e699dc | balrog | struct bt_scatternet_s *net = s->btdev.device.net;
|
111 | 47e699dc | balrog | |
112 | 47e699dc | balrog | /* Go as far as... */
|
113 | 47e699dc | balrog | bt_l2cap_device_done(&s->btdev); |
114 | 47e699dc | balrog | bt_l2cap_device_init(&s->btdev, net); |
115 | 47e699dc | balrog | |
116 | fb8f4cee | Gerd Hoffmann | hid_reset(&s->hid); |
117 | 47e699dc | balrog | s->proto = BT_HID_PROTO_REPORT; |
118 | 47e699dc | balrog | s->state = bt_state_ready; |
119 | 47e699dc | balrog | s->dataother.len = 0;
|
120 | 47e699dc | balrog | s->datain.len = 0;
|
121 | 47e699dc | balrog | s->dataout.len = 0;
|
122 | 47e699dc | balrog | s->feature.len = 0;
|
123 | 47e699dc | balrog | s->intrdataout.len = 0;
|
124 | 47e699dc | balrog | s->intr_state = 0;
|
125 | 47e699dc | balrog | } |
126 | 47e699dc | balrog | |
127 | 47e699dc | balrog | static int bt_hid_out(struct bt_hid_device_s *s) |
128 | 47e699dc | balrog | { |
129 | 47e699dc | balrog | if (s->data_type == BT_DATA_OUTPUT) {
|
130 | fb8f4cee | Gerd Hoffmann | /* nothing */
|
131 | fb8f4cee | Gerd Hoffmann | ; |
132 | 47e699dc | balrog | } |
133 | 47e699dc | balrog | |
134 | 47e699dc | balrog | if (s->data_type == BT_DATA_FEATURE) {
|
135 | 47e699dc | balrog | /* XXX:
|
136 | 47e699dc | balrog | * does this send a USB_REQ_CLEAR_FEATURE/USB_REQ_SET_FEATURE
|
137 | 47e699dc | balrog | * or a SET_REPORT? */
|
138 | fb8f4cee | Gerd Hoffmann | ; |
139 | 47e699dc | balrog | } |
140 | 47e699dc | balrog | |
141 | 47e699dc | balrog | return -1; |
142 | 47e699dc | balrog | } |
143 | 47e699dc | balrog | |
144 | 47e699dc | balrog | static int bt_hid_in(struct bt_hid_device_s *s) |
145 | 47e699dc | balrog | { |
146 | fb8f4cee | Gerd Hoffmann | s->datain.len = hid_keyboard_poll(&s->hid, s->datain.buffer, |
147 | fb8f4cee | Gerd Hoffmann | sizeof(s->datain.buffer));
|
148 | 47e699dc | balrog | return s->datain.len;
|
149 | 47e699dc | balrog | } |
150 | 47e699dc | balrog | |
151 | 47e699dc | balrog | static void bt_hid_send_handshake(struct bt_hid_device_s *s, int result) |
152 | 47e699dc | balrog | { |
153 | 47e699dc | balrog | *s->control->sdu_out(s->control, 1) =
|
154 | 47e699dc | balrog | (BT_HANDSHAKE << 4) | result;
|
155 | 47e699dc | balrog | s->control->sdu_submit(s->control); |
156 | 47e699dc | balrog | } |
157 | 47e699dc | balrog | |
158 | 47e699dc | balrog | static void bt_hid_send_control(struct bt_hid_device_s *s, int operation) |
159 | 47e699dc | balrog | { |
160 | 47e699dc | balrog | *s->control->sdu_out(s->control, 1) =
|
161 | 47e699dc | balrog | (BT_HID_CONTROL << 4) | operation;
|
162 | 47e699dc | balrog | s->control->sdu_submit(s->control); |
163 | 47e699dc | balrog | } |
164 | 47e699dc | balrog | |
165 | 47e699dc | balrog | static void bt_hid_disconnect(struct bt_hid_device_s *s) |
166 | 47e699dc | balrog | { |
167 | 47e699dc | balrog | /* Disconnect s->control and s->interrupt */
|
168 | 47e699dc | balrog | } |
169 | 47e699dc | balrog | |
170 | 47e699dc | balrog | static void bt_hid_send_data(struct bt_l2cap_conn_params_s *ch, int type, |
171 | 47e699dc | balrog | const uint8_t *data, int len) |
172 | 47e699dc | balrog | { |
173 | 47e699dc | balrog | uint8_t *pkt, hdr = (BT_DATA << 4) | type;
|
174 | 47e699dc | balrog | int plen;
|
175 | 47e699dc | balrog | |
176 | 47e699dc | balrog | do {
|
177 | 47e699dc | balrog | plen = MIN(len, ch->remote_mtu - 1);
|
178 | 47e699dc | balrog | pkt = ch->sdu_out(ch, plen + 1);
|
179 | 47e699dc | balrog | |
180 | 47e699dc | balrog | pkt[0] = hdr;
|
181 | 47e699dc | balrog | if (plen)
|
182 | 47e699dc | balrog | memcpy(pkt + 1, data, plen);
|
183 | 47e699dc | balrog | ch->sdu_submit(ch); |
184 | 47e699dc | balrog | |
185 | 47e699dc | balrog | len -= plen; |
186 | 47e699dc | balrog | data += plen; |
187 | 47e699dc | balrog | hdr = (BT_DATC << 4) | type;
|
188 | 47e699dc | balrog | } while (plen == ch->remote_mtu - 1); |
189 | 47e699dc | balrog | } |
190 | 47e699dc | balrog | |
191 | 47e699dc | balrog | static void bt_hid_control_transaction(struct bt_hid_device_s *s, |
192 | 47e699dc | balrog | const uint8_t *data, int len) |
193 | 47e699dc | balrog | { |
194 | 47e699dc | balrog | uint8_t type, parameter; |
195 | 47e699dc | balrog | int rlen, ret = -1; |
196 | 47e699dc | balrog | if (len < 1) |
197 | 47e699dc | balrog | return;
|
198 | 47e699dc | balrog | |
199 | 47e699dc | balrog | type = data[0] >> 4; |
200 | 47e699dc | balrog | parameter = data[0] & 0xf; |
201 | 47e699dc | balrog | |
202 | 47e699dc | balrog | switch (type) {
|
203 | 47e699dc | balrog | case BT_HANDSHAKE:
|
204 | 47e699dc | balrog | case BT_DATA:
|
205 | 47e699dc | balrog | switch (parameter) {
|
206 | 47e699dc | balrog | default:
|
207 | 47e699dc | balrog | /* These are not expected to be sent this direction. */
|
208 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
209 | 47e699dc | balrog | } |
210 | 47e699dc | balrog | break;
|
211 | 47e699dc | balrog | |
212 | 47e699dc | balrog | case BT_HID_CONTROL:
|
213 | 47e699dc | balrog | if (len != 1 || (parameter != BT_HC_VIRTUAL_CABLE_UNPLUG && |
214 | 47e699dc | balrog | s->state == bt_state_transaction)) { |
215 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
216 | 47e699dc | balrog | break;
|
217 | 47e699dc | balrog | } |
218 | 47e699dc | balrog | switch (parameter) {
|
219 | 47e699dc | balrog | case BT_HC_NOP:
|
220 | 47e699dc | balrog | break;
|
221 | 47e699dc | balrog | case BT_HC_HARD_RESET:
|
222 | 47e699dc | balrog | case BT_HC_SOFT_RESET:
|
223 | 47e699dc | balrog | bt_hid_reset(s); |
224 | 47e699dc | balrog | break;
|
225 | 47e699dc | balrog | case BT_HC_SUSPEND:
|
226 | 47e699dc | balrog | if (s->state == bt_state_ready)
|
227 | 47e699dc | balrog | s->state = bt_state_suspend; |
228 | 47e699dc | balrog | else
|
229 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
230 | 47e699dc | balrog | break;
|
231 | 47e699dc | balrog | case BT_HC_EXIT_SUSPEND:
|
232 | 47e699dc | balrog | if (s->state == bt_state_suspend)
|
233 | 47e699dc | balrog | s->state = bt_state_ready; |
234 | 47e699dc | balrog | else
|
235 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
236 | 47e699dc | balrog | break;
|
237 | 47e699dc | balrog | case BT_HC_VIRTUAL_CABLE_UNPLUG:
|
238 | 47e699dc | balrog | bt_hid_disconnect(s); |
239 | 47e699dc | balrog | break;
|
240 | 47e699dc | balrog | default:
|
241 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
242 | 47e699dc | balrog | } |
243 | 47e699dc | balrog | break;
|
244 | 47e699dc | balrog | |
245 | 47e699dc | balrog | case BT_GET_REPORT:
|
246 | 47e699dc | balrog | /* No ReportIDs declared. */
|
247 | 47e699dc | balrog | if (((parameter & 8) && len != 3) || |
248 | 47e699dc | balrog | (!(parameter & 8) && len != 1) || |
249 | 47e699dc | balrog | s->state != bt_state_ready) { |
250 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
251 | 47e699dc | balrog | break;
|
252 | 47e699dc | balrog | } |
253 | 47e699dc | balrog | if (parameter & 8) |
254 | 47e699dc | balrog | rlen = data[2] | (data[3] << 8); |
255 | 47e699dc | balrog | else
|
256 | 47e699dc | balrog | rlen = INT_MAX; |
257 | 47e699dc | balrog | switch (parameter & 3) { |
258 | 47e699dc | balrog | case BT_DATA_OTHER:
|
259 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
260 | 47e699dc | balrog | break;
|
261 | 47e699dc | balrog | case BT_DATA_INPUT:
|
262 | 47e699dc | balrog | /* Here we can as well poll s->usbdev */
|
263 | 47e699dc | balrog | bt_hid_send_data(s->control, BT_DATA_INPUT, |
264 | 47e699dc | balrog | s->datain.buffer, MIN(rlen, s->datain.len)); |
265 | 47e699dc | balrog | break;
|
266 | 47e699dc | balrog | case BT_DATA_OUTPUT:
|
267 | 47e699dc | balrog | bt_hid_send_data(s->control, BT_DATA_OUTPUT, |
268 | 47e699dc | balrog | s->dataout.buffer, MIN(rlen, s->dataout.len)); |
269 | 47e699dc | balrog | break;
|
270 | 47e699dc | balrog | case BT_DATA_FEATURE:
|
271 | 47e699dc | balrog | bt_hid_send_data(s->control, BT_DATA_FEATURE, |
272 | 47e699dc | balrog | s->feature.buffer, MIN(rlen, s->feature.len)); |
273 | 47e699dc | balrog | break;
|
274 | 47e699dc | balrog | } |
275 | 47e699dc | balrog | break;
|
276 | 47e699dc | balrog | |
277 | 47e699dc | balrog | case BT_SET_REPORT:
|
278 | 47e699dc | balrog | if (len < 2 || len > BT_HID_MTU || s->state != bt_state_ready || |
279 | 47e699dc | balrog | (parameter & 3) == BT_DATA_OTHER ||
|
280 | 47e699dc | balrog | (parameter & 3) == BT_DATA_INPUT) {
|
281 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
282 | 47e699dc | balrog | break;
|
283 | 47e699dc | balrog | } |
284 | 47e699dc | balrog | s->data_type = parameter & 3;
|
285 | 47e699dc | balrog | if (s->data_type == BT_DATA_OUTPUT) {
|
286 | 47e699dc | balrog | s->dataout.len = len - 1;
|
287 | 47e699dc | balrog | memcpy(s->dataout.buffer, data + 1, s->dataout.len);
|
288 | 47e699dc | balrog | } else {
|
289 | 47e699dc | balrog | s->feature.len = len - 1;
|
290 | 47e699dc | balrog | memcpy(s->feature.buffer, data + 1, s->feature.len);
|
291 | 47e699dc | balrog | } |
292 | 47e699dc | balrog | if (len == BT_HID_MTU)
|
293 | 47e699dc | balrog | s->state = bt_state_transaction; |
294 | 47e699dc | balrog | else
|
295 | 47e699dc | balrog | bt_hid_out(s); |
296 | 47e699dc | balrog | break;
|
297 | 47e699dc | balrog | |
298 | 47e699dc | balrog | case BT_GET_PROTOCOL:
|
299 | 47e699dc | balrog | if (len != 1 || s->state == bt_state_transaction) { |
300 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
301 | 47e699dc | balrog | break;
|
302 | 47e699dc | balrog | } |
303 | 47e699dc | balrog | *s->control->sdu_out(s->control, 1) = s->proto;
|
304 | 47e699dc | balrog | s->control->sdu_submit(s->control); |
305 | 47e699dc | balrog | break;
|
306 | 47e699dc | balrog | |
307 | 47e699dc | balrog | case BT_SET_PROTOCOL:
|
308 | 47e699dc | balrog | if (len != 1 || s->state == bt_state_transaction || |
309 | 47e699dc | balrog | (parameter != BT_HID_PROTO_BOOT && |
310 | 47e699dc | balrog | parameter != BT_HID_PROTO_REPORT)) { |
311 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
312 | 47e699dc | balrog | break;
|
313 | 47e699dc | balrog | } |
314 | 47e699dc | balrog | s->proto = parameter; |
315 | fb8f4cee | Gerd Hoffmann | s->hid.protocol = parameter; |
316 | 47e699dc | balrog | ret = BT_HS_SUCCESSFUL; |
317 | 47e699dc | balrog | break;
|
318 | 47e699dc | balrog | |
319 | 47e699dc | balrog | case BT_GET_IDLE:
|
320 | 47e699dc | balrog | if (len != 1 || s->state == bt_state_transaction) { |
321 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
322 | 47e699dc | balrog | break;
|
323 | 47e699dc | balrog | } |
324 | fb8f4cee | Gerd Hoffmann | *s->control->sdu_out(s->control, 1) = s->hid.idle;
|
325 | 47e699dc | balrog | s->control->sdu_submit(s->control); |
326 | 47e699dc | balrog | break;
|
327 | 47e699dc | balrog | |
328 | 47e699dc | balrog | case BT_SET_IDLE:
|
329 | 47e699dc | balrog | if (len != 2 || s->state == bt_state_transaction) { |
330 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
331 | 47e699dc | balrog | break;
|
332 | 47e699dc | balrog | } |
333 | 47e699dc | balrog | |
334 | fb8f4cee | Gerd Hoffmann | s->hid.idle = data[1];
|
335 | 47e699dc | balrog | /* XXX: Does this generate a handshake? */
|
336 | 47e699dc | balrog | break;
|
337 | 47e699dc | balrog | |
338 | 47e699dc | balrog | case BT_DATC:
|
339 | 47e699dc | balrog | if (len > BT_HID_MTU || s->state != bt_state_transaction) {
|
340 | 47e699dc | balrog | ret = BT_HS_ERR_INVALID_PARAMETER; |
341 | 47e699dc | balrog | break;
|
342 | 47e699dc | balrog | } |
343 | 47e699dc | balrog | if (s->data_type == BT_DATA_OUTPUT) {
|
344 | 47e699dc | balrog | memcpy(s->dataout.buffer + s->dataout.len, data + 1, len - 1); |
345 | 47e699dc | balrog | s->dataout.len += len - 1;
|
346 | 47e699dc | balrog | } else {
|
347 | 47e699dc | balrog | memcpy(s->feature.buffer + s->feature.len, data + 1, len - 1); |
348 | 47e699dc | balrog | s->feature.len += len - 1;
|
349 | 47e699dc | balrog | } |
350 | 47e699dc | balrog | if (len < BT_HID_MTU) {
|
351 | 47e699dc | balrog | bt_hid_out(s); |
352 | 47e699dc | balrog | s->state = bt_state_ready; |
353 | 47e699dc | balrog | } |
354 | 47e699dc | balrog | break;
|
355 | 47e699dc | balrog | |
356 | 47e699dc | balrog | default:
|
357 | 47e699dc | balrog | ret = BT_HS_ERR_UNSUPPORTED_REQUEST; |
358 | 47e699dc | balrog | } |
359 | 47e699dc | balrog | |
360 | 47e699dc | balrog | if (ret != -1) |
361 | 47e699dc | balrog | bt_hid_send_handshake(s, ret); |
362 | 47e699dc | balrog | } |
363 | 47e699dc | balrog | |
364 | 47e699dc | balrog | static void bt_hid_control_sdu(void *opaque, const uint8_t *data, int len) |
365 | 47e699dc | balrog | { |
366 | 47e699dc | balrog | struct bt_hid_device_s *hid = opaque;
|
367 | 47e699dc | balrog | |
368 | 7442511c | blueswir1 | bt_hid_control_transaction(hid, data, len); |
369 | 47e699dc | balrog | } |
370 | 47e699dc | balrog | |
371 | fb8f4cee | Gerd Hoffmann | static void bt_hid_datain(HIDState *hs) |
372 | 47e699dc | balrog | { |
373 | fb8f4cee | Gerd Hoffmann | struct bt_hid_device_s *hid =
|
374 | fb8f4cee | Gerd Hoffmann | container_of(hs, struct bt_hid_device_s, hid);
|
375 | 47e699dc | balrog | |
376 | 47e699dc | balrog | /* If suspended, wake-up and send a wake-up event first. We might
|
377 | 47e699dc | balrog | * want to also inspect the input report and ignore event like
|
378 | 47e699dc | balrog | * mouse movements until a button event occurs. */
|
379 | 47e699dc | balrog | if (hid->state == bt_state_suspend) {
|
380 | 47e699dc | balrog | hid->state = bt_state_ready; |
381 | 47e699dc | balrog | } |
382 | 47e699dc | balrog | |
383 | 47e699dc | balrog | if (bt_hid_in(hid) > 0) |
384 | 47e699dc | balrog | /* TODO: when in boot-mode precede any Input reports with the ReportID
|
385 | 47e699dc | balrog | * byte, here and in GetReport/SetReport on the Control channel. */
|
386 | 47e699dc | balrog | bt_hid_send_data(hid->interrupt, BT_DATA_INPUT, |
387 | 47e699dc | balrog | hid->datain.buffer, hid->datain.len); |
388 | 47e699dc | balrog | } |
389 | 47e699dc | balrog | |
390 | 47e699dc | balrog | static void bt_hid_interrupt_sdu(void *opaque, const uint8_t *data, int len) |
391 | 47e699dc | balrog | { |
392 | 47e699dc | balrog | struct bt_hid_device_s *hid = opaque;
|
393 | 47e699dc | balrog | |
394 | 47e699dc | balrog | if (len > BT_HID_MTU || len < 1) |
395 | 47e699dc | balrog | goto bad;
|
396 | 47e699dc | balrog | if ((data[0] & 3) != BT_DATA_OUTPUT) |
397 | 47e699dc | balrog | goto bad;
|
398 | 47e699dc | balrog | if ((data[0] >> 4) == BT_DATA) { |
399 | 47e699dc | balrog | if (hid->intr_state)
|
400 | 47e699dc | balrog | goto bad;
|
401 | 47e699dc | balrog | |
402 | 47e699dc | balrog | hid->data_type = BT_DATA_OUTPUT; |
403 | 47e699dc | balrog | hid->intrdataout.len = 0;
|
404 | 47e699dc | balrog | } else if ((data[0] >> 4) == BT_DATC) { |
405 | 47e699dc | balrog | if (!hid->intr_state)
|
406 | 47e699dc | balrog | goto bad;
|
407 | 47e699dc | balrog | } else
|
408 | 47e699dc | balrog | goto bad;
|
409 | 47e699dc | balrog | |
410 | 47e699dc | balrog | memcpy(hid->intrdataout.buffer + hid->intrdataout.len, data + 1, len - 1); |
411 | 47e699dc | balrog | hid->intrdataout.len += len - 1;
|
412 | 47e699dc | balrog | hid->intr_state = (len == BT_HID_MTU); |
413 | 47e699dc | balrog | if (!hid->intr_state) {
|
414 | 47e699dc | balrog | memcpy(hid->dataout.buffer, hid->intrdataout.buffer, |
415 | 47e699dc | balrog | hid->dataout.len = hid->intrdataout.len); |
416 | 47e699dc | balrog | bt_hid_out(hid); |
417 | 47e699dc | balrog | } |
418 | 47e699dc | balrog | |
419 | 47e699dc | balrog | return;
|
420 | 47e699dc | balrog | bad:
|
421 | 47e699dc | balrog | fprintf(stderr, "%s: bad transaction on Interrupt channel.\n",
|
422 | 47e699dc | balrog | __FUNCTION__); |
423 | 47e699dc | balrog | } |
424 | 47e699dc | balrog | |
425 | 47e699dc | balrog | /* "Virtual cable" plug/unplug event. */
|
426 | 47e699dc | balrog | static void bt_hid_connected_update(struct bt_hid_device_s *hid) |
427 | 47e699dc | balrog | { |
428 | 47e699dc | balrog | int prev = hid->connected;
|
429 | 47e699dc | balrog | |
430 | 47e699dc | balrog | hid->connected = hid->control && hid->interrupt; |
431 | 47e699dc | balrog | |
432 | 47e699dc | balrog | /* Stop page-/inquiry-scanning when a host is connected. */
|
433 | 47e699dc | balrog | hid->btdev.device.page_scan = !hid->connected; |
434 | 47e699dc | balrog | hid->btdev.device.inquiry_scan = !hid->connected; |
435 | 47e699dc | balrog | |
436 | 47e699dc | balrog | if (hid->connected && !prev) {
|
437 | fb8f4cee | Gerd Hoffmann | hid_reset(&hid->hid); |
438 | 47e699dc | balrog | hid->proto = BT_HID_PROTO_REPORT; |
439 | 47e699dc | balrog | } |
440 | 47e699dc | balrog | |
441 | 47e699dc | balrog | /* Should set HIDVirtualCable in SDP (possibly need to check that SDP
|
442 | 47e699dc | balrog | * isn't destroyed yet, in case we're being called from handle_destroy) */
|
443 | 47e699dc | balrog | } |
444 | 47e699dc | balrog | |
445 | 47e699dc | balrog | static void bt_hid_close_control(void *opaque) |
446 | 47e699dc | balrog | { |
447 | 47e699dc | balrog | struct bt_hid_device_s *hid = opaque;
|
448 | 47e699dc | balrog | |
449 | 511d2b14 | blueswir1 | hid->control = NULL;
|
450 | 47e699dc | balrog | bt_hid_connected_update(hid); |
451 | 47e699dc | balrog | } |
452 | 47e699dc | balrog | |
453 | 47e699dc | balrog | static void bt_hid_close_interrupt(void *opaque) |
454 | 47e699dc | balrog | { |
455 | 47e699dc | balrog | struct bt_hid_device_s *hid = opaque;
|
456 | 47e699dc | balrog | |
457 | 511d2b14 | blueswir1 | hid->interrupt = NULL;
|
458 | 47e699dc | balrog | bt_hid_connected_update(hid); |
459 | 47e699dc | balrog | } |
460 | 47e699dc | balrog | |
461 | 47e699dc | balrog | static int bt_hid_new_control_ch(struct bt_l2cap_device_s *dev, |
462 | 47e699dc | balrog | struct bt_l2cap_conn_params_s *params)
|
463 | 47e699dc | balrog | { |
464 | 47e699dc | balrog | struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev; |
465 | 47e699dc | balrog | |
466 | 47e699dc | balrog | if (hid->control)
|
467 | 47e699dc | balrog | return 1; |
468 | 47e699dc | balrog | |
469 | 47e699dc | balrog | hid->control = params; |
470 | 47e699dc | balrog | hid->control->opaque = hid; |
471 | 47e699dc | balrog | hid->control->close = bt_hid_close_control; |
472 | 47e699dc | balrog | hid->control->sdu_in = bt_hid_control_sdu; |
473 | 47e699dc | balrog | |
474 | 47e699dc | balrog | bt_hid_connected_update(hid); |
475 | 47e699dc | balrog | |
476 | 47e699dc | balrog | return 0; |
477 | 47e699dc | balrog | } |
478 | 47e699dc | balrog | |
479 | 47e699dc | balrog | static int bt_hid_new_interrupt_ch(struct bt_l2cap_device_s *dev, |
480 | 47e699dc | balrog | struct bt_l2cap_conn_params_s *params)
|
481 | 47e699dc | balrog | { |
482 | 47e699dc | balrog | struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev; |
483 | 47e699dc | balrog | |
484 | 47e699dc | balrog | if (hid->interrupt)
|
485 | 47e699dc | balrog | return 1; |
486 | 47e699dc | balrog | |
487 | 47e699dc | balrog | hid->interrupt = params; |
488 | 47e699dc | balrog | hid->interrupt->opaque = hid; |
489 | 47e699dc | balrog | hid->interrupt->close = bt_hid_close_interrupt; |
490 | 47e699dc | balrog | hid->interrupt->sdu_in = bt_hid_interrupt_sdu; |
491 | 47e699dc | balrog | |
492 | 47e699dc | balrog | bt_hid_connected_update(hid); |
493 | 47e699dc | balrog | |
494 | 47e699dc | balrog | return 0; |
495 | 47e699dc | balrog | } |
496 | 47e699dc | balrog | |
497 | 47e699dc | balrog | static void bt_hid_destroy(struct bt_device_s *dev) |
498 | 47e699dc | balrog | { |
499 | 47e699dc | balrog | struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev; |
500 | 47e699dc | balrog | |
501 | 47e699dc | balrog | if (hid->connected)
|
502 | 47e699dc | balrog | bt_hid_send_control(hid, BT_HC_VIRTUAL_CABLE_UNPLUG); |
503 | 47e699dc | balrog | bt_l2cap_device_done(&hid->btdev); |
504 | 47e699dc | balrog | |
505 | fb8f4cee | Gerd Hoffmann | hid_free(&hid->hid); |
506 | 47e699dc | balrog | |
507 | 7267c094 | Anthony Liguori | g_free(hid); |
508 | 47e699dc | balrog | } |
509 | 47e699dc | balrog | |
510 | 47e699dc | balrog | enum peripheral_minor_class {
|
511 | 47e699dc | balrog | class_other = 0 << 4, |
512 | 47e699dc | balrog | class_keyboard = 1 << 4, |
513 | 47e699dc | balrog | class_pointing = 2 << 4, |
514 | 47e699dc | balrog | class_combo = 3 << 4, |
515 | 47e699dc | balrog | }; |
516 | 47e699dc | balrog | |
517 | 47e699dc | balrog | static struct bt_device_s *bt_hid_init(struct bt_scatternet_s *net, |
518 | fb8f4cee | Gerd Hoffmann | enum peripheral_minor_class minor)
|
519 | 47e699dc | balrog | { |
520 | 7267c094 | Anthony Liguori | struct bt_hid_device_s *s = g_malloc0(sizeof(*s)); |
521 | 47e699dc | balrog | uint32_t class = |
522 | 47e699dc | balrog | /* Format type */
|
523 | 47e699dc | balrog | (0 << 0) | |
524 | 47e699dc | balrog | /* Device class */
|
525 | 47e699dc | balrog | (minor << 2) |
|
526 | 47e699dc | balrog | (5 << 8) | /* "Peripheral" */ |
527 | 47e699dc | balrog | /* Service classes */
|
528 | 47e699dc | balrog | (1 << 13) | /* Limited discoverable mode */ |
529 | 47e699dc | balrog | (1 << 19); /* Capturing device (?) */ |
530 | 47e699dc | balrog | |
531 | 47e699dc | balrog | bt_l2cap_device_init(&s->btdev, net); |
532 | 47e699dc | balrog | bt_l2cap_sdp_init(&s->btdev); |
533 | 47e699dc | balrog | bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_CTRL, |
534 | 47e699dc | balrog | BT_HID_MTU, bt_hid_new_control_ch); |
535 | 47e699dc | balrog | bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_INTR, |
536 | 47e699dc | balrog | BT_HID_MTU, bt_hid_new_interrupt_ch); |
537 | 47e699dc | balrog | |
538 | fb8f4cee | Gerd Hoffmann | hid_init(&s->hid, HID_KEYBOARD, bt_hid_datain); |
539 | fb8f4cee | Gerd Hoffmann | s->btdev.device.lmp_name = "BT Keyboard";
|
540 | 47e699dc | balrog | |
541 | 47e699dc | balrog | s->btdev.device.handle_destroy = bt_hid_destroy; |
542 | 47e699dc | balrog | |
543 | 47e699dc | balrog | s->btdev.device.class[0] = (class >> 0) & 0xff; |
544 | 47e699dc | balrog | s->btdev.device.class[1] = (class >> 8) & 0xff; |
545 | 47e699dc | balrog | s->btdev.device.class[2] = (class >> 16) & 0xff; |
546 | 47e699dc | balrog | |
547 | 47e699dc | balrog | return &s->btdev.device;
|
548 | 47e699dc | balrog | } |
549 | 47e699dc | balrog | |
550 | 47e699dc | balrog | struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net) |
551 | 47e699dc | balrog | { |
552 | fb8f4cee | Gerd Hoffmann | return bt_hid_init(net, class_keyboard);
|
553 | 47e699dc | balrog | } |