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