Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e1aaadd4 authored by Marcel Holtmann's avatar Marcel Holtmann Committed by David S. Miller
Browse files

[Bluetooth] Add support for using the HID subsystem



This patch extends the current Bluetooth HID support to use the new
HID subsystem and adds full report mode support.

Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent a83d6c0d
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
config BT_HIDP
config BT_HIDP
	tristate "HIDP protocol support"
	tristate "HIDP protocol support"
	depends on BT && BT_L2CAP && INPUT
	depends on BT && BT_L2CAP && INPUT
	select HID
	help
	help
	  HIDP (Human Interface Device Protocol) is a transport layer
	  HIDP (Human Interface Device Protocol) is a transport layer
	  for HID reports.  HIDP is required for the Bluetooth Human
	  for HID reports.  HIDP is required for the Bluetooth Human
+161 −9
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@
#include <net/sock.h>
#include <net/sock.h>


#include <linux/input.h>
#include <linux/input.h>
#include <linux/hid.h>


#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/hci_core.h>
@@ -50,7 +51,7 @@
#define BT_DBG(D...)
#define BT_DBG(D...)
#endif
#endif


#define VERSION "1.1"
#define VERSION "1.2"


static DECLARE_RWSEM(hidp_session_sem);
static DECLARE_RWSEM(hidp_session_sem);
static LIST_HEAD(hidp_session_list);
static LIST_HEAD(hidp_session_list);
@@ -124,15 +125,22 @@ static void __hidp_copy_session(struct hidp_session *session, struct hidp_connin
		else
		else
			strncpy(ci->name, "HID Boot Device", 128);
			strncpy(ci->name, "HID Boot Device", 128);
	}
	}

	if (session->hid) {
		ci->vendor  = session->hid->vendor;
		ci->product = session->hid->product;
		ci->version = session->hid->version;
		strncpy(ci->name, session->hid->name, 128);
	}
}
}


static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
static inline int hidp_queue_event(struct hidp_session *session, struct input_dev *dev,
					unsigned int type, unsigned int code, int value)
{
{
	struct hidp_session *session = dev->private;
	struct sk_buff *skb;
	unsigned char newleds;
	unsigned char newleds;
	struct sk_buff *skb;


	BT_DBG("input %p type %d code %d value %d", dev, type, code, value);
	BT_DBG("session %p type %d code %d value %d", session, type, code, value);


	if (type != EV_LED)
	if (type != EV_LED)
		return -1;
		return -1;
@@ -164,6 +172,21 @@ static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned i
	return 0;
	return 0;
}
}


static int hidp_hidinput_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	struct hid_device *hid = dev->private;
	struct hidp_session *session = hid->driver_data;

	return hidp_queue_event(session, dev, type, code, value);
}

static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	struct hidp_session *session = dev->private;

	return hidp_queue_event(session, dev, type, code, value);
}

static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
{
{
	struct input_dev *dev = session->input;
	struct input_dev *dev = session->input;
@@ -219,6 +242,42 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
	input_sync(dev);
	input_sync(dev);
}
}


static inline int hidp_queue_report(struct hidp_session *session, unsigned char *data, int size)
{
	struct sk_buff *skb;

	BT_DBG("session %p hid %p data %p size %d", session, device, data, size);

	if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) {
		BT_ERR("Can't allocate memory for new frame");
		return -ENOMEM;
	}

	*skb_put(skb, 1) = 0xa2;
	if (size > 0)
		memcpy(skb_put(skb, size), data, size);

	skb_queue_tail(&session->intr_transmit, skb);

	hidp_schedule(session);

	return 0;
}

static int hidp_send_report(struct hidp_session *session, struct hid_report *report)
{
	unsigned char buf[32];
	int rsize;

	rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0);
	if (rsize > sizeof(buf))
		return -EIO;

	hid_output_report(report, buf);

	return hidp_queue_report(session, buf, rsize);
}

static void hidp_idle_timeout(unsigned long arg)
static void hidp_idle_timeout(unsigned long arg)
{
{
	struct hidp_session *session = (struct hidp_session *) arg;
	struct hidp_session *session = (struct hidp_session *) arg;
@@ -346,6 +405,10 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf


		if (session->input)
		if (session->input)
			hidp_input_report(session, skb);
			hidp_input_report(session, skb);

		if (session->hid)
			hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0);

		break;
		break;


	case HIDP_DATA_RTYPE_OTHER:
	case HIDP_DATA_RTYPE_OTHER:
@@ -404,8 +467,14 @@ static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_


	if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
	if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
		hidp_set_timer(session);
		hidp_set_timer(session);

		if (session->input)
		if (session->input)
			hidp_input_report(session, skb);
			hidp_input_report(session, skb);

		if (session->hid) {
			hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1);
			BT_DBG("report len %d", skb->len);
		}
	} else {
	} else {
		BT_DBG("Unsupported protocol header 0x%02x", hdr);
		BT_DBG("Unsupported protocol header 0x%02x", hdr);
	}
	}
