Name of daemon implementation will match Android HAL name. This will make code navigation easier as daemon part and HAL library implementation will be in foo.c and hal-foo.c respectively. --- android/Android.mk | 2 +- android/Makefile.am | 2 +- android/hid.c | 1095 --------------------------------------------------- android/hid.h | 27 -- android/hidhost.c | 1095 +++++++++++++++++++++++++++++++++++++++++++++++++++ android/hidhost.h | 27 ++ android/main.c | 2 +- 7 files changed, 1125 insertions(+), 1125 deletions(-) delete mode 100644 android/hid.c delete mode 100644 android/hid.h create mode 100644 android/hidhost.c create mode 100644 android/hidhost.h diff --git a/android/Android.mk b/android/Android.mk index f9bf070..51037a7 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -22,7 +22,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ main.c \ adapter.c \ - hid.c \ + hidhost.c \ socket.c \ ipc.c ipc.h \ a2dp.c \ diff --git a/android/Makefile.am b/android/Makefile.am index 6790f24..073edc8 100644 --- a/android/Makefile.am +++ b/android/Makefile.am @@ -17,7 +17,7 @@ android_bluetoothd_SOURCES = android/main.c \ src/shared/util.h src/shared/util.c \ src/shared/mgmt.h src/shared/mgmt.c \ android/adapter.h android/adapter.c \ - android/hid.h android/hid.c \ + android/hidhost.h android/hidhost.c \ android/ipc.h android/ipc.c \ android/a2dp.h android/a2dp.c \ android/socket.h android/socket.c \ diff --git a/android/hid.c b/android/hid.c deleted file mode 100644 index 1089301..0000000 --- a/android/hid.c +++ /dev/null @@ -1,1095 +0,0 @@ -/* - * - * BlueZ - Bluetooth protocol stack for Linux - * - * Copyright (C) 2013 Intel Corporation. All rights reserved. - * - * - * 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 of the License, or - * (at your option) any later version. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdint.h> -#include <stdbool.h> -#include <errno.h> -#include <unistd.h> -#include <fcntl.h> - -#include <glib.h> - -#include "btio/btio.h" -#include "lib/bluetooth.h" -#include "lib/sdp.h" -#include "lib/sdp_lib.h" -#include "lib/uuid.h" -#include "src/shared/mgmt.h" -#include "src/sdp-client.h" -#include "src/glib-helper.h" -#include "profiles/input/uhid_copy.h" - -#include "log.h" -#include "hal-msg.h" -#include "ipc.h" -#include "hid.h" -#include "adapter.h" -#include "utils.h" - -#define L2CAP_PSM_HIDP_CTRL 0x11 -#define L2CAP_PSM_HIDP_INTR 0x13 -#define UHID_DEVICE_FILE "/dev/uhid" - -/* HID message types */ -#define HID_MSG_GET_REPORT 0x40 -#define HID_MSG_SET_REPORT 0x50 -#define HID_MSG_GET_PROTOCOL 0x60 -#define HID_MSG_SET_PROTOCOL 0x70 -#define HID_MSG_DATA 0xa0 - -/* HID data types */ -#define HID_DATA_TYPE_INPUT 0x01 -#define HID_DATA_TYPE_OUTPUT 0x02 -#define HID_DATA_TYPE_FEATURE 0x03 - -/* HID protocol header parameters */ -#define HID_PROTO_BOOT 0x00 -#define HID_PROTO_REPORT 0x01 - -/* HID GET REPORT Size Field */ -#define HID_GET_REPORT_SIZE_FIELD 0x08 - -static int notification_sk = -1; -static GIOChannel *ctrl_io = NULL; -static GIOChannel *intr_io = NULL; -static GSList *devices = NULL; - -struct hid_device { - bdaddr_t dst; - uint8_t state; - uint8_t subclass; - uint16_t vendor; - uint16_t product; - uint16_t version; - uint8_t country; - int rd_size; - void *rd_data; - uint8_t boot_dev; - GIOChannel *ctrl_io; - GIOChannel *intr_io; - guint ctrl_watch; - guint intr_watch; - int uhid_fd; - guint uhid_watch_id; - uint8_t last_hid_msg; -}; - -static int device_cmp(gconstpointer s, gconstpointer user_data) -{ - const struct hid_device *dev = s; - const bdaddr_t *dst = user_data; - - return bacmp(&dev->dst, dst); -} - -static void uhid_destroy(int fd) -{ - struct uhid_event ev; - - /* destroy uHID device */ - memset(&ev, 0, sizeof(ev)); - ev.type = UHID_DESTROY; - - if (write(fd, &ev, sizeof(ev)) < 0) - error("Failed to destroy uHID device: %s (%d)", - strerror(errno), errno); - - close(fd); -} - -static void hid_device_free(struct hid_device *dev) -{ - if (dev->ctrl_watch > 0) - g_source_remove(dev->ctrl_watch); - - if (dev->intr_watch > 0) - g_source_remove(dev->intr_watch); - - if (dev->intr_io) - g_io_channel_unref(dev->intr_io); - - if (dev->ctrl_io) - g_io_channel_unref(dev->ctrl_io); - - if (dev->uhid_watch_id) { - g_source_remove(dev->uhid_watch_id); - dev->uhid_watch_id = 0; - } - - if (dev->uhid_fd > 0) - uhid_destroy(dev->uhid_fd); - - g_free(dev->rd_data); - - devices = g_slist_remove(devices, dev); - g_free(dev); -} - -static void handle_uhid_event(struct hid_device *dev, struct uhid_event *ev) -{ - DBG("UHID_OUTPUT UHID_FEATURE unsupported"); -} - -static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond, - gpointer user_data) -{ - struct hid_device *dev = user_data; - struct uhid_event ev; - ssize_t bread; - int fd; - - DBG(""); - - if (cond & (G_IO_ERR | G_IO_NVAL)) - goto failed; - - fd = g_io_channel_unix_get_fd(io); - memset(&ev, 0, sizeof(ev)); - - bread = read(fd, &ev, sizeof(ev)); - if (bread < 0) { - DBG("read: %s (%d)", strerror(errno), errno); - goto failed; - } - - DBG("uHID event type %d received", ev.type); - - switch (ev.type) { - case UHID_START: - case UHID_STOP: - /* These are called to start and stop the underlying hardware. - * We open the channels before creating the device so the - * hardware is always ready. No need to handle these. - * The kernel never destroys a device itself! Only an explicit - * UHID_DESTROY request can remove a device. */ - - break; - case UHID_OPEN: - case UHID_CLOSE: - /* OPEN/CLOSE are sent whenever user-space opens any interface - * provided by the kernel HID device. Whenever the open-count - * is non-zero we must be ready for I/O. As long as it is zero, - * we can decide to drop all I/O and put the device - * asleep This is optional, though. */ - break; - case UHID_OUTPUT: - case UHID_FEATURE: - handle_uhid_event(dev, &ev); - break; - case UHID_OUTPUT_EV: - /* This is only sent by kernels prior to linux-3.11. It - * requires us to parse HID-descriptors in user-space to - * properly handle it. This is redundant as the kernel - * does it already. That's why newer kernels assemble - * the output-reports and send it to us via UHID_OUTPUT. */ - DBG("UHID_OUTPUT_EV unsupported"); - break; - default: - warn("unexpected uHID event"); - } - - return TRUE; - -failed: - dev->uhid_watch_id = 0; - return FALSE; -} - -static gboolean intr_io_watch_cb(GIOChannel *chan, gpointer data) -{ - struct hid_device *dev = data; - uint8_t buf[UHID_DATA_MAX]; - struct uhid_event ev; - int fd, bread; - - /* Wait uHID if not ready */ - if (dev->uhid_fd < 0) - return TRUE; - - fd = g_io_channel_unix_get_fd(chan); - bread = read(fd, buf, sizeof(buf)); - if (bread < 0) { - error("read: %s(%d)", strerror(errno), -errno); - return TRUE; - } - - /* Discard non-data packets */ - if (bread == 0 || buf[0] != (HID_MSG_DATA | HID_DATA_TYPE_INPUT)) - return TRUE; - - /* send data to uHID device skipping HIDP header byte */ - memset(&ev, 0, sizeof(ev)); - ev.type = UHID_INPUT; - ev.u.input.size = bread - 1; - memcpy(ev.u.input.data, &buf[1], ev.u.input.size); - - if (write(dev->uhid_fd, &ev, sizeof(ev)) < 0) - DBG("write: %s (%d)", strerror(errno), errno); - - return TRUE; -} - -static void bt_hid_notify_state(struct hid_device *dev, uint8_t state) -{ - struct hal_ev_hidhost_conn_state ev; - char address[18]; - - if (dev->state == state) - return; - - dev->state = state; - - ba2str(&dev->dst, address); - DBG("device %s state %u", address, state); - - bdaddr2android(&dev->dst, ev.bdaddr); - ev.state = state; - - ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, - HAL_EV_HIDHOST_CONN_STATE, sizeof(ev), &ev, -1); -} - -static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, - gpointer data) -{ - struct hid_device *dev = data; - - if (cond & G_IO_IN) - return intr_io_watch_cb(chan, data); - - /* Checking for ctrl_watch avoids a double g_io_channel_shutdown since - * it's likely that ctrl_watch_cb has been queued for dispatching in - * this mainloop iteration */ - if ((cond & (G_IO_HUP | G_IO_ERR)) && dev->ctrl_watch) - g_io_channel_shutdown(chan, TRUE, NULL); - - dev->intr_watch = 0; - - if (dev->intr_io) { - g_io_channel_unref(dev->intr_io); - dev->intr_io = NULL; - } - - /* Close control channel */ - if (dev->ctrl_io && !(cond & G_IO_NVAL)) - g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); - - return FALSE; -} - -static void bt_hid_notify_proto_mode(struct hid_device *dev, uint8_t *buf, - int len) -{ - struct hal_ev_hidhost_proto_mode ev; - char address[18]; - - ba2str(&dev->dst, address); - DBG("device %s", address); - - memset(&ev, 0, sizeof(ev)); - bdaddr2android(&dev->dst, ev.bdaddr); - - if (buf[0] == HID_MSG_DATA) { - ev.status = HAL_HIDHOST_STATUS_OK; - if (buf[1] == HID_PROTO_REPORT) - ev.mode = HAL_HIDHOST_REPORT_PROTOCOL; - else if (buf[1] == HID_PROTO_BOOT) - ev.mode = HAL_HIDHOST_BOOT_PROTOCOL; - else - ev.mode = HAL_HIDHOST_UNSUPPORTED_PROTOCOL; - - } else { - ev.status = buf[0]; - ev.mode = HAL_HIDHOST_UNSUPPORTED_PROTOCOL; - } - - ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, - HAL_EV_HIDHOST_PROTO_MODE, sizeof(ev), &ev, -1); -} - -static void bt_hid_notify_get_report(struct hid_device *dev, uint8_t *buf, - int len) -{ - struct hal_ev_hidhost_get_report *ev; - int ev_len; - char address[18]; - - ba2str(&dev->dst, address); - DBG("device %s", address); - - ev_len = sizeof(*ev) + sizeof(struct hal_ev_hidhost_get_report) + 1; - - if (!((buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_INPUT)) || - (buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_OUTPUT)) || - (buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_FEATURE)))) { - ev = g_malloc0(ev_len); - ev->status = buf[0]; - bdaddr2android(&dev->dst, ev->bdaddr); - goto send; - } - - /* Report porotocol mode reply contains id after hdr, in boot - * protocol mode id doesn't exist */ - ev_len += (dev->boot_dev) ? (len - 1) : (len - 2); - ev = g_malloc0(ev_len); - ev->status = HAL_HIDHOST_STATUS_OK; - bdaddr2android(&dev->dst, ev->bdaddr); - - /* Report porotocol mode reply contains id after hdr, in boot - * protocol mode id doesn't exist */ - if (dev->boot_dev) { - ev->len = len - 1; - memcpy(ev->data, buf + 1, ev->len); - } else { - ev->len = len - 2; - memcpy(ev->data, buf + 2, ev->len); - } - -send: - ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, - HAL_EV_HIDHOST_GET_REPORT, ev_len, ev, -1); - g_free(ev); -} - -static gboolean ctrl_io_watch_cb(GIOChannel *chan, gpointer data) -{ - struct hid_device *dev = data; - int fd, bread; - uint8_t buf[UHID_DATA_MAX]; - - DBG(""); - - fd = g_io_channel_unix_get_fd(chan); - bread = read(fd, buf, sizeof(buf)); - if (bread < 0) { - error("read: %s(%d)", strerror(errno), -errno); - return TRUE; - } - - switch (dev->last_hid_msg) { - case HID_MSG_GET_PROTOCOL: - case HID_MSG_SET_PROTOCOL: - bt_hid_notify_proto_mode(dev, buf, bread); - break; - case HID_MSG_GET_REPORT: - bt_hid_notify_get_report(dev, buf, bread); - break; - } - - /* reset msg type request */ - dev->last_hid_msg = 0; - - return TRUE; -} - -static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, - gpointer data) -{ - struct hid_device *dev = data; - char address[18]; - - if (cond & G_IO_IN) - return ctrl_io_watch_cb(chan, data); - - ba2str(&dev->dst, address); - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); - - /* Checking for intr_watch avoids a double g_io_channel_shutdown since - * it's likely that intr_watch_cb has been queued for dispatching in - * this mainloop iteration */ - if ((cond & (G_IO_HUP | G_IO_ERR)) && dev->intr_watch) - g_io_channel_shutdown(chan, TRUE, NULL); - - if (dev->intr_io && !(cond & G_IO_NVAL)) - g_io_channel_shutdown(dev->intr_io, TRUE, NULL); - - hid_device_free(dev); - - return FALSE; -} - -static void bt_hid_set_info(struct hid_device *dev) -{ - struct hal_ev_hidhost_info ev; - - DBG(""); - - bdaddr2android(&dev->dst, ev.bdaddr); - ev.attr = 0; /* TODO: Check what is this field */ - ev.subclass = dev->subclass; - ev.app_id = 0; /* TODO: Check what is this field */ - ev.vendor = dev->vendor; - ev.product = dev->product; - ev.version = dev->version; - ev.country = dev->country; - ev.descr_len = dev->rd_size; - memset(ev.descr, 0, sizeof(ev.descr)); - memcpy(ev.descr, dev->rd_data, ev.descr_len); - - ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, HAL_EV_HIDHOST_INFO, - sizeof(ev), &ev, -1); -} - -static int uhid_create(struct hid_device *dev) -{ - GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL; - GIOChannel *io; - struct uhid_event ev; - - dev->uhid_fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC); - if (dev->uhid_fd < 0) { - error("Failed to open uHID device: %s", strerror(errno)); - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_NO_HID); - return -errno; - } - - memset(&ev, 0, sizeof(ev)); - ev.type = UHID_CREATE; - strcpy((char *) ev.u.create.name, "bluez-input-device"); - ev.u.create.bus = BUS_BLUETOOTH; - ev.u.create.vendor = dev->vendor; - ev.u.create.product = dev->product; - ev.u.create.version = dev->version; - ev.u.create.country = dev->country; - ev.u.create.rd_size = dev->rd_size; - ev.u.create.rd_data = dev->rd_data; - - if (write(dev->uhid_fd, &ev, sizeof(ev)) < 0) { - error("Failed to create uHID device: %s", strerror(errno)); - close(dev->uhid_fd); - dev->uhid_fd = -1; - return -errno; - } - - io = g_io_channel_unix_new(dev->uhid_fd); - g_io_channel_set_encoding(io, NULL, NULL); - dev->uhid_watch_id = g_io_add_watch(io, cond, uhid_event_cb, dev); - g_io_channel_unref(io); - - bt_hid_set_info(dev); - - return 0; -} - -static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, - gpointer user_data) -{ - struct hid_device *dev = user_data; - - DBG(""); - - if (conn_err) - goto failed; - - if (uhid_create(dev) < 0) - goto failed; - - dev->intr_watch = g_io_add_watch(dev->intr_io, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - intr_watch_cb, dev); - - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); - - return; - -failed: - /* So we guarantee the interrupt channel is closed before the - * control channel (if we only do unref GLib will close it only - * after returning control to the mainloop */ - if (!conn_err) - g_io_channel_shutdown(dev->intr_io, FALSE, NULL); - - g_io_channel_unref(dev->intr_io); - dev->intr_io = NULL; - - if (dev->ctrl_io) { - g_io_channel_unref(dev->ctrl_io); - dev->ctrl_io = NULL; - } -} - -static void control_connect_cb(GIOChannel *chan, GError *conn_err, - gpointer user_data) -{ - struct hid_device *dev = user_data; - GError *err = NULL; - const bdaddr_t *src = bt_adapter_get_address(); - - DBG(""); - - if (conn_err) { - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); - error("%s", conn_err->message); - goto failed; - } - - /* Connect to the HID interrupt channel */ - dev->intr_io = bt_io_connect(interrupt_connect_cb, dev, NULL, &err, - BT_IO_OPT_SOURCE_BDADDR, src, - BT_IO_OPT_DEST_BDADDR, &dev->dst, - BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, - BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, - BT_IO_OPT_INVALID); - if (!dev->intr_io) { - error("%s", err->message); - g_error_free(err); - goto failed; - } - - dev->ctrl_watch = g_io_add_watch(dev->ctrl_io, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - ctrl_watch_cb, dev); - - return; - -failed: - hid_device_free(dev); -} - -static void hid_sdp_search_cb(sdp_list_t *recs, int err, gpointer data) -{ - struct hid_device *dev = data; - sdp_list_t *list; - GError *gerr = NULL; - const bdaddr_t *src = bt_adapter_get_address(); - - DBG(""); - - if (err < 0) { - error("Unable to get SDP record: %s", strerror(-err)); - goto fail; - } - - if (!recs || !recs->data) { - error("No SDP records found"); - goto fail; - } - - for (list = recs; list != NULL; list = list->next) { - sdp_record_t *rec = list->data; - sdp_data_t *data; - - data = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); - if (data) - dev->vendor = data->val.uint16; - - data = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); - if (data) - dev->product = data->val.uint16; - - data = sdp_data_get(rec, SDP_ATTR_VERSION); - if (data) - dev->version = data->val.uint16; - - data = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE); - if (data) - dev->country = data->val.uint8; - - data = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS); - if (data) - dev->subclass = data->val.uint8; - - data = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE); - if (data) - dev->boot_dev = data->val.uint8; - - data = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); - if (data) { - if (!SDP_IS_SEQ(data->dtd)) - goto fail; - - /* First HIDDescriptor */ - data = data->val.dataseq; - if (!SDP_IS_SEQ(data->dtd)) - goto fail; - - /* ClassDescriptorType */ - data = data->val.dataseq; - if (data->dtd != SDP_UINT8) - goto fail; - - /* ClassDescriptorData */ - data = data->next; - if (!data || !SDP_IS_TEXT_STR(data->dtd)) - goto fail; - - dev->rd_size = data->unitSize; - dev->rd_data = g_memdup(data->val.str, data->unitSize); - } - } - - if (dev->ctrl_io) { - if (uhid_create(dev) < 0) - goto fail; - return; - } - - dev->ctrl_io = bt_io_connect(control_connect_cb, dev, NULL, &gerr, - BT_IO_OPT_SOURCE_BDADDR, src, - BT_IO_OPT_DEST_BDADDR, &dev->dst, - BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, - BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, - BT_IO_OPT_INVALID); - if (gerr) { - error("%s", gerr->message); - g_error_free(gerr); - goto fail; - } - - return; - -fail: - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); - hid_device_free(dev); -} - -static uint8_t bt_hid_connect(struct hal_cmd_hidhost_connect *cmd, - uint16_t len) -{ - struct hid_device *dev; - char addr[18]; - bdaddr_t dst; - GSList *l; - const bdaddr_t *src = bt_adapter_get_address(); - uuid_t uuid; - - DBG(""); - - if (len < sizeof(*cmd)) - return HAL_STATUS_INVALID; - - android2bdaddr(&cmd->bdaddr, &dst); - - l = g_slist_find_custom(devices, &dst, device_cmp); - if (l) - return HAL_STATUS_FAILED; - - dev = g_new0(struct hid_device, 1); - bacpy(&dev->dst, &dst); - dev->uhid_fd = -1; - - ba2str(&dev->dst, addr); - DBG("connecting to %s", addr); - - bt_string2uuid(&uuid, HID_UUID); - if (bt_search_service(src, &dev->dst, &uuid, hid_sdp_search_cb, dev, - NULL) < 0) { - error("Failed to search sdp details"); - hid_device_free(dev); - return HAL_STATUS_FAILED; - } - - devices = g_slist_append(devices, dev); - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTING); - - return HAL_STATUS_SUCCESS; -} - -static uint8_t bt_hid_disconnect(struct hal_cmd_hidhost_disconnect *cmd, - uint16_t len) -{ - struct hid_device *dev; - GSList *l; - bdaddr_t dst; - - DBG(""); - - if (len < sizeof(*cmd)) - return HAL_STATUS_INVALID; - - android2bdaddr(&cmd->bdaddr, &dst); - - l = g_slist_find_custom(devices, &dst, device_cmp); - if (!l) - return HAL_STATUS_FAILED; - - dev = l->data; - - /* Wait either channels to HUP */ - if (dev->intr_io) - g_io_channel_shutdown(dev->intr_io, TRUE, NULL); - - if (dev->ctrl_io) - g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); - - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTING); - - return HAL_STATUS_SUCCESS; -} - -static uint8_t bt_hid_virtual_unplug(struct hal_cmd_hidhost_vp *cmd, - uint16_t len) -{ - DBG("Not Implemented"); - - return HAL_STATUS_FAILED; -} - -static uint8_t bt_hid_info(struct hal_cmd_hidhost_set_info *cmd, uint16_t len) -{ - DBG("Not Implemented"); - - return HAL_STATUS_FAILED; -} - -static uint8_t bt_hid_get_protocol(struct hal_cmd_hidhost_get_protocol *cmd, - uint16_t len) -{ - struct hid_device *dev; - GSList *l; - bdaddr_t dst; - int fd; - uint8_t hdr; - - DBG(""); - - if (len < sizeof(*cmd)) - return HAL_STATUS_INVALID; - - android2bdaddr(&cmd->bdaddr, &dst); - - l = g_slist_find_custom(devices, &dst, device_cmp); - if (!l) - return HAL_STATUS_FAILED; - - dev = l->data; - - if (dev->boot_dev) - return HAL_STATUS_UNSUPPORTED; - - hdr = HID_MSG_GET_PROTOCOL | cmd->mode; - fd = g_io_channel_unix_get_fd(dev->ctrl_io); - - if (write(fd, &hdr, sizeof(hdr)) < 0) { - error("error while querying device protocol"); - return HAL_STATUS_FAILED; - } - - dev->last_hid_msg = HID_MSG_GET_PROTOCOL; - return HAL_STATUS_SUCCESS; -} - -static uint8_t bt_hid_set_protocol(struct hal_cmd_hidhost_set_protocol *cmd, - uint16_t len) -{ - struct hid_device *dev; - GSList *l; - bdaddr_t dst; - int fd; - uint8_t hdr; - - DBG(""); - - if (len < sizeof(*cmd)) - return HAL_STATUS_INVALID; - - android2bdaddr(&cmd->bdaddr, &dst); - - l = g_slist_find_custom(devices, &dst, device_cmp); - if (!l) - return HAL_STATUS_FAILED; - - dev = l->data; - - if (dev->boot_dev) - return HAL_STATUS_UNSUPPORTED; - - hdr = HID_MSG_SET_PROTOCOL | cmd->mode; - fd = g_io_channel_unix_get_fd(dev->ctrl_io); - - if (write(fd, &hdr, sizeof(hdr)) < 0) { - error("error while setting device protocol"); - return HAL_STATUS_FAILED; - } - - dev->last_hid_msg = HID_MSG_SET_PROTOCOL; - return HAL_STATUS_SUCCESS; -} - -static uint8_t bt_hid_get_report(struct hal_cmd_hidhost_get_report *cmd, - uint16_t len) -{ - struct hid_device *dev; - GSList *l; - bdaddr_t dst; - int fd; - uint8_t *req; - uint8_t req_size; - - DBG(""); - - if (len < sizeof(*cmd)) - return HAL_STATUS_INVALID; - - android2bdaddr(&cmd->bdaddr, &dst); - - l = g_slist_find_custom(devices, &dst, device_cmp); - if (!l) - return HAL_STATUS_FAILED; - - dev = l->data; - req_size = (cmd->buf_size > 0) ? 4 : 2; - req = g_try_malloc0(req_size); - if (!req) - return HAL_STATUS_NOMEM; - - req[0] = HID_MSG_GET_REPORT | cmd->type; - req[1] = cmd->id; - - if (cmd->buf_size > 0) { - req[0] = req[0] | HID_GET_REPORT_SIZE_FIELD; - bt_put_le16(cmd->buf_size, &req[2]); - } - - fd = g_io_channel_unix_get_fd(dev->ctrl_io); - - if (write(fd, req, req_size) < 0) { - error("error while querying device protocol"); - g_free(req); - return HAL_STATUS_FAILED; - } - - dev->last_hid_msg = HID_MSG_GET_REPORT; - g_free(req); - return HAL_STATUS_SUCCESS; -} - -static uint8_t bt_hid_set_report(struct hal_cmd_hidhost_set_report *cmd, - uint16_t len) -{ - struct hid_device *dev; - GSList *l; - bdaddr_t dst; - int fd; - uint8_t *req; - uint8_t req_size; - - DBG(""); - - if (len < sizeof(*cmd)) - return HAL_STATUS_INVALID; - - android2bdaddr(&cmd->bdaddr, &dst); - - l = g_slist_find_custom(devices, &dst, device_cmp); - if (!l) - return HAL_STATUS_FAILED; - - dev = l->data; - req_size = 1 + cmd->len; - req = g_try_malloc0(req_size); - if (!req) - return HAL_STATUS_NOMEM; - - req[0] = HID_MSG_SET_REPORT | cmd->type; - memcpy(req + 1, cmd->data, req_size - 1); - - fd = g_io_channel_unix_get_fd(dev->ctrl_io); - - if (write(fd, req, req_size) < 0) { - error("error while querying device protocol"); - g_free(req); - return HAL_STATUS_FAILED; - } - - dev->last_hid_msg = HID_MSG_SET_REPORT; - g_free(req); - return HAL_STATUS_SUCCESS; -} - -static uint8_t bt_hid_send_data(struct hal_cmd_hidhost_send_data *cmd, - uint16_t len) -{ - DBG("Not Implemented"); - - return HAL_STATUS_FAILED; -} - -void bt_hid_handle_cmd(int sk, uint8_t opcode, void *buf, uint16_t len) -{ - uint8_t status = HAL_STATUS_FAILED; - - switch (opcode) { - case HAL_OP_HIDHOST_CONNECT: - status = bt_hid_connect(buf, len); - break; - case HAL_OP_HIDHOST_DISCONNECT: - status = bt_hid_disconnect(buf, len); - break; - case HAL_OP_HIDHOST_VP: - status = bt_hid_virtual_unplug(buf, len); - break; - case HAL_OP_HIDHOST_SET_INFO: - status = bt_hid_info(buf, len); - break; - case HAL_OP_HIDHOST_GET_PROTOCOL: - status = bt_hid_get_protocol(buf, len); - break; - case HAL_OP_HIDHOST_SET_PROTOCOL: - status = bt_hid_set_protocol(buf, len); - break; - case HAL_OP_HIDHOST_GET_REPORT: - status = bt_hid_get_report(buf, len); - break; - case HAL_OP_HIDHOST_SET_REPORT: - status = bt_hid_set_report(buf, len); - break; - case HAL_OP_HIDHOST_SEND_DATA: - status = bt_hid_send_data(buf, len); - break; - default: - DBG("Unhandled command, opcode 0x%x", opcode); - break; - } - - ipc_send_rsp(sk, HAL_SERVICE_ID_HIDHOST, status); -} - -static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) -{ - struct hid_device *dev; - bdaddr_t src, dst; - char address[18]; - uint16_t psm; - GError *gerr = NULL; - GSList *l; - uuid_t uuid; - - if (err) { - error("%s", err->message); - return; - } - - bt_io_get(chan, &err, - BT_IO_OPT_SOURCE_BDADDR, &src, - BT_IO_OPT_DEST_BDADDR, &dst, - BT_IO_OPT_PSM, &psm, - BT_IO_OPT_INVALID); - if (err) { - error("%s", gerr->message); - g_io_channel_shutdown(chan, TRUE, NULL); - return; - } - - ba2str(&dst, address); - DBG("Incoming connection from %s on PSM %d", address, psm); - - switch (psm) { - case L2CAP_PSM_HIDP_CTRL: - l = g_slist_find_custom(devices, &dst, device_cmp); - if (l) - return; - - dev = g_new0(struct hid_device, 1); - bacpy(&dev->dst, &dst); - dev->ctrl_io = g_io_channel_ref(chan); - dev->uhid_fd = -1; - - bt_string2uuid(&uuid, HID_UUID); - if (bt_search_service(&src, &dev->dst, &uuid, - hid_sdp_search_cb, dev, NULL) < 0) { - error("failed to search sdp details"); - hid_device_free(dev); - return; - } - - devices = g_slist_append(devices, dev); - - dev->ctrl_watch = g_io_add_watch(dev->ctrl_io, - G_IO_HUP | G_IO_ERR | G_IO_NVAL, - ctrl_watch_cb, dev); - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTING); - break; - - case L2CAP_PSM_HIDP_INTR: - l = g_slist_find_custom(devices, &dst, device_cmp); - if (!l) - return; - - dev = l->data; - dev->intr_io = g_io_channel_ref(chan); - dev->intr_watch = g_io_add_watch(dev->intr_io, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - intr_watch_cb, dev); - bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); - break; - } -} - -bool bt_hid_register(int sk, const bdaddr_t *addr) -{ - GError *err = NULL; - const bdaddr_t *src = bt_adapter_get_address(); - - DBG(""); - - ctrl_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, - BT_IO_OPT_SOURCE_BDADDR, src, - BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, - BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, - BT_IO_OPT_INVALID); - if (!ctrl_io) { - error("Failed to listen on ctrl channel: %s", err->message); - g_error_free(err); - return false; - } - - intr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, - BT_IO_OPT_SOURCE_BDADDR, src, - BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, - BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, - BT_IO_OPT_INVALID); - if (!intr_io) { - error("Failed to listen on intr channel: %s", err->message); - g_io_channel_unref(ctrl_io); - g_error_free(err); - return false; - } - - notification_sk = sk; - - return true; -} - -void bt_hid_unregister(void) -{ - DBG(""); - - notification_sk = -1; - - if (ctrl_io) { - g_io_channel_shutdown(ctrl_io, TRUE, NULL); - g_io_channel_unref(ctrl_io); - ctrl_io = NULL; - } - - if (intr_io) { - g_io_channel_shutdown(intr_io, TRUE, NULL); - g_io_channel_unref(intr_io); - intr_io = NULL; - } -} diff --git a/android/hid.h b/android/hid.h deleted file mode 100644 index 688086a..0000000 --- a/android/hid.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * - * BlueZ - Bluetooth protocol stack for Linux - * - * Copyright (C) 2013 Intel Corporation. All rights reserved. - * - * - * 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 of the License, or - * (at your option) any later version. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -void bt_hid_handle_cmd(int sk, uint8_t opcode, void *buf, uint16_t len); - -bool bt_hid_register(int sk, const bdaddr_t *addr); -void bt_hid_unregister(void); diff --git a/android/hidhost.c b/android/hidhost.c new file mode 100644 index 0000000..c7b4114 --- /dev/null +++ b/android/hidhost.c @@ -0,0 +1,1095 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * + * 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 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> + +#include <glib.h> + +#include "btio/btio.h" +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" +#include "src/shared/mgmt.h" +#include "src/sdp-client.h" +#include "src/glib-helper.h" +#include "profiles/input/uhid_copy.h" + +#include "log.h" +#include "hal-msg.h" +#include "ipc.h" +#include "hidhost.h" +#include "adapter.h" +#include "utils.h" + +#define L2CAP_PSM_HIDP_CTRL 0x11 +#define L2CAP_PSM_HIDP_INTR 0x13 +#define UHID_DEVICE_FILE "/dev/uhid" + +/* HID message types */ +#define HID_MSG_GET_REPORT 0x40 +#define HID_MSG_SET_REPORT 0x50 +#define HID_MSG_GET_PROTOCOL 0x60 +#define HID_MSG_SET_PROTOCOL 0x70 +#define HID_MSG_DATA 0xa0 + +/* HID data types */ +#define HID_DATA_TYPE_INPUT 0x01 +#define HID_DATA_TYPE_OUTPUT 0x02 +#define HID_DATA_TYPE_FEATURE 0x03 + +/* HID protocol header parameters */ +#define HID_PROTO_BOOT 0x00 +#define HID_PROTO_REPORT 0x01 + +/* HID GET REPORT Size Field */ +#define HID_GET_REPORT_SIZE_FIELD 0x08 + +static int notification_sk = -1; +static GIOChannel *ctrl_io = NULL; +static GIOChannel *intr_io = NULL; +static GSList *devices = NULL; + +struct hid_device { + bdaddr_t dst; + uint8_t state; + uint8_t subclass; + uint16_t vendor; + uint16_t product; + uint16_t version; + uint8_t country; + int rd_size; + void *rd_data; + uint8_t boot_dev; + GIOChannel *ctrl_io; + GIOChannel *intr_io; + guint ctrl_watch; + guint intr_watch; + int uhid_fd; + guint uhid_watch_id; + uint8_t last_hid_msg; +}; + +static int device_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct hid_device *dev = s; + const bdaddr_t *dst = user_data; + + return bacmp(&dev->dst, dst); +} + +static void uhid_destroy(int fd) +{ + struct uhid_event ev; + + /* destroy uHID device */ + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + if (write(fd, &ev, sizeof(ev)) < 0) + error("Failed to destroy uHID device: %s (%d)", + strerror(errno), errno); + + close(fd); +} + +static void hid_device_free(struct hid_device *dev) +{ + if (dev->ctrl_watch > 0) + g_source_remove(dev->ctrl_watch); + + if (dev->intr_watch > 0) + g_source_remove(dev->intr_watch); + + if (dev->intr_io) + g_io_channel_unref(dev->intr_io); + + if (dev->ctrl_io) + g_io_channel_unref(dev->ctrl_io); + + if (dev->uhid_watch_id) { + g_source_remove(dev->uhid_watch_id); + dev->uhid_watch_id = 0; + } + + if (dev->uhid_fd > 0) + uhid_destroy(dev->uhid_fd); + + g_free(dev->rd_data); + + devices = g_slist_remove(devices, dev); + g_free(dev); +} + +static void handle_uhid_event(struct hid_device *dev, struct uhid_event *ev) +{ + DBG("UHID_OUTPUT UHID_FEATURE unsupported"); +} + +static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct hid_device *dev = user_data; + struct uhid_event ev; + ssize_t bread; + int fd; + + DBG(""); + + if (cond & (G_IO_ERR | G_IO_NVAL)) + goto failed; + + fd = g_io_channel_unix_get_fd(io); + memset(&ev, 0, sizeof(ev)); + + bread = read(fd, &ev, sizeof(ev)); + if (bread < 0) { + DBG("read: %s (%d)", strerror(errno), errno); + goto failed; + } + + DBG("uHID event type %d received", ev.type); + + switch (ev.type) { + case UHID_START: + case UHID_STOP: + /* These are called to start and stop the underlying hardware. + * We open the channels before creating the device so the + * hardware is always ready. No need to handle these. + * The kernel never destroys a device itself! Only an explicit + * UHID_DESTROY request can remove a device. */ + + break; + case UHID_OPEN: + case UHID_CLOSE: + /* OPEN/CLOSE are sent whenever user-space opens any interface + * provided by the kernel HID device. Whenever the open-count + * is non-zero we must be ready for I/O. As long as it is zero, + * we can decide to drop all I/O and put the device + * asleep This is optional, though. */ + break; + case UHID_OUTPUT: + case UHID_FEATURE: + handle_uhid_event(dev, &ev); + break; + case UHID_OUTPUT_EV: + /* This is only sent by kernels prior to linux-3.11. It + * requires us to parse HID-descriptors in user-space to + * properly handle it. This is redundant as the kernel + * does it already. That's why newer kernels assemble + * the output-reports and send it to us via UHID_OUTPUT. */ + DBG("UHID_OUTPUT_EV unsupported"); + break; + default: + warn("unexpected uHID event"); + } + + return TRUE; + +failed: + dev->uhid_watch_id = 0; + return FALSE; +} + +static gboolean intr_io_watch_cb(GIOChannel *chan, gpointer data) +{ + struct hid_device *dev = data; + uint8_t buf[UHID_DATA_MAX]; + struct uhid_event ev; + int fd, bread; + + /* Wait uHID if not ready */ + if (dev->uhid_fd < 0) + return TRUE; + + fd = g_io_channel_unix_get_fd(chan); + bread = read(fd, buf, sizeof(buf)); + if (bread < 0) { + error("read: %s(%d)", strerror(errno), -errno); + return TRUE; + } + + /* Discard non-data packets */ + if (bread == 0 || buf[0] != (HID_MSG_DATA | HID_DATA_TYPE_INPUT)) + return TRUE; + + /* send data to uHID device skipping HIDP header byte */ + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + ev.u.input.size = bread - 1; + memcpy(ev.u.input.data, &buf[1], ev.u.input.size); + + if (write(dev->uhid_fd, &ev, sizeof(ev)) < 0) + DBG("write: %s (%d)", strerror(errno), errno); + + return TRUE; +} + +static void bt_hid_notify_state(struct hid_device *dev, uint8_t state) +{ + struct hal_ev_hidhost_conn_state ev; + char address[18]; + + if (dev->state == state) + return; + + dev->state = state; + + ba2str(&dev->dst, address); + DBG("device %s state %u", address, state); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.state = state; + + ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_CONN_STATE, sizeof(ev), &ev, -1); +} + +static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct hid_device *dev = data; + + if (cond & G_IO_IN) + return intr_io_watch_cb(chan, data); + + /* Checking for ctrl_watch avoids a double g_io_channel_shutdown since + * it's likely that ctrl_watch_cb has been queued for dispatching in + * this mainloop iteration */ + if ((cond & (G_IO_HUP | G_IO_ERR)) && dev->ctrl_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + dev->intr_watch = 0; + + if (dev->intr_io) { + g_io_channel_unref(dev->intr_io); + dev->intr_io = NULL; + } + + /* Close control channel */ + if (dev->ctrl_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); + + return FALSE; +} + +static void bt_hid_notify_proto_mode(struct hid_device *dev, uint8_t *buf, + int len) +{ + struct hal_ev_hidhost_proto_mode ev; + char address[18]; + + ba2str(&dev->dst, address); + DBG("device %s", address); + + memset(&ev, 0, sizeof(ev)); + bdaddr2android(&dev->dst, ev.bdaddr); + + if (buf[0] == HID_MSG_DATA) { + ev.status = HAL_HIDHOST_STATUS_OK; + if (buf[1] == HID_PROTO_REPORT) + ev.mode = HAL_HIDHOST_REPORT_PROTOCOL; + else if (buf[1] == HID_PROTO_BOOT) + ev.mode = HAL_HIDHOST_BOOT_PROTOCOL; + else + ev.mode = HAL_HIDHOST_UNSUPPORTED_PROTOCOL; + + } else { + ev.status = buf[0]; + ev.mode = HAL_HIDHOST_UNSUPPORTED_PROTOCOL; + } + + ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_PROTO_MODE, sizeof(ev), &ev, -1); +} + +static void bt_hid_notify_get_report(struct hid_device *dev, uint8_t *buf, + int len) +{ + struct hal_ev_hidhost_get_report *ev; + int ev_len; + char address[18]; + + ba2str(&dev->dst, address); + DBG("device %s", address); + + ev_len = sizeof(*ev) + sizeof(struct hal_ev_hidhost_get_report) + 1; + + if (!((buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_INPUT)) || + (buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_OUTPUT)) || + (buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_FEATURE)))) { + ev = g_malloc0(ev_len); + ev->status = buf[0]; + bdaddr2android(&dev->dst, ev->bdaddr); + goto send; + } + + /* Report porotocol mode reply contains id after hdr, in boot + * protocol mode id doesn't exist */ + ev_len += (dev->boot_dev) ? (len - 1) : (len - 2); + ev = g_malloc0(ev_len); + ev->status = HAL_HIDHOST_STATUS_OK; + bdaddr2android(&dev->dst, ev->bdaddr); + + /* Report porotocol mode reply contains id after hdr, in boot + * protocol mode id doesn't exist */ + if (dev->boot_dev) { + ev->len = len - 1; + memcpy(ev->data, buf + 1, ev->len); + } else { + ev->len = len - 2; + memcpy(ev->data, buf + 2, ev->len); + } + +send: + ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_GET_REPORT, ev_len, ev, -1); + g_free(ev); +} + +static gboolean ctrl_io_watch_cb(GIOChannel *chan, gpointer data) +{ + struct hid_device *dev = data; + int fd, bread; + uint8_t buf[UHID_DATA_MAX]; + + DBG(""); + + fd = g_io_channel_unix_get_fd(chan); + bread = read(fd, buf, sizeof(buf)); + if (bread < 0) { + error("read: %s(%d)", strerror(errno), -errno); + return TRUE; + } + + switch (dev->last_hid_msg) { + case HID_MSG_GET_PROTOCOL: + case HID_MSG_SET_PROTOCOL: + bt_hid_notify_proto_mode(dev, buf, bread); + break; + case HID_MSG_GET_REPORT: + bt_hid_notify_get_report(dev, buf, bread); + break; + } + + /* reset msg type request */ + dev->last_hid_msg = 0; + + return TRUE; +} + +static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct hid_device *dev = data; + char address[18]; + + if (cond & G_IO_IN) + return ctrl_io_watch_cb(chan, data); + + ba2str(&dev->dst, address); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + + /* Checking for intr_watch avoids a double g_io_channel_shutdown since + * it's likely that intr_watch_cb has been queued for dispatching in + * this mainloop iteration */ + if ((cond & (G_IO_HUP | G_IO_ERR)) && dev->intr_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + if (dev->intr_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(dev->intr_io, TRUE, NULL); + + hid_device_free(dev); + + return FALSE; +} + +static void bt_hid_set_info(struct hid_device *dev) +{ + struct hal_ev_hidhost_info ev; + + DBG(""); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.attr = 0; /* TODO: Check what is this field */ + ev.subclass = dev->subclass; + ev.app_id = 0; /* TODO: Check what is this field */ + ev.vendor = dev->vendor; + ev.product = dev->product; + ev.version = dev->version; + ev.country = dev->country; + ev.descr_len = dev->rd_size; + memset(ev.descr, 0, sizeof(ev.descr)); + memcpy(ev.descr, dev->rd_data, ev.descr_len); + + ipc_send(notification_sk, HAL_SERVICE_ID_HIDHOST, HAL_EV_HIDHOST_INFO, + sizeof(ev), &ev, -1); +} + +static int uhid_create(struct hid_device *dev) +{ + GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL; + GIOChannel *io; + struct uhid_event ev; + + dev->uhid_fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC); + if (dev->uhid_fd < 0) { + error("Failed to open uHID device: %s", strerror(errno)); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_NO_HID); + return -errno; + } + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char *) ev.u.create.name, "bluez-input-device"); + ev.u.create.bus = BUS_BLUETOOTH; + ev.u.create.vendor = dev->vendor; + ev.u.create.product = dev->product; + ev.u.create.version = dev->version; + ev.u.create.country = dev->country; + ev.u.create.rd_size = dev->rd_size; + ev.u.create.rd_data = dev->rd_data; + + if (write(dev->uhid_fd, &ev, sizeof(ev)) < 0) { + error("Failed to create uHID device: %s", strerror(errno)); + close(dev->uhid_fd); + dev->uhid_fd = -1; + return -errno; + } + + io = g_io_channel_unix_new(dev->uhid_fd); + g_io_channel_set_encoding(io, NULL, NULL); + dev->uhid_watch_id = g_io_add_watch(io, cond, uhid_event_cb, dev); + g_io_channel_unref(io); + + bt_hid_set_info(dev); + + return 0; +} + +static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct hid_device *dev = user_data; + + DBG(""); + + if (conn_err) + goto failed; + + if (uhid_create(dev) < 0) + goto failed; + + dev->intr_watch = g_io_add_watch(dev->intr_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + intr_watch_cb, dev); + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); + + return; + +failed: + /* So we guarantee the interrupt channel is closed before the + * control channel (if we only do unref GLib will close it only + * after returning control to the mainloop */ + if (!conn_err) + g_io_channel_shutdown(dev->intr_io, FALSE, NULL); + + g_io_channel_unref(dev->intr_io); + dev->intr_io = NULL; + + if (dev->ctrl_io) { + g_io_channel_unref(dev->ctrl_io); + dev->ctrl_io = NULL; + } +} + +static void control_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct hid_device *dev = user_data; + GError *err = NULL; + const bdaddr_t *src = bt_adapter_get_address(); + + DBG(""); + + if (conn_err) { + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + error("%s", conn_err->message); + goto failed; + } + + /* Connect to the HID interrupt channel */ + dev->intr_io = bt_io_connect(interrupt_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!dev->intr_io) { + error("%s", err->message); + g_error_free(err); + goto failed; + } + + dev->ctrl_watch = g_io_add_watch(dev->ctrl_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + ctrl_watch_cb, dev); + + return; + +failed: + hid_device_free(dev); +} + +static void hid_sdp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct hid_device *dev = data; + sdp_list_t *list; + GError *gerr = NULL; + const bdaddr_t *src = bt_adapter_get_address(); + + DBG(""); + + if (err < 0) { + error("Unable to get SDP record: %s", strerror(-err)); + goto fail; + } + + if (!recs || !recs->data) { + error("No SDP records found"); + goto fail; + } + + for (list = recs; list != NULL; list = list->next) { + sdp_record_t *rec = list->data; + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); + if (data) + dev->vendor = data->val.uint16; + + data = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); + if (data) + dev->product = data->val.uint16; + + data = sdp_data_get(rec, SDP_ATTR_VERSION); + if (data) + dev->version = data->val.uint16; + + data = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE); + if (data) + dev->country = data->val.uint8; + + data = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS); + if (data) + dev->subclass = data->val.uint8; + + data = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE); + if (data) + dev->boot_dev = data->val.uint8; + + data = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); + if (data) { + if (!SDP_IS_SEQ(data->dtd)) + goto fail; + + /* First HIDDescriptor */ + data = data->val.dataseq; + if (!SDP_IS_SEQ(data->dtd)) + goto fail; + + /* ClassDescriptorType */ + data = data->val.dataseq; + if (data->dtd != SDP_UINT8) + goto fail; + + /* ClassDescriptorData */ + data = data->next; + if (!data || !SDP_IS_TEXT_STR(data->dtd)) + goto fail; + + dev->rd_size = data->unitSize; + dev->rd_data = g_memdup(data->val.str, data->unitSize); + } + } + + if (dev->ctrl_io) { + if (uhid_create(dev) < 0) + goto fail; + return; + } + + dev->ctrl_io = bt_io_connect(control_connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + goto fail; + } + + return; + +fail: + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + hid_device_free(dev); +} + +static uint8_t bt_hid_connect(struct hal_cmd_hidhost_connect *cmd, + uint16_t len) +{ + struct hid_device *dev; + char addr[18]; + bdaddr_t dst; + GSList *l; + const bdaddr_t *src = bt_adapter_get_address(); + uuid_t uuid; + + DBG(""); + + if (len < sizeof(*cmd)) + return HAL_STATUS_INVALID; + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) + return HAL_STATUS_FAILED; + + dev = g_new0(struct hid_device, 1); + bacpy(&dev->dst, &dst); + dev->uhid_fd = -1; + + ba2str(&dev->dst, addr); + DBG("connecting to %s", addr); + + bt_string2uuid(&uuid, HID_UUID); + if (bt_search_service(src, &dev->dst, &uuid, hid_sdp_search_cb, dev, + NULL) < 0) { + error("Failed to search sdp details"); + hid_device_free(dev); + return HAL_STATUS_FAILED; + } + + devices = g_slist_append(devices, dev); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTING); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t bt_hid_disconnect(struct hal_cmd_hidhost_disconnect *cmd, + uint16_t len) +{ + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + + DBG(""); + + if (len < sizeof(*cmd)) + return HAL_STATUS_INVALID; + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return HAL_STATUS_FAILED; + + dev = l->data; + + /* Wait either channels to HUP */ + if (dev->intr_io) + g_io_channel_shutdown(dev->intr_io, TRUE, NULL); + + if (dev->ctrl_io) + g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTING); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t bt_hid_virtual_unplug(struct hal_cmd_hidhost_vp *cmd, + uint16_t len) +{ + DBG("Not Implemented"); + + return HAL_STATUS_FAILED; +} + +static uint8_t bt_hid_info(struct hal_cmd_hidhost_set_info *cmd, uint16_t len) +{ + DBG("Not Implemented"); + + return HAL_STATUS_FAILED; +} + +static uint8_t bt_hid_get_protocol(struct hal_cmd_hidhost_get_protocol *cmd, + uint16_t len) +{ + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t hdr; + + DBG(""); + + if (len < sizeof(*cmd)) + return HAL_STATUS_INVALID; + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return HAL_STATUS_FAILED; + + dev = l->data; + + if (dev->boot_dev) + return HAL_STATUS_UNSUPPORTED; + + hdr = HID_MSG_GET_PROTOCOL | cmd->mode; + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, &hdr, sizeof(hdr)) < 0) { + error("error while querying device protocol"); + return HAL_STATUS_FAILED; + } + + dev->last_hid_msg = HID_MSG_GET_PROTOCOL; + return HAL_STATUS_SUCCESS; +} + +static uint8_t bt_hid_set_protocol(struct hal_cmd_hidhost_set_protocol *cmd, + uint16_t len) +{ + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t hdr; + + DBG(""); + + if (len < sizeof(*cmd)) + return HAL_STATUS_INVALID; + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return HAL_STATUS_FAILED; + + dev = l->data; + + if (dev->boot_dev) + return HAL_STATUS_UNSUPPORTED; + + hdr = HID_MSG_SET_PROTOCOL | cmd->mode; + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, &hdr, sizeof(hdr)) < 0) { + error("error while setting device protocol"); + return HAL_STATUS_FAILED; + } + + dev->last_hid_msg = HID_MSG_SET_PROTOCOL; + return HAL_STATUS_SUCCESS; +} + +static uint8_t bt_hid_get_report(struct hal_cmd_hidhost_get_report *cmd, + uint16_t len) +{ + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t *req; + uint8_t req_size; + + DBG(""); + + if (len < sizeof(*cmd)) + return HAL_STATUS_INVALID; + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return HAL_STATUS_FAILED; + + dev = l->data; + req_size = (cmd->buf_size > 0) ? 4 : 2; + req = g_try_malloc0(req_size); + if (!req) + return HAL_STATUS_NOMEM; + + req[0] = HID_MSG_GET_REPORT | cmd->type; + req[1] = cmd->id; + + if (cmd->buf_size > 0) { + req[0] = req[0] | HID_GET_REPORT_SIZE_FIELD; + bt_put_le16(cmd->buf_size, &req[2]); + } + + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, req, req_size) < 0) { + error("error while querying device protocol"); + g_free(req); + return HAL_STATUS_FAILED; + } + + dev->last_hid_msg = HID_MSG_GET_REPORT; + g_free(req); + return HAL_STATUS_SUCCESS; +} + +static uint8_t bt_hid_set_report(struct hal_cmd_hidhost_set_report *cmd, + uint16_t len) +{ + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t *req; + uint8_t req_size; + + DBG(""); + + if (len < sizeof(*cmd)) + return HAL_STATUS_INVALID; + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return HAL_STATUS_FAILED; + + dev = l->data; + req_size = 1 + cmd->len; + req = g_try_malloc0(req_size); + if (!req) + return HAL_STATUS_NOMEM; + + req[0] = HID_MSG_SET_REPORT | cmd->type; + memcpy(req + 1, cmd->data, req_size - 1); + + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, req, req_size) < 0) { + error("error while querying device protocol"); + g_free(req); + return HAL_STATUS_FAILED; + } + + dev->last_hid_msg = HID_MSG_SET_REPORT; + g_free(req); + return HAL_STATUS_SUCCESS; +} + +static uint8_t bt_hid_send_data(struct hal_cmd_hidhost_send_data *cmd, + uint16_t len) +{ + DBG("Not Implemented"); + + return HAL_STATUS_FAILED; +} + +void bt_hid_handle_cmd(int sk, uint8_t opcode, void *buf, uint16_t len) +{ + uint8_t status = HAL_STATUS_FAILED; + + switch (opcode) { + case HAL_OP_HIDHOST_CONNECT: + status = bt_hid_connect(buf, len); + break; + case HAL_OP_HIDHOST_DISCONNECT: + status = bt_hid_disconnect(buf, len); + break; + case HAL_OP_HIDHOST_VP: + status = bt_hid_virtual_unplug(buf, len); + break; + case HAL_OP_HIDHOST_SET_INFO: + status = bt_hid_info(buf, len); + break; + case HAL_OP_HIDHOST_GET_PROTOCOL: + status = bt_hid_get_protocol(buf, len); + break; + case HAL_OP_HIDHOST_SET_PROTOCOL: + status = bt_hid_set_protocol(buf, len); + break; + case HAL_OP_HIDHOST_GET_REPORT: + status = bt_hid_get_report(buf, len); + break; + case HAL_OP_HIDHOST_SET_REPORT: + status = bt_hid_set_report(buf, len); + break; + case HAL_OP_HIDHOST_SEND_DATA: + status = bt_hid_send_data(buf, len); + break; + default: + DBG("Unhandled command, opcode 0x%x", opcode); + break; + } + + ipc_send_rsp(sk, HAL_SERVICE_ID_HIDHOST, status); +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct hid_device *dev; + bdaddr_t src, dst; + char address[18]; + uint16_t psm; + GError *gerr = NULL; + GSList *l; + uuid_t uuid; + + if (err) { + error("%s", err->message); + return; + } + + bt_io_get(chan, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_PSM, &psm, + BT_IO_OPT_INVALID); + if (err) { + error("%s", gerr->message); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + ba2str(&dst, address); + DBG("Incoming connection from %s on PSM %d", address, psm); + + switch (psm) { + case L2CAP_PSM_HIDP_CTRL: + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) + return; + + dev = g_new0(struct hid_device, 1); + bacpy(&dev->dst, &dst); + dev->ctrl_io = g_io_channel_ref(chan); + dev->uhid_fd = -1; + + bt_string2uuid(&uuid, HID_UUID); + if (bt_search_service(&src, &dev->dst, &uuid, + hid_sdp_search_cb, dev, NULL) < 0) { + error("failed to search sdp details"); + hid_device_free(dev); + return; + } + + devices = g_slist_append(devices, dev); + + dev->ctrl_watch = g_io_add_watch(dev->ctrl_io, + G_IO_HUP | G_IO_ERR | G_IO_NVAL, + ctrl_watch_cb, dev); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTING); + break; + + case L2CAP_PSM_HIDP_INTR: + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return; + + dev = l->data; + dev->intr_io = g_io_channel_ref(chan); + dev->intr_watch = g_io_add_watch(dev->intr_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + intr_watch_cb, dev); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); + break; + } +} + +bool bt_hid_register(int sk, const bdaddr_t *addr) +{ + GError *err = NULL; + const bdaddr_t *src = bt_adapter_get_address(); + + DBG(""); + + ctrl_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!ctrl_io) { + error("Failed to listen on ctrl channel: %s", err->message); + g_error_free(err); + return false; + } + + intr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!intr_io) { + error("Failed to listen on intr channel: %s", err->message); + g_io_channel_unref(ctrl_io); + g_error_free(err); + return false; + } + + notification_sk = sk; + + return true; +} + +void bt_hid_unregister(void) +{ + DBG(""); + + notification_sk = -1; + + if (ctrl_io) { + g_io_channel_shutdown(ctrl_io, TRUE, NULL); + g_io_channel_unref(ctrl_io); + ctrl_io = NULL; + } + + if (intr_io) { + g_io_channel_shutdown(intr_io, TRUE, NULL); + g_io_channel_unref(intr_io); + intr_io = NULL; + } +} diff --git a/android/hidhost.h b/android/hidhost.h new file mode 100644 index 0000000..688086a --- /dev/null +++ b/android/hidhost.h @@ -0,0 +1,27 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * + * 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 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void bt_hid_handle_cmd(int sk, uint8_t opcode, void *buf, uint16_t len); + +bool bt_hid_register(int sk, const bdaddr_t *addr); +void bt_hid_unregister(void); diff --git a/android/main.c b/android/main.c index 71fc60b..057c2f7 100644 --- a/android/main.c +++ b/android/main.c @@ -50,7 +50,7 @@ #include "adapter.h" #include "socket.h" -#include "hid.h" +#include "hidhost.h" #include "hal-msg.h" #include "ipc.h" #include "a2dp.h" -- 1.8.4.2 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html