572 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			572 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  * QEMU Bluetooth HID Profile wrapper for USB HID.
 | |
|  *
 | |
|  * Copyright (C) 2007-2008 OpenMoko, Inc.
 | |
|  * Written by Andrzej Zaborowski <andrew@openedhand.com>
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License as
 | |
|  * published by the Free Software Foundation; either version 2 or
 | |
|  * (at your option) version 3 of the License.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along
 | |
|  * with this program; if not, if not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "qemu-common.h"
 | |
| #include "usb.h"
 | |
| #include "bt.h"
 | |
| 
 | |
| enum hid_transaction_req {
 | |
|     BT_HANDSHAKE			= 0x0,
 | |
|     BT_HID_CONTROL			= 0x1,
 | |
|     BT_GET_REPORT			= 0x4,
 | |
|     BT_SET_REPORT			= 0x5,
 | |
|     BT_GET_PROTOCOL			= 0x6,
 | |
|     BT_SET_PROTOCOL			= 0x7,
 | |
|     BT_GET_IDLE				= 0x8,
 | |
|     BT_SET_IDLE				= 0x9,
 | |
|     BT_DATA				= 0xa,
 | |
|     BT_DATC				= 0xb,
 | |
| };
 | |
| 
 | |
| enum hid_transaction_handshake {
 | |
|     BT_HS_SUCCESSFUL			= 0x0,
 | |
|     BT_HS_NOT_READY			= 0x1,
 | |
|     BT_HS_ERR_INVALID_REPORT_ID		= 0x2,
 | |
|     BT_HS_ERR_UNSUPPORTED_REQUEST	= 0x3,
 | |
|     BT_HS_ERR_INVALID_PARAMETER		= 0x4,
 | |
|     BT_HS_ERR_UNKNOWN			= 0xe,
 | |
|     BT_HS_ERR_FATAL			= 0xf,
 | |
| };
 | |
| 
 | |
| enum hid_transaction_control {
 | |
|     BT_HC_NOP				= 0x0,
 | |
|     BT_HC_HARD_RESET			= 0x1,
 | |
|     BT_HC_SOFT_RESET			= 0x2,
 | |
|     BT_HC_SUSPEND			= 0x3,
 | |
|     BT_HC_EXIT_SUSPEND			= 0x4,
 | |
|     BT_HC_VIRTUAL_CABLE_UNPLUG		= 0x5,
 | |
| };
 | |
| 
 | |
| enum hid_protocol {
 | |
|     BT_HID_PROTO_BOOT			= 0,
 | |
|     BT_HID_PROTO_REPORT			= 1,
 | |
| };
 | |
| 
 | |
| enum hid_boot_reportid {
 | |
|     BT_HID_BOOT_INVALID			= 0,
 | |
|     BT_HID_BOOT_KEYBOARD,
 | |
|     BT_HID_BOOT_MOUSE,
 | |
| };
 | |
| 
 | |
| enum hid_data_pkt {
 | |
|     BT_DATA_OTHER			= 0,
 | |
|     BT_DATA_INPUT,
 | |
|     BT_DATA_OUTPUT,
 | |
|     BT_DATA_FEATURE,
 | |
| };
 | |
| 
 | |
| #define BT_HID_MTU			48
 | |
| 
 | |
| /* HID interface requests */
 | |
| #define GET_REPORT			0xa101
 | |
| #define GET_IDLE			0xa102
 | |
| #define GET_PROTOCOL			0xa103
 | |
| #define SET_REPORT			0x2109
 | |
| #define SET_IDLE			0x210a
 | |
| #define SET_PROTOCOL			0x210b
 | |
| 
 | |
