Implemented basic HID connect method. Host connects to
bt device at L2CAP level.
---
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
reconnect related features not yet done.
---
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..7f9e386 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((bdaddr_t *)&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((bdaddr_t *)&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
Implemented basic HID disconnect method. Host disconnects
with bt device at L2CAP level.
---
android/hid.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/android/hid.c b/android/hid.c
index 7f9e386..0a26bfe 100644
--- a/android/hid.c
+++ b/android/hid.c
@@ -278,6 +278,28 @@ static uint8_t bt_hid_connect(struct hal_cmd_hid_connect *cmd, uint16_t len)
return HAL_STATUS_SUCCESS;
}
+static uint8_t bt_hid_disconnect(struct hal_cmd_hid_disconnect *cmd,
+ uint16_t len)
+{
+ GSList *l;
+ bdaddr_t dst;
+
+ DBG("");
+
+ if (len < sizeof(*cmd))
+ return HAL_STATUS_INVALID;
+
+ android2bdaddr((bdaddr_t *)&cmd->bdaddr, &dst);
+
+ l = g_slist_find_custom(devices, &dst, device_cmp);
+ if (!l)
+ return HAL_STATUS_FAILED;
+
+ hid_device_free(l->data);
+
+ return HAL_STATUS_SUCCESS;
+}
+
void bt_hid_handle_cmd(GIOChannel *io, uint8_t opcode, void *buf, uint16_t len)
{
uint8_t status = HAL_STATUS_FAILED;
@@ -287,6 +309,7 @@ void bt_hid_handle_cmd(GIOChannel *io, uint8_t opcode, void *buf, uint16_t len)
status = bt_hid_connect(buf, len);
break;
case HAL_OP_HID_DISCONNECT:
+ status = bt_hid_disconnect(buf, len);
break;
default:
DBG("Unhandled command, opcode 0x%x", opcode);
--
1.7.9.5