[PATCH] android: HID host

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Frederic Danis <frederic.danis@xxxxxxxxxxxxxxx>

Basic implementation of HID host for Android.
---
 android/hidhost-device.c | 975 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 975 insertions(+)
 create mode 100644 android/hidhost-device.c

diff --git a/android/hidhost-device.c b/android/hidhost-device.c
new file mode 100644
index 0000000..eb3849d
--- /dev/null
+++ b/android/hidhost-device.c
@@ -0,0 +1,975 @@
+/*
+ *
+ *  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 <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <btio/btio.h>
+
+#include <linux/uhid.h>
+
+#include "log.h"
+#include "src/shared/mgmt.h"
+#include "adapter.h"
+#include "device.h"
+
+#define L2CAP_PSM_HIDP_CTRL	0x11
+#define L2CAP_PSM_HIDP_INTR	0x13
+
+#define UHID_DEVICE_FILE	"/dev/uhid"
+
+#define REPORT_MAP_MAX_SIZE	512
+
+enum reconnect_mode_t {
+	RECONNECT_NONE = 0,
+	RECONNECT_DEVICE,
+	RECONNECT_HOST,
+	RECONNECT_ANY
+};
+
+struct input_device {
+/*	struct btd_service	*service;
+*/
+	struct bt_device	*device;
+/*	char			*path;
+*/
+	bdaddr_t		src;
+	bdaddr_t		dst;
+/*	uint32_t		handle;
+*/
+	GIOChannel		*ctrl_io;
+	GIOChannel		*intr_io;
+	guint			ctrl_watch;
+	guint			intr_watch;
+/*	guint			sec_watch;
+*/
+	guint			uhid_watch_id;
+/*	struct hidp_connadd_req *req;
+*/
+	guint			dc_id;
+/*	bool			disable_sdp;
+	char			*name;
+*/
+	enum reconnect_mode_t	reconnect_mode;
+	guint			reconnect_timer;
+	uint32_t		reconnect_attempt;
+	int			uhid_fd;
+};
+
+static GSList *conns = NULL;
+
+static struct input_device *find_conn(GSList *list,
+						const bdaddr_t *bd_addr)
+{
+	for (; list; list = list->next) {
+		struct input_device *idev = list->data;
+
+		if (bacmp(bd_addr, bt_device_get_address(idev->device)))
+			return idev;
+	}
+
+	return NULL;
+}
+
+static void input_device_enter_reconnect_mode(struct input_device *idev);
+
+static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct input_device *idev = data;
+	char address[18];
+
+	ba2str(&idev->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)) && idev->ctrl_watch)
+		g_io_channel_shutdown(chan, TRUE, NULL);
+
+	bt_device_remove_disconnect_watch(idev->device, idev->dc_id);
+	idev->dc_id = 0;
+
+	idev->intr_watch = 0;
+
+	if (idev->intr_io) {
+		g_io_channel_unref(idev->intr_io);
+		idev->intr_io = NULL;
+	}
+
+	/* Close control channel */
+	if (idev->ctrl_io && !(cond & G_IO_NVAL))
+		g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL);
+
+	/* TODO:
+	btd_service_disconnecting_complete(idev->service, 0);
+	*/
+
+	/* Enter the auto-reconnect mode if needed */
+	input_device_enter_reconnect_mode(idev);
+
+	return FALSE;
+}
+
+static void ctrl_disconnected(struct input_device *idev, GIOChannel *chan,
+							GIOCondition cond)
+{
+	char address[18];
+
+	ba2str(&idev->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)) && idev->intr_watch)
+		g_io_channel_shutdown(chan, TRUE, NULL);
+
+	idev->ctrl_watch = 0;
+
+	if (idev->ctrl_io) {
+		g_io_channel_unref(idev->ctrl_io);
+		idev->ctrl_io = NULL;
+	}
+
+	/* Close interrupt channel */
+	if (idev->intr_io && !(cond & G_IO_NVAL))
+		g_io_channel_shutdown(idev->intr_io, TRUE, NULL);
+}
+
+static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct input_device *idev = data;
+#if 0
+	char address[18];
+
+	ba2str(&idev->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)) && idev->intr_watch)
+		g_io_channel_shutdown(chan, TRUE, NULL);
+
+	idev->ctrl_watch = 0;
+
+	if (idev->ctrl_io) {
+		g_io_channel_unref(idev->ctrl_io);
+		idev->ctrl_io = NULL;
+	}
+
+	/* Close interrupt channel */
+	if (idev->intr_io && !(cond & G_IO_NVAL))
+		g_io_channel_shutdown(idev->intr_io, TRUE, NULL);
+
+	return FALSE;
+#else
+	if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+		/* TODO: */
+		;
+	}
+
+	return TRUE;
+#endif
+}
+
+#ifndef STORAGEDIR
+#define STORAGEDIR "/var/lib/bluetooth"
+#endif
+
+sdp_record_t *record_from_string(const char *str)
+{
+	sdp_record_t *rec;
+	int size, i, len;
+	uint8_t *pdata;
+	char tmp[3];
+
+	size = strlen(str)/2;
+	pdata = g_malloc0(size);
+
+	tmp[2] = 0;
+	for (i = 0; i < size; i++) {
+		memcpy(tmp, str + (i * 2), 2);
+		pdata[i] = (uint8_t) strtol(tmp, NULL, 16);
+	}
+
+	rec = sdp_extract_pdu(pdata, size, &len);
+	g_free(pdata);
+
+	return rec;
+}
+
+#define HIDP_VIRTUAL_CABLE_UNPLUG	0
+#define HIDP_BOOT_PROTOCOL_MODE		1
+#define HIDP_BLUETOOTH_VENDOR_ID	9
+
+struct uhid_connadd_req {
+/*	int ctrl_sock;		/* Connected control socket */
+/*	int intr_sock;		/* Connected interrupt socket */
+	uint16_t parser;	/* Parser version */
+	uint16_t rd_size;	/* Report descriptor size */
+	uint8_t *rd_data;	/* Report descriptor data */
+	uint8_t  country;
+	uint8_t  subclass;
+	uint16_t vendor;
+	uint16_t product;
+/*	uint16_t version;
+*/
+	uint32_t flags;
+/*	uint32_t idle_to;
+*/
+	char name[128];		/* Device name */
+};
+
+static void epox_endian_quirk(unsigned char *data, int size)
+{
+	/* USAGE_PAGE (Keyboard)	05 07
+	 * USAGE_MINIMUM (0)		19 00
+	 * USAGE_MAXIMUM (65280)	2A 00 FF   <= must be FF 00
+	 * LOGICAL_MINIMUM (0)		15 00
+	 * LOGICAL_MAXIMUM (65280)	26 00 FF   <= must be FF 00
+	 */
+	unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff,
+						0x15, 0x00, 0x26, 0x00, 0xff };
+	unsigned int i;
+
+	if (!data)
+		return;
+
+	for (i = 0; i < size - sizeof(pattern); i++) {
+		if (!memcmp(data + i, pattern, sizeof(pattern))) {
+			data[i + 5] = 0xff;
+			data[i + 6] = 0x00;
+			data[i + 10] = 0xff;
+			data[i + 11] = 0x00;
+		}
+	}
+}
+
+static int create_hid_dev_name(sdp_record_t *rec, struct uhid_connadd_req *req)
+{
+	char sdesc[sizeof(req->name)];
+
+	if (sdp_get_service_desc(rec, sdesc, sizeof(sdesc)) == 0) {
+		char pname[sizeof(req->name)];
+
+		if (sdp_get_provider_name(rec, pname, sizeof(pname)) == 0 &&
+						strncmp(sdesc, pname, 5) != 0)
+			snprintf(req->name, sizeof(req->name), "%s %s", pname,
+									sdesc);
+		else
+			snprintf(req->name, sizeof(req->name), "%s", sdesc);
+	} else {
+		return sdp_get_service_name(rec, req->name, sizeof(req->name));
+	}
+
+	return 0;
+}
+
+/* See HID profile specification v1.0, "7.11.6 HIDDescriptorList" for details
+ * on the attribute format. */
+static int extract_hid_desc_data(sdp_record_t *rec,
+						struct uhid_connadd_req *req)
+{
+	sdp_data_t *d;
+
+	d = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST);
+	if (!d)
+		goto invalid_desc;
+
+	if (!SDP_IS_SEQ(d->dtd))
+		goto invalid_desc;
+
+	/* First HIDDescriptor */
+	d = d->val.dataseq;
+	if (!SDP_IS_SEQ(d->dtd))
+		goto invalid_desc;
+
+	/* ClassDescriptorType */
+	d = d->val.dataseq;
+	if (d->dtd != SDP_UINT8)
+		goto invalid_desc;
+
+	/* ClassDescriptorData */
+	d = d->next;
+	if (!d || !SDP_IS_TEXT_STR(d->dtd))
+		goto invalid_desc;
+
+	req->rd_data = g_try_malloc0(d->unitSize);
+	if (req->rd_data) {
+		memcpy(req->rd_data, d->val.str, d->unitSize);
+		req->rd_size = d->unitSize;
+		epox_endian_quirk(req->rd_data, req->rd_size);
+	}
+
+	return 0;
+
+invalid_desc:
+	error("Missing or invalid HIDDescriptorList SDP attribute");
+	return -EINVAL;
+}
+
+static int extract_hid_record(sdp_record_t *rec, struct uhid_connadd_req *req)
+{
+	sdp_data_t *pdlist;
+	uint8_t attr_val;
+	int err;
+
+	err = create_hid_dev_name(rec, req);
+	if (err < 0)
+		DBG("No valid Service Name or Service Description found");
+
+	pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION);
+	req->parser = pdlist ? pdlist->val.uint16 : 0x0100;
+
+	pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS);
+	req->subclass = pdlist ? pdlist->val.uint8 : 0;
+
+	pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE);
+	req->country = pdlist ? pdlist->val.uint8 : 0;
+
+	pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE);
+	attr_val = pdlist ? pdlist->val.uint8 : 0;
+	if (attr_val)
+		req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG);
+
+	pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE);
+	attr_val = pdlist ? pdlist->val.uint8 : 0;
+	if (attr_val)
+		req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE);
+
+	err = extract_hid_desc_data(rec, req);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int uhid_add_connection(struct input_device *idev)
+{
+	struct uhid_connadd_req *req;
+	sdp_record_t *rec;
+	char src_addr[18], dst_addr[18];
+	char filename[PATH_MAX + 1];
+	GKeyFile *key_file;
+	char handle[11], *str;
+	uint8_t value[REPORT_MAP_MAX_SIZE];
+	struct uhid_event ev;
+	int i;
+	int err;
+
+	/* TODO: */
+#if 1
+	req = g_new0(struct uhid_connadd_req, 1);
+
+	ba2str(&idev->src, src_addr);
+	ba2str(&idev->dst, dst_addr);
+
+	snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", src_addr,
+								dst_addr);
+	filename[PATH_MAX] = '\0';
+#if 0
+	sprintf(handle, "0x%8.8X", idev->handle);
+#else
+	sprintf(handle, "0x00010000");
+#endif
+
+	key_file = g_key_file_new();
+	g_key_file_load_from_file(key_file, filename, 0, NULL);
+	str = g_key_file_get_string(key_file, "ServiceRecords", handle, NULL);
+	g_key_file_free(key_file);
+
+	if (!str) {
+		error("Rejected connection from unknown device %s", dst_addr);
+#if 0
+		err = -EPERM;
+		goto cleanup;
+#else
+		return -1;
+#endif
+	}
+
+	rec = record_from_string(str);
+	g_free(str);
+
+	err = extract_hid_record(rec, req);
+	sdp_record_free(rec);
+	if (err < 0) {
+		error("Could not parse HID SDP record: %s (%d)", strerror(-err),
+									-err);
+#if 0
+		goto cleanup;
+#else
+		return -1;
+#endif
+	}
+#endif
+
+	DBG("Report MAP:");
+	for (i = 0; i < req->rd_size; i++) {
+#if 0
+		switch (value[i]) {
+		case 0x85:
+		case 0x86:
+		case 0x87:
+			hogdev->has_report_id = TRUE;
+		}
+#endif
+
+		if (i % 2 == 0) {
+			if (i + 1 == req->rd_size)
+				DBG("\t %02x", req->rd_data[i]);
+			else
+				DBG("\t %02x %02x", req->rd_data[i],
+							req->rd_data[i + 1]);
+		}
+	}
+
+#if 0
+	vendor_src = btd_device_get_vendor_src(idev->device);
+	vendor = btd_device_get_vendor(idev->device);
+	product = btd_device_get_product(idev->device);
+	version = btd_device_get_version(idev->device);
+/*	DBG("DIS information: vendor_src=0x%X, vendor=0x%X, product=0x%X, "
+			"version=0x%X",	vendor_src, vendor, product, version);
+*/
+#else
+	DBG("DIS information: vendor=0x%X, product=0x%X, version=0x%X",
+					req->vendor, req->product, req->parser);
+#endif
+
+	/* create uHID device */
+	memset(&ev, 0, sizeof(ev));
+	ev.type = UHID_CREATE;
+	strcpy((char *) ev.u.create.name, "bluez-hid-device");
+#if 0
+	ev.u.create.vendor = vendor;
+	ev.u.create.product = product;
+	ev.u.create.version = version;
+	ev.u.create.country = hogdev->bcountrycode;
+#else
+	ev.u.create.vendor = req->vendor;
+	ev.u.create.product = req->product;
+	ev.u.create.version = req->parser;
+	ev.u.create.country = req->country;
+#endif
+	ev.u.create.bus = BUS_BLUETOOTH;
+	ev.u.create.rd_data = req->rd_data;
+	ev.u.create.rd_size = req->rd_size;
+
+	if (write(idev->uhid_fd, &ev, sizeof(ev)) < 0)
+		error("Failed to create uHID device: %s", strerror(errno));
+
+	return 0;
+}
+
+static int is_connected(struct input_device *idev)
+{
+	/* TODO: */
+#if 0
+	struct hidp_conninfo ci;
+	int ctl;
+
+	/* Standard HID */
+	ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+	if (ctl < 0)
+		return 0;
+
+	memset(&ci, 0, sizeof(ci));
+	bacpy(&ci.bdaddr, &idev->dst);
+	if (ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) {
+		close(ctl);
+		return 0;
+	}
+
+	close(ctl);
+
+	if (ci.state != BT_CONNECTED)
+		return 0;
+	else
+		return 1;
+#else
+	return 0;
+#endif
+}
+
+static void disconnect_cb(struct bt_device *device, gboolean removal,
+				void *user_data)
+{
+	/* TODO: */
+#if 0
+	struct input_device *idev = user_data;
+	int flags;
+
+	info("Input: disconnect %s", idev->path);
+
+	flags = removal ? (1 << HIDP_VIRTUAL_CABLE_UNPLUG) : 0;
+
+	connection_disconnect(idev, flags);
+#endif
+}
+
+static int input_device_connected(struct input_device *idev)
+{
+	int err;
+
+	if (idev->intr_io == NULL || idev->ctrl_io == NULL)
+		return -ENOTCONN;
+
+	err = uhid_add_connection(idev);
+	if (err < 0)
+		return err;
+
+	idev->dc_id = bt_device_add_disconnect_watch(idev->device,
+							disconnect_cb,
+							idev, NULL);
+
+	/* TODO:
+	btd_service_connecting_complete(idev->service, 0);
+	*/
+
+	return 0;
+}
+
+static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err,
+							gpointer user_data)
+{
+	struct input_device *idev = user_data;
+	int err;
+
+	if (conn_err) {
+		err = -EIO;
+		goto failed;
+	}
+
+	err = input_device_connected(idev);
+	if (err < 0)
+		goto failed;
+
+	idev->intr_watch = g_io_add_watch(idev->intr_io,
+					G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+					intr_watch_cb, idev);
+
+	return;
+
+failed:
+	/* TODO:
+	btd_service_connecting_complete(idev->service, err);
+	*/
+
+	/* 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(idev->intr_io, FALSE, NULL);
+
+	g_io_channel_unref(idev->intr_io);
+	idev->intr_io = NULL;
+
+	if (idev->ctrl_io) {
+		g_io_channel_unref(idev->ctrl_io);
+		idev->ctrl_io = NULL;
+	}
+}
+
+static void control_connect_cb(GIOChannel *chan, GError *conn_err,
+							gpointer user_data)
+{
+	struct input_device *idev = user_data;
+	GIOChannel *io;
+	GError *err = NULL;
+
+	if (conn_err) {
+		error("%s", conn_err->message);
+		goto failed;
+	}
+
+	/* Connect to the HID interrupt channel */
+	io = bt_io_connect(interrupt_connect_cb, idev,
+				NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+				BT_IO_OPT_DEST_BDADDR, &idev->dst,
+				BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+		goto failed;
+	}
+
+	idev->intr_io = io;
+
+	idev->ctrl_watch = g_io_add_watch(idev->ctrl_io,
+					G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+					ctrl_watch_cb, idev);
+
+	return;
+
+failed:
+	/* TODO:
+	btd_service_connecting_complete(idev->service, -EIO);
+	*/
+	g_io_channel_unref(idev->ctrl_io);
+	idev->ctrl_io = NULL;
+}
+
+static int dev_connect(struct input_device *idev)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	/* TODO: */
+#if 0
+	if (idev->disable_sdp)
+		bt_clear_cached_session(&idev->src, &idev->dst);
+#endif
+
+	io = bt_io_connect(control_connect_cb, idev,
+			NULL, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+			BT_IO_OPT_DEST_BDADDR, &idev->dst,
+			BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
+			BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+			BT_IO_OPT_INVALID);
+	idev->ctrl_io = io;
+
+	if (err == NULL)
+		return 0;
+
+	error("%s", err->message);
+	g_error_free(err);
+
+	return -EIO;
+}
+
+static gboolean input_device_auto_reconnect(gpointer user_data)
+{
+	struct input_device *idev = user_data;
+
+	/* TODO: */
+#if 0
+	DBG("path=%s, attempt=%d", idev->path, idev->reconnect_attempt);
+
+	/* Stop the recurrent reconnection attempts if the device is reconnected
+	 * or is marked for removal. */
+	if (device_is_temporary(idev->device) ||
+					device_is_connected(idev->device))
+		return FALSE;
+#else
+	DBG("attempt=%d", idev->reconnect_attempt);
+#endif
+
+	/* Only attempt an auto-reconnect for at most 3 minutes (6 * 30s). */
+	if (idev->reconnect_attempt >= 6)
+		return FALSE;
+
+	/* Check if the profile is already connected. */
+	if (idev->ctrl_io)
+		return FALSE;
+
+	if (is_connected(idev))
+		return FALSE;
+
+	idev->reconnect_attempt++;
+	dev_connect(idev);
+
+	return TRUE;
+}
+
+static const char * const _reconnect_mode_str[] = {
+	"none",
+	"device",
+	"host",
+	"any"
+};
+
+static const char *reconnect_mode_to_string(const enum reconnect_mode_t mode)
+{
+	return _reconnect_mode_str[mode];
+}
+
+static void input_device_enter_reconnect_mode(struct input_device *idev)
+{
+#if 0
+	DBG("path=%s reconnect_mode=%s", idev->path,
+				reconnect_mode_to_string(idev->reconnect_mode));
+#else
+	DBG("reconnect_mode=%s",
+		reconnect_mode_to_string(idev->reconnect_mode));
+#endif
+
+	/* Only attempt an auto-reconnect when the device is required to accept
+	 * reconnections from the host. */
+	if (idev->reconnect_mode != RECONNECT_ANY &&
+				idev->reconnect_mode != RECONNECT_HOST)
+		return;
+
+	/* TODO: */
+#if 0
+	/* If the device is temporary we are not required to reconnect with the
+	 * device. This is likely the case of a removing device. */
+	if (device_is_temporary(idev->device) ||
+					device_is_connected(idev->device))
+		return;
+#endif
+
+	if (idev->reconnect_timer > 0)
+		g_source_remove(idev->reconnect_timer);
+
+	DBG("registering auto-reconnect");
+	idev->reconnect_attempt = 0;
+	idev->reconnect_timer = g_timeout_add_seconds(30,
+					input_device_auto_reconnect, idev);
+
+}
+
+int input_device_connect(bdaddr_t *bd_addr)
+{
+	struct input_device *idev;
+
+	DBG("");
+
+	idev = find_conn(conns, bd_addr);
+
+	if (idev->ctrl_io)
+		return -EBUSY;
+
+	if (is_connected(idev))
+		return -EALREADY;
+
+	return dev_connect(idev);
+}
+
+static void forward_report(struct input_device *idev,
+						struct uhid_event *ev)
+{
+	/* TODO: */
+#if 0
+	struct report *report;
+	GSList *l;
+	void *data;
+	int size;
+	guint type;
+
+	if (idev->has_report_id) {
+		data = ev->u.output.data + 1;
+		size = ev->u.output.size - 1;
+	} else {
+		data = ev->u.output.data;
+		size = ev->u.output.size;
+	}
+
+	switch (ev->type) {
+	case UHID_OUTPUT:
+		type = HOG_REPORT_TYPE_OUTPUT;
+		break;
+	case UHID_FEATURE:
+		type = HOG_REPORT_TYPE_FEATURE;
+		break;
+	default:
+		return;
+	}
+
+	l = g_slist_find_custom(hogdev->reports, GUINT_TO_POINTER(type),
+							report_type_cmp);
+	if (!l)
+		return;
+
+	report = l->data;
+
+	DBG("Sending report type %d to device 0x%04X handle 0x%X", type,
+			idev->id, report->decl->value_handle);
+
+	if (idev->attrib == NULL)
+		return;
+
+	if (report->decl->properties & ATT_CHAR_PROPER_WRITE)
+		gatt_write_char(idev->attrib, report->decl->value_handle,
+				data, size, output_written_cb, idev);
+	else if (report->decl->properties & ATT_CHAR_PROPER_WRITE_WITHOUT_RESP)
+		gatt_write_cmd(idev->attrib, report->decl->value_handle,
+						data, size, NULL, NULL);
+#endif
+}
+
+static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	struct input_device *idev = user_data;
+	struct uhid_event ev;
+	ssize_t bread;
+	int fd;
+
+	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) {
+		int err = -errno;
+		DBG("uhid-dev read: %s(%d)", strerror(-err), -err);
+		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.
+		 * For HoG we open the channels before creating the device so
+		 * the hardware is always ready. No need to handle these.
+		 * Note that these are also called when the kernel switches
+		 * between device-drivers loaded on the HID device. But we can
+		 * simply keep the hardware alive during transitions and it
+		 * works just fine.
+		 * 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. Moreover, some
+		 * special device drivers are buggy in that regard, so
+		 * maybe we just keep I/O always awake like HIDP in the
+		 * kernel does. */
+		break;
+	case UHID_OUTPUT:
+	case UHID_FEATURE:
+		forward_report(idev, &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.
+		 * We never implemented this, so we rely on users to use
+		 * recent-enough kernels if they want this feature. No reason
+		 * to implement this for older kernels. */
+		DBG("Unsupported uHID output event: type %d code %d value %d",
+			ev.u.output_ev.type, ev.u.output_ev.code,
+			ev.u.output_ev.value);
+		break;
+	default:
+		warn("unexpected uHID event");
+		break;
+	}
+
+	return TRUE;
+
+failed:
+	idev->uhid_watch_id = 0;
+	return FALSE;
+}
+
+static struct input_device *input_new_device(struct bt_device *device)
+{
+	struct input_device *idev;
+
+	idev = g_try_new0(struct input_device, 1);
+	if (!idev)
+		return NULL;
+
+	idev->device = bt_device_ref(device);
+
+	return idev;
+}
+
+static void input_free_device(struct input_device *idev)
+{
+	bt_device_unref(idev->device);
+/*	g_slist_free_full(idev->reports, report_free);
+*/	g_free(idev);
+}
+
+struct input_device *input_register_device(struct bt_device *device)
+{
+	struct input_device *idev;
+	GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+	GIOChannel *io;
+
+	idev = input_new_device(device);
+	if (!idev)
+		return NULL;
+
+	idev->uhid_fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC);
+	if (idev->uhid_fd < 0) {
+		error("Failed to open uHID device: %s(%d)", strerror(errno),
+									errno);
+		input_free_device(idev);
+		return NULL;
+	}
+
+	io = g_io_channel_unix_new(idev->uhid_fd);
+	g_io_channel_set_encoding(io, NULL, NULL);
+	idev->uhid_watch_id = g_io_add_watch(io, cond, uhid_event_cb, idev);
+	g_io_channel_unref(io);
+
+	return idev;
+}
+
+int input_unregister_device(struct input_device *idev)
+{
+	struct uhid_event ev;
+
+	if (idev->uhid_watch_id) {
+		g_source_remove(idev->uhid_watch_id);
+		idev->uhid_watch_id = 0;
+	}
+
+	memset(&ev, 0, sizeof(ev));
+	ev.type = UHID_DESTROY;
+	if (write(idev->uhid_fd, &ev, sizeof(ev)) < 0)
+		error("Failed to destroy uHID device: %s", strerror(errno));
+
+	close(idev->uhid_fd);
+	idev->uhid_fd = -1;
+
+	input_free_device(idev);
+
+	return 0;
+}
-- 
1.8.1.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




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux