Implemented basic HID connect method. Host connects to bt device at L2CAP level. --- v3: Updated patches as per Luiz comments (remove unnecessary type cast and tabs) v2: Updated patches as per Luiz comments v1: Patchset adds hid connect and disconnect mechanisms at L2CAP level. It opens the control channel and interrupt channel and listens on io events. UHID, hid server and --- Makefile.android | 3 +- android/Android.mk | 1 + android/hid.c | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 1 deletion(-) diff --git a/Makefile.android b/Makefile.android index 2b57daa..22002be 100644 --- a/Makefile.android +++ b/Makefile.android @@ -12,7 +12,8 @@ android_bluetoothd_SOURCES = android/main.c \ android/adapter.h android/adapter.c \ android/hid.h android/hid.c \ android/ipc.h android/ipc.c \ - android/socket.h android/socket.c + android/socket.h android/socket.c \ + btio/btio.h btio/btio.c android_bluetoothd_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ diff --git a/android/Android.mk b/android/Android.mk index 22208e0..28ec465 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -28,6 +28,7 @@ LOCAL_SRC_FILES := \ ../lib/sdp.c \ ../lib/bluetooth.c \ ../lib/hci.c \ + ../btio/btio.c LOCAL_C_INCLUDES := \ $(call include-path-for, glib) \ diff --git a/android/hid.c b/android/hid.c index f2da0d3..913dab4 100644 --- a/android/hid.c +++ b/android/hid.c @@ -23,16 +23,260 @@ #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 "src/shared/mgmt.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 MAX_READ_BUFFER 4096 static GIOChannel *notification_io = NULL; +static GSList *devices = NULL; + +struct hid_device { + bdaddr_t dst; + GIOChannel *ctrl_io; + GIOChannel *intr_io; + guint ctrl_watch; + guint intr_watch; +}; + +static int device_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct hid_device *hdev = s; + const bdaddr_t *dst = user_data; + + return bacmp(&hdev->dst, dst); +} + +static void hid_device_free(struct hid_device *hdev) +{ + if (hdev->ctrl_watch > 0) + g_source_remove(hdev->ctrl_watch); + + if (hdev->intr_watch > 0) + g_source_remove(hdev->intr_watch); + + if (hdev->intr_io) + g_io_channel_unref(hdev->intr_io); + + if (hdev->ctrl_io) + g_io_channel_unref(hdev->ctrl_io); + + devices = g_slist_remove(devices, hdev); + g_free(hdev); +} + +static gboolean intr_io_watch_cb(GIOChannel *chan, gpointer data) +{ + char buf[MAX_READ_BUFFER]; + int fd, bread; + + 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; + } + + DBG("bytes read %d", bread); + + /* TODO: At this moment only baseband is connected, i.e. mouse + * movements keyboard events doesn't effect on UI. Have to send + * this data to uhid fd for profile connection. */ + + return TRUE; +} + +static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct hid_device *hdev = data; + char address[18]; + + if (cond & G_IO_IN) + return intr_io_watch_cb(chan, data); + + ba2str(&hdev->dst, address); + DBG("Device %s disconnected", address); + + /* 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)) && hdev->ctrl_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + hdev->intr_watch = 0; + + if (hdev->intr_io) { + g_io_channel_unref(hdev->intr_io); + hdev->intr_io = NULL; + } + + /* Close control channel */ + if (hdev->ctrl_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(hdev->ctrl_io, TRUE, NULL); + + return FALSE; +} + +static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct hid_device *hdev = data; + char address[18]; + + ba2str(&hdev->dst, address); + DBG("Device %s disconnected", address); + + /* 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)) && hdev->intr_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + hdev->ctrl_watch = 0; + + if (hdev->ctrl_io) { + g_io_channel_unref(hdev->ctrl_io); + hdev->ctrl_io = NULL; + } + + if (hdev->intr_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(hdev->intr_io, TRUE, NULL); + + return FALSE; +} + +static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct hid_device *hdev = user_data; + + DBG(""); + + if (conn_err) + goto failed; + + /*TODO: Get device details through SDP and create UHID fd and start + * listening on uhid events */ + hdev->intr_watch = g_io_add_watch(hdev->intr_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + intr_watch_cb, hdev); + + 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(hdev->intr_io, FALSE, NULL); + + g_io_channel_unref(hdev->intr_io); + hdev->intr_io = NULL; + + if (hdev->ctrl_io) { + g_io_channel_unref(hdev->ctrl_io); + hdev->ctrl_io = NULL; + } +} + +static void control_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct hid_device *hdev = user_data; + GError *err = NULL; + const bdaddr_t *src = bt_adapter_get_address(); + + DBG(""); + + if (conn_err) { + error("%s", conn_err->message); + goto failed; + } + + /* Connect to the HID interrupt channel */ + hdev->intr_io = bt_io_connect(interrupt_connect_cb, hdev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, &hdev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!hdev->intr_io) { + error("%s", err->message); + g_error_free(err); + goto failed; + } + + hdev->ctrl_watch = g_io_add_watch(hdev->ctrl_io, + G_IO_HUP | G_IO_ERR | G_IO_NVAL, + ctrl_watch_cb, hdev); + + return; + +failed: + g_io_channel_unref(hdev->ctrl_io); + hdev->ctrl_io = NULL; +} + +static uint8_t bt_hid_connect(struct hal_cmd_hid_connect *cmd, uint16_t len) +{ + struct hid_device *hdev; + char addr[18]; + bdaddr_t dst; + GSList *l; + GError *err = NULL; + const bdaddr_t *src = bt_adapter_get_address(); + + 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; + + hdev = g_new0(struct hid_device, 1); + android2bdaddr(&cmd->bdaddr, &hdev->dst); + ba2str(&hdev->dst, addr); + + DBG("connecting to %s", addr); + + hdev->ctrl_io = bt_io_connect(control_connect_cb, hdev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, &hdev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + hid_device_free(hdev); + return HAL_STATUS_FAILED; + } + + devices = g_slist_append(devices, hdev); + + return HAL_STATUS_SUCCESS; +} void bt_hid_handle_cmd(GIOChannel *io, uint8_t opcode, void *buf, uint16_t len) { @@ -40,6 +284,7 @@ void bt_hid_handle_cmd(GIOChannel *io, uint8_t opcode, void *buf, uint16_t len) switch (opcode) { case HAL_OP_HID_CONNECT: + status = bt_hid_connect(buf, len); break; case HAL_OP_HID_DISCONNECT: break; -- 1.7.9.5 -- 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