@@ -471,6 +540,11 @@ static int hidp_session(void *arg)
		product = session->input->id.product;
		product = session->input->id.product;
	}
	}


	if (session->hid) {
		vendor  = session->hid->vendor;
		product = session->hid->product;
	}

	daemonize("khidpd_%04x%04x", vendor, product);
	daemonize("khidpd_%04x%04x", vendor, product);
	set_user_nice(current, -15);
	set_user_nice(current, -15);
	current->flags |= PF_NOFREEZE;
	current->flags |= PF_NOFREEZE;
@@ -521,6 +595,12 @@ static int hidp_session(void *arg)
		session->input = NULL;
		session->input = NULL;
	}
	}


	if (session->hid) {
		if (session->hid->claimed & HID_CLAIMED_INPUT)
			hidinput_disconnect(session->hid);
		hid_free_device(session->hid);
	}

	up_write(&hidp_session_sem);
	up_write(&hidp_session_sem);


	kfree(session);
	kfree(session);
@@ -590,6 +670,44 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co
	input_register_device(input);
	input_register_device(input);
}
}


static inline void hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req)
{
	struct hid_device *hid = session->hid;
	struct hid_report *report;
	bdaddr_t src, dst;

	baswap(&src, &bt_sk(session->ctrl_sock->sk)->src);
	baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst);

	hid->driver_data = session;

	hid->country = req->country;

	hid->bus     = BUS_BLUETOOTH;
	hid->vendor  = req->vendor;
	hid->product = req->product;
	hid->version = req->version;

	strncpy(hid->name, req->name, 128);
	strncpy(hid->phys, batostr(&src), 64);
	strncpy(hid->uniq, batostr(&dst), 64);

	hid->dev = hidp_get_device(session);

	hid->hidinput_input_event = hidp_hidinput_event;

	list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
		hidp_send_report(session, report);

	list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
		hidp_send_report(session, report);

	if (hidinput_connect(hid) == 0) {
		hid->claimed |= HID_CLAIMED_INPUT;
		hid_ff_init(hid);
	}
}

int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock)
int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock)
{
{
	struct hidp_session *session, *s;
	struct hidp_session *session, *s;
@@ -605,11 +723,39 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
	if (!session)
	if (!session)
		return -ENOMEM;
		return -ENOMEM;


	BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size);

	if (req->rd_size > 0) {
		unsigned char *buf = kmalloc(req->rd_size, GFP_KERNEL);

		if (!buf) {
			kfree(session);
			return -ENOMEM;
		}

		if (copy_from_user(buf, req->rd_data, req->rd_size)) {
			kfree(buf);
			kfree(session);
			return -EFAULT;
		}

		session->hid = hid_parse_report(buf, req->rd_size);

		kfree(buf);

		if (!session->hid) {
			kfree(session);
			return -EINVAL;
		}
	}

	if (!session->hid) {
		session->input = input_allocate_device();
		session->input = input_allocate_device();
		if (!session->input) {
		if (!session->input) {
			kfree(session);
			kfree(session);
			return -ENOMEM;
			return -ENOMEM;
		}
		}
	}


	down_write(&hidp_session_sem);
	down_write(&hidp_session_sem);


@@ -644,6 +790,9 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
	if (session->input)
	if (session->input)
		hidp_setup_input(session, req);
		hidp_setup_input(session, req);


	if (session->hid)
		hidp_setup_hid(session, req);

	__hidp_link_session(session);
	__hidp_link_session(session);


	hidp_set_timer(session);
	hidp_set_timer(session);
@@ -677,6 +826,9 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
failed:
failed:
	up_write(&hidp_session_sem);
	up_write(&hidp_session_sem);


	if (session->hid)
		hid_free_device(session->hid);

	kfree(session->input);
	kfree(session->input);
	kfree(session);
	kfree(session);
	return err;
	return err;
+2 −0
Original line number Original line Diff line number Diff line
@@ -145,6 +145,8 @@ struct hidp_session {


	struct input_dev *input;
	struct input_dev *input;


	struct hid_device *hid;

	struct timer_list timer;
	struct timer_list timer;


	struct sk_buff_head ctrl_transmit;
	struct sk_buff_head ctrl_transmit;