| struct bt_hid_device_s {
 | |
|     struct bt_l2cap_device_s btdev;
 | |
|     struct bt_l2cap_conn_params_s *control;
 | |
|     struct bt_l2cap_conn_params_s *interrupt;
 | |
|     USBDevice *usbdev;
 | |
| 
 | |
|     int proto;
 | |
|     int connected;
 | |
|     int data_type;
 | |
|     int intr_state;
 | |
|     struct {
 | |
|         int len;
 | |
|         uint8_t buffer[1024];
 | |
|     } dataother, datain, dataout, feature, intrdataout;
 | |
|     enum {
 | |
|         bt_state_ready,
 | |
|         bt_state_transaction,
 | |
|         bt_state_suspend,
 | |
|     } state;
 | |
| };
 | |
| 
 | |
| static void bt_hid_reset(struct bt_hid_device_s *s)
 | |
| {
 | |
|     struct bt_scatternet_s *net = s->btdev.device.net;
 | |
| 
 | |
|     /* Go as far as... */
 | |
|     bt_l2cap_device_done(&s->btdev);
 | |
|     bt_l2cap_device_init(&s->btdev, net);
 | |
| 
 | |
|     s->usbdev->info->handle_reset(s->usbdev);
 | |
|     s->proto = BT_HID_PROTO_REPORT;
 | |
|     s->state = bt_state_ready;
 | |
|     s->dataother.len = 0;
 | |
|     s->datain.len = 0;
 | |
|     s->dataout.len = 0;
 | |
|     s->feature.len = 0;
 | |
|     s->intrdataout.len = 0;
 | |
|     s->intr_state = 0;
 | |
| }
 | |
| 
 | |
| static int bt_hid_out(struct bt_hid_device_s *s)
 | |
| {
 | |
|     USBPacket p;
 | |
| 
 | |
|     if (s->data_type == BT_DATA_OUTPUT) {
 | |
|         p.pid = USB_TOKEN_OUT;
 | |
|         p.devep = 1;
 | |
|         p.data = s->dataout.buffer;
 | |
|         p.len = s->dataout.len;
 | |
|         s->dataout.len = s->usbdev->info->handle_data(s->usbdev, &p);
 | |
| 
 | |
|         return s->dataout.len;
 | |
|     }
 | |
| 
 | |
|     if (s->data_type == BT_DATA_FEATURE) {
 | |
|         /* XXX:
 | |
|          * does this send a USB_REQ_CLEAR_FEATURE/USB_REQ_SET_FEATURE
 | |
|          * or a SET_REPORT? */
 | |
|         p.devep = 0;
 | |
|     }
 | |
| 
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| static int bt_hid_in(struct bt_hid_device_s *s)
 | |
| {
 | |
|     USBPacket p;
 | |
| 
 | |
|     p.pid = USB_TOKEN_IN;
 | |
|     p.devep = 1;
 | |
|     p.data = s->datain.buffer;
 | |
|     p.len = sizeof(s->datain.buffer);
 | |
|     s->datain.len = s->usbdev->info->handle_data(s->usbdev, &p);
 | |
| 
 | |
|     return s->datain.len;
 | |
| }
 | |
| 
 | |
| static void bt_hid_send_handshake(struct bt_hid_device_s *s, int result)
 | |
| {
 | |
|     *s->control->sdu_out(s->control, 1) =
 | |
|             (BT_HANDSHAKE << 4) | result;
 | |
|     s->control->sdu_submit(s->control);
 | |
| }
 | |
| 
 | |
| static void bt_hid_send_control(struct bt_hid_device_s *s, int operation)
 | |
| {
 | |
|     *s->control->sdu_out(s->control, 1) =
 | |
|             (BT_HID_CONTROL << 4) | operation;
 | |
|     s->control->sdu_submit(s->control);
 | |
| }
 | |
| 
 | |
| static void bt_hid_disconnect(struct bt_hid_device_s *s)
 | |
| {
 | |
|     /* Disconnect s->control and s->interrupt */
 | |
| }
 | |
| 
 | |
| static void bt_hid_send_data(struct bt_l2cap_conn_params_s *ch, int type,
 | |
|                 const uint8_t *data, int len)
 | |
| {
 | |
|     uint8_t *pkt, hdr = (BT_DATA << 4) | type;
 | |
|     int plen;
 | |
| 
 | |
|     do {
 | |
|         plen = MIN(len, ch->remote_mtu - 1);
 | |
|         pkt = ch->sdu_out(ch, plen + 1);
 | |
| 
 | |
|         pkt[0] = hdr;
 | |
|         if (plen)
 | |
|             memcpy(pkt + 1, data, plen);
 | |
|         ch->sdu_submit(ch);
 | |
| 
 | |
|         len -= plen;
 | |
|         data += plen;
 | |
|         hdr = (BT_DATC << 4) | type;
 | |
|     } while (plen == ch->remote_mtu - 1);
 | |
| }
 | |
| 
 | |
| static void bt_hid_control_transaction(struct bt_hid_device_s *s,
 | |
|                 const uint8_t *data, int len)
 | |
| {
 | |
|     uint8_t type, parameter;
 | |
|     int rlen, ret = -1;
 | |
|     if (len < 1)
 | |
|         return;
 | |
| 
 | |
|     type = data[0] >> 4;
 | |
|     parameter = data[0] & 0xf;
 | |
| 
 | |
|     switch (type) {
 | |
|     case BT_HANDSHAKE:
 | |
|     case BT_DATA:
 | |
|         switch (parameter) {
 | |
|         default:
 | |
|             /* These are not expected to be sent this direction.  */
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case BT_HID_CONTROL:
 | |
|         if (len != 1 || (parameter != BT_HC_VIRTUAL_CABLE_UNPLUG &&
 | |
|                                 s->state == bt_state_transaction)) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         switch (parameter) {
 | |
|         case BT_HC_NOP:
 | |
|             break;
 | |
|         case BT_HC_HARD_RESET:
 | |
|         case BT_HC_SOFT_RESET:
 | |
|             bt_hid_reset(s);
 | |
|             break;
 | |
|         case BT_HC_SUSPEND:
 | |
|             if (s->state == bt_state_ready)
 | |
|                 s->state = bt_state_suspend;
 | |
|             else
 | |
|                 ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         case BT_HC_EXIT_SUSPEND:
 | |
|             if (s->state == bt_state_suspend)
 | |
|                 s->state = bt_state_ready;
 | |
|             else
 | |
|                 ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         case BT_HC_VIRTUAL_CABLE_UNPLUG:
 | |
|             bt_hid_disconnect(s);
 | |
|             break;
 | |
|         default:
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case BT_GET_REPORT:
 | |
|         /* No ReportIDs declared.  */
 | |
|         if (((parameter & 8) && len != 3) ||
 | |
|                         (!(parameter & 8) && len != 1) ||
 | |
|                         s->state != bt_state_ready) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         if (parameter & 8)
 | |
|             rlen = data[2] | (data[3] << 8);
 | |
|         else
 | |
|             rlen = INT_MAX;
 | |
|         switch (parameter & 3) {
 | |
|         case BT_DATA_OTHER:
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         case BT_DATA_INPUT:
 | |
|             /* Here we can as well poll s->usbdev */
 | |
|             bt_hid_send_data(s->control, BT_DATA_INPUT,
 | |
|                             s->datain.buffer, MIN(rlen, s->datain.len));
 | |
|             break;
 | |
|         case BT_DATA_OUTPUT:
 | |
|             bt_hid_send_data(s->control, BT_DATA_OUTPUT,
 | |
|                             s->dataout.buffer, MIN(rlen, s->dataout.len));
 | |
|             break;
 | |
|         case BT_DATA_FEATURE:
 | |
|             bt_hid_send_data(s->control, BT_DATA_FEATURE,
 | |
|                             s->feature.buffer, MIN(rlen, s->feature.len));
 | |
|             break;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case BT_SET_REPORT:
 | |
|         if (len < 2 || len > BT_HID_MTU || s->state != bt_state_ready ||
 | |
|                         (parameter & 3) == BT_DATA_OTHER ||
 | |
|                         (parameter & 3) == BT_DATA_INPUT) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         s->data_type = parameter & 3;
 | |
|         if (s->data_type == BT_DATA_OUTPUT) {
 | |
|             s->dataout.len = len - 1;
 | |
|             memcpy(s->dataout.buffer, data + 1, s->dataout.len);
 | |
|         } else {
 | |
|             s->feature.len = len - 1;
 | |
|             memcpy(s->feature.buffer, data + 1, s->feature.len);
 | |
|         }
 | |
|         if (len == BT_HID_MTU)
 | |
|             s->state = bt_state_transaction;
 | |
|         else
 | |
|             bt_hid_out(s);
 | |
|         break;
 | |
| 
 | |
|     case BT_GET_PROTOCOL:
 | |
|         if (len != 1 || s->state == bt_state_transaction) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         *s->control->sdu_out(s->control, 1) = s->proto;
 | |
|         s->control->sdu_submit(s->control);
 | |
|         break;
 | |
| 
 | |
|     case BT_SET_PROTOCOL:
 | |
|         if (len != 1 || s->state == bt_state_transaction ||
 | |
|                         (parameter != BT_HID_PROTO_BOOT &&
 | |
|                          parameter != BT_HID_PROTO_REPORT)) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         s->proto = parameter;
 | |
|         s->usbdev->info->handle_control(s->usbdev, SET_PROTOCOL, s->proto, 0, 0,
 | |
|                                         NULL);
 | |
|         ret = BT_HS_SUCCESSFUL;
 | |
|         break;
 | |
| 
 | |
|     case BT_GET_IDLE:
 | |
|         if (len != 1 || s->state == bt_state_transaction) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         s->usbdev->info->handle_control(s->usbdev, GET_IDLE, 0, 0, 1,
 | |
|                         s->control->sdu_out(s->control, 1));
 | |
|         s->control->sdu_submit(s->control);
 | |
|         break;
 | |
| 
 | |
|     case BT_SET_IDLE:
 | |
|         if (len != 2 || s->state == bt_state_transaction) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         /* We don't need to know about the Idle Rate here really,
 | |
|          * so just pass it on to the device.  */
 | |
|         ret = s->usbdev->info->handle_control(s->usbdev,
 | |
|                         SET_IDLE, data[1], 0, 0, NULL) ?
 | |
|                 BT_HS_SUCCESSFUL : BT_HS_ERR_INVALID_PARAMETER;
 | |
|         /* XXX: Does this generate a handshake? */
 | |
|         break;
 | |
| 
 | |
|     case BT_DATC:
 | |
|         if (len > BT_HID_MTU || s->state != bt_state_transaction) {
 | |
|             ret = BT_HS_ERR_INVALID_PARAMETER;
 | |
|             break;
 | |
|         }
 | |
|         if (s->data_type == BT_DATA_OUTPUT) {
 | |
|             memcpy(s->dataout.buffer + s->dataout.len, data + 1, len - 1);
 | |
|             s->dataout.len += len - 1;
 | |
|         } else {
 | |
|             memcpy(s->feature.buffer + s->feature.len, data + 1, len - 1);
 | |
|             s->feature.len += len - 1;
 | |
|         }
 | |
|         if (len < BT_HID_MTU) {
 | |
|             bt_hid_out(s);
 | |
|             s->state = bt_state_ready;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         ret = BT_HS_ERR_UNSUPPORTED_REQUEST;
 | |
|     }
 | |
| 
 | |
|     if (ret != -1)
 | |
|         bt_hid_send_handshake(s, ret);
 | |
| }
 | |
| 
 | |
| static void bt_hid_control_sdu(void *opaque, const uint8_t *data, int len)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = opaque;
 | |
| 
 | |
|     bt_hid_control_transaction(hid, data, len);
 | |
| }
 | |
| 
 | |
| static void bt_hid_datain(void *opaque)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = opaque;
 | |
| 
 | |
|     /* If suspended, wake-up and send a wake-up event first.  We might
 | |
|      * want to also inspect the input report and ignore event like
 | |
|      * mouse movements until a button event occurs.  */
 | |
|     if (hid->state == bt_state_suspend) {
 | |
|         hid->state = bt_state_ready;
 | |
|     }
 | |
| 
 | |
|     if (bt_hid_in(hid) > 0)
 | |
|         /* TODO: when in boot-mode precede any Input reports with the ReportID
 | |
|          * byte, here and in GetReport/SetReport on the Control channel.  */
 | |
|         bt_hid_send_data(hid->interrupt, BT_DATA_INPUT,
 | |
|                         hid->datain.buffer, hid->datain.len);
 | |
| }
 | |
| 
 | |
| static void bt_hid_interrupt_sdu(void *opaque, const uint8_t *data, int len)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = opaque;
 | |
| 
 | |
|     if (len > BT_HID_MTU || len < 1)
 | |
|         goto bad;
 | |
|     if ((data[0] & 3) != BT_DATA_OUTPUT)
 | |
|         goto bad;
 | |
|     if ((data[0] >> 4) == BT_DATA) {
 | |
|         if (hid->intr_state)
 | |
|             goto bad;
 | |
| 
 | |
|         hid->data_type = BT_DATA_OUTPUT;
 | |
|         hid->intrdataout.len = 0;
 | |
|     } else if ((data[0] >> 4) == BT_DATC) {
 | |
|         if (!hid->intr_state)
 | |
|             goto bad;
 | |
|     } else
 | |
|         goto bad;
 | |
| 
 | |
|     memcpy(hid->intrdataout.buffer + hid->intrdataout.len, data + 1, len - 1);
 | |
|     hid->intrdataout.len += len - 1;
 | |
|     hid->intr_state = (len == BT_HID_MTU);
 | |
|     if (!hid->intr_state) {
 | |
|         memcpy(hid->dataout.buffer, hid->intrdataout.buffer,
 | |
|                         hid->dataout.len = hid->intrdataout.len);
 | |
|         bt_hid_out(hid);
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| bad:
 | |
|     fprintf(stderr, "%s: bad transaction on Interrupt channel.\n",
 | |
|                     __FUNCTION__);
 | |
| }
 | |
| 
 | |
| /* "Virtual cable" plug/unplug event.  */
 | |
| static void bt_hid_connected_update(struct bt_hid_device_s *hid)
 | |
| {
 | |
|     int prev = hid->connected;
 | |
| 
 | |
|     hid->connected = hid->control && hid->interrupt;
 | |
| 
 | |
|     /* Stop page-/inquiry-scanning when a host is connected.  */
 | |
|     hid->btdev.device.page_scan = !hid->connected;
 | |
|     hid->btdev.device.inquiry_scan = !hid->connected;
 | |
| 
 | |
|     if (hid->connected && !prev) {
 | |
|         hid->usbdev->info->handle_reset(hid->usbdev);
 | |
|         hid->proto = BT_HID_PROTO_REPORT;
 | |
|     }
 | |
| 
 | |
|     /* Should set HIDVirtualCable in SDP (possibly need to check that SDP
 | |
|      * isn't destroyed yet, in case we're being called from handle_destroy) */
 | |
| }
 | |
| 
 | |
| static void bt_hid_close_control(void *opaque)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = opaque;
 | |
| 
 | |
|     hid->control = NULL;
 | |
|     bt_hid_connected_update(hid);
 | |
| }
 | |
| 
 | |
| static void bt_hid_close_interrupt(void *opaque)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = opaque;
 | |
| 
 | |
|     hid->interrupt = NULL;
 | |
|     bt_hid_connected_update(hid);
 | |
| }
 | |
| 
 | |
| static int bt_hid_new_control_ch(struct bt_l2cap_device_s *dev,
 | |
|                 struct bt_l2cap_conn_params_s *params)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
 | |
| 
 | |
|     if (hid->control)
 | |
|         return 1;
 | |
| 
 | |
|     hid->control = params;
 | |
|     hid->control->opaque = hid;
 | |
|     hid->control->close = bt_hid_close_control;
 | |
|     hid->control->sdu_in = bt_hid_control_sdu;
 | |
| 
 | |
|     bt_hid_connected_update(hid);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int bt_hid_new_interrupt_ch(struct bt_l2cap_device_s *dev,
 | |
|                 struct bt_l2cap_conn_params_s *params)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
 | |
| 
 | |
|     if (hid->interrupt)
 | |
|         return 1;
 | |
| 
 | |
|     hid->interrupt = params;
 | |
|     hid->interrupt->opaque = hid;
 | |
|     hid->interrupt->close = bt_hid_close_interrupt;
 | |
|     hid->interrupt->sdu_in = bt_hid_interrupt_sdu;
 | |
| 
 | |
|     bt_hid_connected_update(hid);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void bt_hid_destroy(struct bt_device_s *dev)
 | |
| {
 | |
|     struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
 | |
| 
 | |
|     if (hid->connected)
 | |
|         bt_hid_send_control(hid, BT_HC_VIRTUAL_CABLE_UNPLUG);
 | |
|     bt_l2cap_device_done(&hid->btdev);
 | |
| 
 | |
|     hid->usbdev->info->handle_destroy(hid->usbdev);
 | |
| 
 | |
|     qemu_free(hid);
 | |
| }
 | |
| 
 | |
| enum peripheral_minor_class {
 | |
|     class_other		= 0 << 4,
 | |
|     class_keyboard	= 1 << 4,
 | |
|     class_pointing	= 2 << 4,
 | |
|     class_combo		= 3 << 4,
 | |
| };
 | |
| 
 | |
| static struct bt_device_s *bt_hid_init(struct bt_scatternet_s *net,
 | |
|                 USBDevice *dev, enum peripheral_minor_class minor)
 | |
| {
 | |
|     struct bt_hid_device_s *s = qemu_mallocz(sizeof(*s));
 | |
|     uint32_t class =
 | |
|             /* Format type */
 | |
|             (0 << 0) |
 | |
|             /* Device class */
 | |
|             (minor << 2) |
 | |
|             (5 << 8) |  /* "Peripheral" */
 | |
|             /* Service classes */
 | |
|             (1 << 13) | /* Limited discoverable mode */
 | |
|             (1 << 19);  /* Capturing device (?) */
 | |
| 
 | |
|     bt_l2cap_device_init(&s->btdev, net);
 | |
|     bt_l2cap_sdp_init(&s->btdev);
 | |
|     bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_CTRL,
 | |
|                     BT_HID_MTU, bt_hid_new_control_ch);
 | |
|     bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_INTR,
 | |
|                     BT_HID_MTU, bt_hid_new_interrupt_ch);
 | |
| 
 | |
|     s->usbdev = dev;
 | |
|     s->btdev.device.lmp_name = s->usbdev->product_desc;
 | |
|     usb_hid_datain_cb(s->usbdev, s, bt_hid_datain);
 | |
| 
 | |
|     s->btdev.device.handle_destroy = bt_hid_destroy;
 | |
| 
 | |
|     s->btdev.device.class[0] = (class >>  0) & 0xff;
 | |
|     s->btdev.device.class[1] = (class >>  8) & 0xff;
 | |
|     s->btdev.device.class[2] = (class >> 16) & 0xff;
 | |
| 
 | |
|     return &s->btdev.device;
 | |
| }
 | |
| 
 | |
| struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net)
 | |
| {
 | |
|     USBDevice *dev = usb_create_simple(NULL /* FIXME */, "usb-kbd");
 | |
|     return bt_hid_init(net, dev, class_keyboard);
 | |
| }
 |