diff options
Diffstat (limited to 'net/bluetooth/hidp/core.c')
| -rw-r--r-- | net/bluetooth/hidp/core.c | 1326 | 
1 files changed, 858 insertions, 468 deletions
diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index c0ee8b3928e..8181ea4bc2f 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -1,6 +1,7 @@  /*     HIDP implementation for Linux Bluetooth stack (BlueZ).     Copyright (C) 2003-2004 Marcel Holtmann <marcel@holtmann.org> +   Copyright (C) 2013 David Herrmann <dh.herrmann@gmail.com>     This program is free software; you can redistribute it and/or modify     it under the terms of the GNU General Public License version 2 as @@ -20,26 +21,10 @@     SOFTWARE IS DISCLAIMED.  */ +#include <linux/kref.h>  #include <linux/module.h> - -#include <linux/types.h> -#include <linux/errno.h> -#include <linux/kernel.h> -#include <linux/sched.h> -#include <linux/slab.h> -#include <linux/poll.h> -#include <linux/freezer.h> -#include <linux/fcntl.h> -#include <linux/skbuff.h> -#include <linux/socket.h> -#include <linux/ioctl.h>  #include <linux/file.h> -#include <linux/init.h> -#include <linux/wait.h> -#include <net/sock.h> - -#include <linux/input.h> -#include <linux/hid.h> +#include <linux/kthread.h>  #include <linux/hidraw.h>  #include <net/bluetooth/bluetooth.h> @@ -54,138 +39,133 @@ static DECLARE_RWSEM(hidp_session_sem);  static LIST_HEAD(hidp_session_list);  static unsigned char hidp_keycode[256] = { -	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, -	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3, -	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26, -	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, -	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, -	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, -	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, -	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, -	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0, -	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, -	150,158,159,128,136,177,178,176,142,152,173,140 +	  0,   0,   0,   0,  30,  48,  46,  32,  18,  33,  34,  35,  23,  36, +	 37,  38,  50,  49,  24,  25,  16,  19,  31,  20,  22,  47,  17,  45, +	 21,  44,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  28,   1, +	 14,  15,  57,  12,  13,  26,  27,  43,  43,  39,  40,  41,  51,  52, +	 53,  58,  59,  60,  61,  62,  63,  64,  65,  66,  67,  68,  87,  88, +	 99,  70, 119, 110, 102, 104, 111, 107, 109, 106, 105, 108, 103,  69, +	 98,  55,  74,  78,  96,  79,  80,  81,  75,  76,  77,  71,  72,  73, +	 82,  83,  86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190, +	191, 192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, +	136, 113, 115, 114,   0,   0,   0, 121,   0,  89,  93, 124,  92,  94, +	 95,   0,   0,   0, 122, 123,  90,  91,  85,   0,   0,   0,   0,   0, +	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, +	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, +	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, +	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, +	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, +	 29,  42,  56, 125,  97,  54, 100, 126, 164, 166, 165, 163, 161, 115, +	114, 113, 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140  };  static unsigned char hidp_mkeyspat[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; -static struct hidp_session *__hidp_get_session(bdaddr_t *bdaddr) -{ -	struct hidp_session *session; -	struct list_head *p; +static int hidp_session_probe(struct l2cap_conn *conn, +			      struct l2cap_user *user); +static void hidp_session_remove(struct l2cap_conn *conn, +				struct l2cap_user *user); +static int hidp_session_thread(void *arg); +static void hidp_session_terminate(struct hidp_session *s); -	BT_DBG(""); - -	list_for_each(p, &hidp_session_list) { -		session = list_entry(p, struct hidp_session, list); -		if (!bacmp(bdaddr, &session->bdaddr)) -			return session; -	} -	return NULL; -} - -static void __hidp_link_session(struct hidp_session *session) -{ -	__module_get(THIS_MODULE); -	list_add(&session->list, &hidp_session_list); - -	hci_conn_hold_device(session->conn); -} - -static void __hidp_unlink_session(struct hidp_session *session) -{ -	hci_conn_put_device(session->conn); - -	list_del(&session->list); -	module_put(THIS_MODULE); -} - -static void __hidp_copy_session(struct hidp_session *session, struct hidp_conninfo *ci) +static void hidp_copy_session(struct hidp_session *session, struct hidp_conninfo *ci)  { +	memset(ci, 0, sizeof(*ci));  	bacpy(&ci->bdaddr, &session->bdaddr);  	ci->flags = session->flags; -	ci->state = session->state; - -	ci->vendor  = 0x0000; -	ci->product = 0x0000; -	ci->version = 0x0000; -	memset(ci->name, 0, 128); +	ci->state = BT_CONNECTED;  	if (session->input) {  		ci->vendor  = session->input->id.vendor;  		ci->product = session->input->id.product;  		ci->version = session->input->id.version;  		if (session->input->name) -			strncpy(ci->name, session->input->name, 128); +			strlcpy(ci->name, session->input->name, 128);  		else -			strncpy(ci->name, "HID Boot Device", 128); -	} - -	if (session->hid) { +			strlcpy(ci->name, "HID Boot Device", 128); +	} else 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); +		strlcpy(ci->name, session->hid->name, 128);  	}  } -static int hidp_queue_event(struct hidp_session *session, struct input_dev *dev, -				unsigned int type, unsigned int code, int value) +/* assemble skb, queue message on @transmit and wake up the session thread */ +static int hidp_send_message(struct hidp_session *session, struct socket *sock, +			     struct sk_buff_head *transmit, unsigned char hdr, +			     const unsigned char *data, int size)  { -	unsigned char newleds;  	struct sk_buff *skb; +	struct sock *sk = sock->sk; -	BT_DBG("session %p type %d code %d value %d", session, type, code, value); - -	if (type != EV_LED) -		return -1; - -	newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | -		  (!!test_bit(LED_COMPOSE, dev->led) << 3) | -		  (!!test_bit(LED_SCROLLL, dev->led) << 2) | -		  (!!test_bit(LED_CAPSL,   dev->led) << 1) | -		  (!!test_bit(LED_NUML,    dev->led)); - -	if (session->leds == newleds) -		return 0; +	BT_DBG("session %p data %p size %d", session, data, size); -	session->leds = newleds; +	if (atomic_read(&session->terminate)) +		return -EIO; -	if (!(skb = alloc_skb(3, GFP_ATOMIC))) { +	skb = alloc_skb(size + 1, GFP_ATOMIC); +	if (!skb) {  		BT_ERR("Can't allocate memory for new frame");  		return -ENOMEM;  	} -	*skb_put(skb, 1) = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; -	*skb_put(skb, 1) = 0x01; -	*skb_put(skb, 1) = newleds; - -	skb_queue_tail(&session->intr_transmit, skb); +	*skb_put(skb, 1) = hdr; +	if (data && size > 0) +		memcpy(skb_put(skb, size), data, size); -	hidp_schedule(session); +	skb_queue_tail(transmit, skb); +	wake_up_interruptible(sk_sleep(sk));  	return 0;  } -static int hidp_hidinput_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +static int hidp_send_ctrl_message(struct hidp_session *session, +				  unsigned char hdr, const unsigned char *data, +				  int size)  { -	struct hid_device *hid = input_get_drvdata(dev); -	struct hidp_session *session = hid->driver_data; +	return hidp_send_message(session, session->ctrl_sock, +				 &session->ctrl_transmit, hdr, data, size); +} -	return hidp_queue_event(session, dev, type, code, value); +static int hidp_send_intr_message(struct hidp_session *session, +				  unsigned char hdr, const unsigned char *data, +				  int size) +{ +	return hidp_send_message(session, session->intr_sock, +				 &session->intr_transmit, hdr, data, size);  } -static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +static int hidp_input_event(struct input_dev *dev, unsigned int type, +			    unsigned int code, int value)  {  	struct hidp_session *session = input_get_drvdata(dev); +	unsigned char newleds; +	unsigned char hdr, data[2]; + +	BT_DBG("session %p type %d code %d value %d", +	       session, type, code, value); + +	if (type != EV_LED) +		return -1; + +	newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | +		  (!!test_bit(LED_COMPOSE, dev->led) << 3) | +		  (!!test_bit(LED_SCROLLL, dev->led) << 2) | +		  (!!test_bit(LED_CAPSL,   dev->led) << 1) | +		  (!!test_bit(LED_NUML,    dev->led)); -	return hidp_queue_event(session, dev, type, code, value); +	if (session->leds == newleds) +		return 0; + +	session->leds = newleds; + +	hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; +	data[0] = 0x01; +	data[1] = newleds; + +	return hidp_send_intr_message(session, hdr, data, 2);  }  static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) @@ -243,102 +223,184 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)  	input_sync(dev);  } -static int __hidp_send_ctrl_message(struct hidp_session *session, -			unsigned char hdr, unsigned char *data, int size) +static int hidp_get_raw_report(struct hid_device *hid, +		unsigned char report_number, +		unsigned char *data, size_t count, +		unsigned char report_type)  { +	struct hidp_session *session = hid->driver_data;  	struct sk_buff *skb; +	size_t len; +	int numbered_reports = hid->report_enum[report_type].numbered; +	int ret; -	BT_DBG("session %p data %p size %d", session, data, size); +	if (atomic_read(&session->terminate)) +		return -EIO; -	if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) { -		BT_ERR("Can't allocate memory for new frame"); -		return -ENOMEM; +	switch (report_type) { +	case HID_FEATURE_REPORT: +		report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE; +		break; +	case HID_INPUT_REPORT: +		report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT; +		break; +	case HID_OUTPUT_REPORT: +		report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUPUT; +		break; +	default: +		return -EINVAL;  	} -	*skb_put(skb, 1) = hdr; -	if (data && size > 0) -		memcpy(skb_put(skb, size), data, size); +	if (mutex_lock_interruptible(&session->report_mutex)) +		return -ERESTARTSYS; -	skb_queue_tail(&session->ctrl_transmit, skb); +	/* Set up our wait, and send the report request to the device. */ +	session->waiting_report_type = report_type & HIDP_DATA_RTYPE_MASK; +	session->waiting_report_number = numbered_reports ? report_number : -1; +	set_bit(HIDP_WAITING_FOR_RETURN, &session->flags); +	data[0] = report_number; +	ret = hidp_send_ctrl_message(session, report_type, data, 1); +	if (ret) +		goto err; -	return 0; -} +	/* Wait for the return of the report. The returned report +	   gets put in session->report_return.  */ +	while (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) && +	       !atomic_read(&session->terminate)) { +		int res; + +		res = wait_event_interruptible_timeout(session->report_queue, +			!test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) +				|| atomic_read(&session->terminate), +			5*HZ); +		if (res == 0) { +			/* timeout */ +			ret = -EIO; +			goto err; +		} +		if (res < 0) { +			/* signal */ +			ret = -ERESTARTSYS; +			goto err; +		} +	} -static inline int hidp_send_ctrl_message(struct hidp_session *session, -			unsigned char hdr, unsigned char *data, int size) -{ -	int err; +	skb = session->report_return; +	if (skb) { +		len = skb->len < count ? skb->len : count; +		memcpy(data, skb->data, len); + +		kfree_skb(skb); +		session->report_return = NULL; +	} else { +		/* Device returned a HANDSHAKE, indicating  protocol error. */ +		len = -EIO; +	} -	err = __hidp_send_ctrl_message(session, hdr, data, size); +	clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); +	mutex_unlock(&session->report_mutex); -	hidp_schedule(session); +	return len; -	return err; +err: +	clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); +	mutex_unlock(&session->report_mutex); +	return ret;  } -static int hidp_queue_report(struct hidp_session *session, -				unsigned char *data, int size) +static int hidp_set_raw_report(struct hid_device *hid, unsigned char reportnum, +			       unsigned char *data, size_t count, +			       unsigned char report_type)  { -	struct sk_buff *skb; - -	BT_DBG("session %p hid %p data %p size %d", session, session->hid, data, size); +	struct hidp_session *session = hid->driver_data; +	int ret; -	if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) { -		BT_ERR("Can't allocate memory for new frame"); -		return -ENOMEM; +	switch (report_type) { +	case HID_FEATURE_REPORT: +		report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE; +		break; +	case HID_INPUT_REPORT: +		report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_INPUT; +		break; +	case HID_OUTPUT_REPORT: +		report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUPUT; +		break; +	default: +		return -EINVAL;  	} -	*skb_put(skb, 1) = 0xa2; -	if (size > 0) -		memcpy(skb_put(skb, size), data, size); +	if (mutex_lock_interruptible(&session->report_mutex)) +		return -ERESTARTSYS; -	skb_queue_tail(&session->intr_transmit, skb); +	/* Set up our wait, and send the report request to the device. */ +	data[0] = reportnum; +	set_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags); +	ret = hidp_send_ctrl_message(session, report_type, data, count); +	if (ret) +		goto err; -	hidp_schedule(session); +	/* Wait for the ACK from the device. */ +	while (test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags) && +	       !atomic_read(&session->terminate)) { +		int res; + +		res = wait_event_interruptible_timeout(session->report_queue, +			!test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags) +				|| atomic_read(&session->terminate), +			10*HZ); +		if (res == 0) { +			/* timeout */ +			ret = -EIO; +			goto err; +		} +		if (res < 0) { +			/* signal */ +			ret = -ERESTARTSYS; +			goto err; +		} +	} -	return 0; -} +	if (!session->output_report_success) { +		ret = -EIO; +		goto err; +	} -static int hidp_send_report(struct hidp_session *session, struct hid_report *report) -{ -	unsigned char buf[32]; -	int rsize; +	ret = count; -	rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0); -	if (rsize > sizeof(buf)) -		return -EIO; +err: +	clear_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags); +	mutex_unlock(&session->report_mutex); +	return ret; +} -	hid_output_report(report, buf); +static int hidp_output_report(struct hid_device *hid, __u8 *data, size_t count) +{ +	struct hidp_session *session = hid->driver_data; -	return hidp_queue_report(session, buf, rsize); +	return hidp_send_intr_message(session, +				      HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT, +				      data, count);  } -static int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, size_t count, -		unsigned char report_type) +static int hidp_raw_request(struct hid_device *hid, unsigned char reportnum, +			    __u8 *buf, size_t len, unsigned char rtype, +			    int reqtype)  { -	switch (report_type) { -	case HID_FEATURE_REPORT: -		report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE; -		break; -	case HID_OUTPUT_REPORT: -		report_type = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; -		break; +	switch (reqtype) { +	case HID_REQ_GET_REPORT: +		return hidp_get_raw_report(hid, reportnum, buf, len, rtype); +	case HID_REQ_SET_REPORT: +		return hidp_set_raw_report(hid, reportnum, buf, len, rtype);  	default: -		return -EINVAL; +		return -EIO;  	} - -	if (hidp_send_ctrl_message(hid->driver_data, report_type, -			data, count)) -		return -ENOMEM; -	return count;  }  static void hidp_idle_timeout(unsigned long arg)  {  	struct hidp_session *session = (struct hidp_session *) arg; -	atomic_inc(&session->terminate); -	hidp_schedule(session); +	hidp_session_terminate(session);  }  static void hidp_set_timer(struct hidp_session *session) @@ -347,26 +409,41 @@ static void hidp_set_timer(struct hidp_session *session)  		mod_timer(&session->timer, jiffies + HZ * session->idle_to);  } -static inline void hidp_del_timer(struct hidp_session *session) +static void hidp_del_timer(struct hidp_session *session)  {  	if (session->idle_to > 0)  		del_timer(&session->timer);  } +static void hidp_process_report(struct hidp_session *session, +				int type, const u8 *data, int len, int intr) +{ +	if (len > HID_MAX_BUFFER_SIZE) +		len = HID_MAX_BUFFER_SIZE; + +	memcpy(session->input_buf, data, len); +	hid_input_report(session->hid, type, session->input_buf, len, intr); +} +  static void hidp_process_handshake(struct hidp_session *session,  					unsigned char param)  {  	BT_DBG("session %p param 0x%02x", session, param); +	session->output_report_success = 0; /* default condition */  	switch (param) {  	case HIDP_HSHK_SUCCESSFUL:  		/* FIXME: Call into SET_ GET_ handlers here */ +		session->output_report_success = 1;  		break;  	case HIDP_HSHK_NOT_READY:  	case HIDP_HSHK_ERR_INVALID_REPORT_ID:  	case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST:  	case HIDP_HSHK_ERR_INVALID_PARAMETER: +		if (test_and_clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) +			wake_up_interruptible(&session->report_queue); +  		/* FIXME: Call into SET_ GET_ handlers here */  		break; @@ -376,15 +453,19 @@ static void hidp_process_handshake(struct hidp_session *session,  	case HIDP_HSHK_ERR_FATAL:  		/* Device requests a reboot, as this is the only way this error  		 * can be recovered. */ -		__hidp_send_ctrl_message(session, +		hidp_send_ctrl_message(session,  			HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0);  		break;  	default: -		__hidp_send_ctrl_message(session, +		hidp_send_ctrl_message(session,  			HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);  		break;  	} + +	/* Wake up the waiting thread. */ +	if (test_and_clear_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags)) +		wake_up_interruptible(&session->report_queue);  }  static void hidp_process_hid_control(struct hidp_session *session, @@ -397,15 +478,15 @@ static void hidp_process_hid_control(struct hidp_session *session,  		skb_queue_purge(&session->ctrl_transmit);  		skb_queue_purge(&session->intr_transmit); -		/* Kill session thread */ -		atomic_inc(&session->terminate); -		hidp_schedule(session); +		hidp_session_terminate(session);  	}  } -static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, +/* Returns true if the passed-in skb should be freed by the caller. */ +static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb,  				unsigned char param)  { +	int done_with_skb = 1;  	BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param);  	switch (param) { @@ -416,8 +497,8 @@ static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb,  			hidp_input_report(session, skb);  		if (session->hid) -			hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); - +			hidp_process_report(session, HID_INPUT_REPORT, +					    skb->data, skb->len, 0);  		break;  	case HIDP_DATA_RTYPE_OTHER: @@ -426,15 +507,30 @@ static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb,  		break;  	default: -		__hidp_send_ctrl_message(session, +		hidp_send_ctrl_message(session,  			HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);  	} + +	if (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) && +				param == session->waiting_report_type) { +		if (session->waiting_report_number < 0 || +		    session->waiting_report_number == skb->data[0]) { +			/* hidp_get_raw_report() is waiting on this report. */ +			session->report_return = skb; +			done_with_skb = 0; +			clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); +			wake_up_interruptible(&session->report_queue); +		} +	} + +	return done_with_skb;  }  static void hidp_recv_ctrl_frame(struct hidp_session *session,  					struct sk_buff *skb)  {  	unsigned char hdr, type, param; +	int free_skb = 1;  	BT_DBG("session %p skb %p len %d", session, skb, skb->len); @@ -454,16 +550,17 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session,  		break;  	case HIDP_TRANS_DATA: -		hidp_process_data(session, skb, param); +		free_skb = hidp_process_data(session, skb, param);  		break;  	default: -		__hidp_send_ctrl_message(session, +		hidp_send_ctrl_message(session,  			HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_UNSUPPORTED_REQUEST, NULL, 0);  		break;  	} -	kfree_skb(skb); +	if (free_skb) +		kfree_skb(skb);  }  static void hidp_recv_intr_frame(struct hidp_session *session, @@ -483,7 +580,8 @@ static void hidp_recv_intr_frame(struct hidp_session *session,  			hidp_input_report(session, skb);  		if (session->hid) { -			hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1); +			hidp_process_report(session, HID_INPUT_REPORT, +					    skb->data, skb->len, 1);  			BT_DBG("report len %d", skb->len);  		}  	} else { @@ -508,25 +606,24 @@ static int hidp_send_frame(struct socket *sock, unsigned char *data, int len)  	return kernel_sendmsg(sock, &msg, &iv, 1, len);  } -static void hidp_process_transmit(struct hidp_session *session) +/* dequeue message from @transmit and send via @sock */ +static void hidp_process_transmit(struct hidp_session *session, +				  struct sk_buff_head *transmit, +				  struct socket *sock)  {  	struct sk_buff *skb; +	int ret;  	BT_DBG("session %p", session); -	while ((skb = skb_dequeue(&session->ctrl_transmit))) { -		if (hidp_send_frame(session->ctrl_sock, skb->data, skb->len) < 0) { -			skb_queue_head(&session->ctrl_transmit, skb); +	while ((skb = skb_dequeue(transmit))) { +		ret = hidp_send_frame(sock, skb->data, skb->len); +		if (ret == -EAGAIN) { +			skb_queue_head(transmit, skb);  			break; -		} - -		hidp_set_timer(session); -		kfree_skb(skb); -	} - -	while ((skb = skb_dequeue(&session->intr_transmit))) { -		if (hidp_send_frame(session->intr_sock, skb->data, skb->len) < 0) { -			skb_queue_head(&session->intr_transmit, skb); +		} else if (ret < 0) { +			hidp_session_terminate(session); +			kfree_skb(skb);  			break;  		} @@ -535,118 +632,11 @@ static void hidp_process_transmit(struct hidp_session *session)  	}  } -static int hidp_session(void *arg) -{ -	struct hidp_session *session = arg; -	struct sock *ctrl_sk = session->ctrl_sock->sk; -	struct sock *intr_sk = session->intr_sock->sk; -	struct sk_buff *skb; -	int vendor = 0x0000, product = 0x0000; -	wait_queue_t ctrl_wait, intr_wait; - -	BT_DBG("session %p", session); - -	if (session->input) { -		vendor  = session->input->id.vendor; -		product = session->input->id.product; -	} - -	if (session->hid) { -		vendor  = session->hid->vendor; -		product = session->hid->product; -	} - -	daemonize("khidpd_%04x%04x", vendor, product); -	set_user_nice(current, -15); - -	init_waitqueue_entry(&ctrl_wait, current); -	init_waitqueue_entry(&intr_wait, current); -	add_wait_queue(sk_sleep(ctrl_sk), &ctrl_wait); -	add_wait_queue(sk_sleep(intr_sk), &intr_wait); -	while (!atomic_read(&session->terminate)) { -		set_current_state(TASK_INTERRUPTIBLE); - -		if (ctrl_sk->sk_state != BT_CONNECTED || intr_sk->sk_state != BT_CONNECTED) -			break; - -		while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) { -			skb_orphan(skb); -			hidp_recv_ctrl_frame(session, skb); -		} - -		while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) { -			skb_orphan(skb); -			hidp_recv_intr_frame(session, skb); -		} - -		hidp_process_transmit(session); - -		schedule(); -	} -	set_current_state(TASK_RUNNING); -	remove_wait_queue(sk_sleep(intr_sk), &intr_wait); -	remove_wait_queue(sk_sleep(ctrl_sk), &ctrl_wait); - -	down_write(&hidp_session_sem); - -	hidp_del_timer(session); - -	if (session->input) { -		input_unregister_device(session->input); -		session->input = NULL; -	} - -	if (session->hid) { -		hid_destroy_device(session->hid); -		session->hid = NULL; -	} - -	/* Wakeup user-space polling for socket errors */ -	session->intr_sock->sk->sk_err = EUNATCH; -	session->ctrl_sock->sk->sk_err = EUNATCH; - -	hidp_schedule(session); - -	fput(session->intr_sock->file); - -	wait_event_timeout(*(sk_sleep(ctrl_sk)), -		(ctrl_sk->sk_state == BT_CLOSED), msecs_to_jiffies(500)); - -	fput(session->ctrl_sock->file); - -	__hidp_unlink_session(session); - -	up_write(&hidp_session_sem); - -	kfree(session); -	return 0; -} - -static struct device *hidp_get_device(struct hidp_session *session) -{ -	bdaddr_t *src = &bt_sk(session->ctrl_sock->sk)->src; -	bdaddr_t *dst = &bt_sk(session->ctrl_sock->sk)->dst; -	struct device *device = NULL; -	struct hci_dev *hdev; - -	hdev = hci_get_route(dst, src); -	if (!hdev) -		return NULL; - -	session->conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); -	if (session->conn) -		device = &session->conn->dev; - -	hci_dev_put(hdev); - -	return device; -} -  static int hidp_setup_input(struct hidp_session *session,  				struct hidp_connadd_req *req)  {  	struct input_dev *input; -	int err, i; +	int i;  	input = input_allocate_device();  	if (!input) @@ -689,16 +679,10 @@ static int hidp_setup_input(struct hidp_session *session,  		input->relbit[0] |= BIT_MASK(REL_WHEEL);  	} -	input->dev.parent = hidp_get_device(session); +	input->dev.parent = &session->conn->hcon->dev;  	input->event = hidp_input_event; -	err = input_register_device(input); -	if (err < 0) { -		hci_conn_put_device(session->conn); -		return err; -	} -  	return 0;  } @@ -721,17 +705,6 @@ static int hidp_parse(struct hid_device *hid)  static int hidp_start(struct hid_device *hid)  { -	struct hidp_session *session = hid->driver_data; -	struct hid_report *report; - -	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); -  	return 0;  } @@ -751,9 +724,12 @@ static struct hid_ll_driver hidp_hid_driver = {  	.stop = hidp_stop,  	.open  = hidp_open,  	.close = hidp_close, -	.hidinput_input_event = hidp_hidinput_event, +	.raw_request = hidp_raw_request, +	.output_report = hidp_output_report,  }; +/* This function sets up the hid device. It does not add it +   to the HID system. That is done in hidp_add_connection(). */  static int hidp_setup_hid(struct hidp_session *session,  				struct hidp_connadd_req *req)  { @@ -786,24 +762,28 @@ static int hidp_setup_hid(struct hidp_session *session,  	hid->version = req->version;  	hid->country = req->country; -	strncpy(hid->name, req->name, 128); -	strncpy(hid->phys, batostr(&bt_sk(session->ctrl_sock->sk)->src), 64); -	strncpy(hid->uniq, batostr(&bt_sk(session->ctrl_sock->sk)->dst), 64); +	strncpy(hid->name, req->name, sizeof(req->name) - 1); -	hid->dev.parent = hidp_get_device(session); -	hid->ll_driver = &hidp_hid_driver; +	snprintf(hid->phys, sizeof(hid->phys), "%pMR", +		 &l2cap_pi(session->ctrl_sock->sk)->chan->src); -	hid->hid_output_raw_report = hidp_output_raw_report; +	/* NOTE: Some device modules depend on the dst address being stored in +	 * uniq. Please be aware of this before making changes to this behavior. +	 */ +	snprintf(hid->uniq, sizeof(hid->uniq), "%pMR", +		 &l2cap_pi(session->ctrl_sock->sk)->chan->dst); -	err = hid_add_device(hid); -	if (err < 0) -		goto failed; +	hid->dev.parent = &session->conn->hcon->dev; +	hid->ll_driver = &hidp_hid_driver; -	return 0; +	/* True if device is blacklisted in drivers/hid/hid-core.c */ +	if (hid_ignore(hid)) { +		hid_destroy_device(session->hid); +		session->hid = NULL; +		return -ENODEV; +	} -failed: -	hid_destroy_device(hid); -	session->hid = NULL; +	return 0;  fault:  	kfree(session->rd_data); @@ -812,162 +792,602 @@ fault:  	return err;  } -int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock) +/* initialize session devices */ +static int hidp_session_dev_init(struct hidp_session *session, +				 struct hidp_connadd_req *req)  { -	struct hidp_session *session, *s; -	int err; +	int ret; -	BT_DBG(""); +	if (req->rd_size > 0) { +		ret = hidp_setup_hid(session, req); +		if (ret && ret != -ENODEV) +			return ret; +	} -	if (bacmp(&bt_sk(ctrl_sock->sk)->src, &bt_sk(intr_sock->sk)->src) || -			bacmp(&bt_sk(ctrl_sock->sk)->dst, &bt_sk(intr_sock->sk)->dst)) -		return -ENOTUNIQ; +	if (!session->hid) { +		ret = hidp_setup_input(session, req); +		if (ret < 0) +			return ret; +	} -	session = kzalloc(sizeof(struct hidp_session), GFP_KERNEL); -	if (!session) -		return -ENOMEM; +	return 0; +} -	BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size); +/* destroy session devices */ +static void hidp_session_dev_destroy(struct hidp_session *session) +{ +	if (session->hid) +		put_device(&session->hid->dev); +	else if (session->input) +		input_put_device(session->input); -	down_write(&hidp_session_sem); +	kfree(session->rd_data); +	session->rd_data = NULL; +} -	s = __hidp_get_session(&bt_sk(ctrl_sock->sk)->dst); -	if (s && s->state == BT_CONNECTED) { -		err = -EEXIST; -		goto failed; +/* add HID/input devices to their underlying bus systems */ +static int hidp_session_dev_add(struct hidp_session *session) +{ +	int ret; + +	/* Both HID and input systems drop a ref-count when unregistering the +	 * device but they don't take a ref-count when registering them. Work +	 * around this by explicitly taking a refcount during registration +	 * which is dropped automatically by unregistering the devices. */ + +	if (session->hid) { +		ret = hid_add_device(session->hid); +		if (ret) +			return ret; +		get_device(&session->hid->dev); +	} else if (session->input) { +		ret = input_register_device(session->input); +		if (ret) +			return ret; +		input_get_device(session->input);  	} -	bacpy(&session->bdaddr, &bt_sk(ctrl_sock->sk)->dst); +	return 0; +} -	session->ctrl_mtu = min_t(uint, l2cap_pi(ctrl_sock->sk)->omtu, l2cap_pi(ctrl_sock->sk)->imtu); -	session->intr_mtu = min_t(uint, l2cap_pi(intr_sock->sk)->omtu, l2cap_pi(intr_sock->sk)->imtu); +/* remove HID/input devices from their bus systems */ +static void hidp_session_dev_del(struct hidp_session *session) +{ +	if (session->hid) +		hid_destroy_device(session->hid); +	else if (session->input) +		input_unregister_device(session->input); +} -	BT_DBG("ctrl mtu %d intr mtu %d", session->ctrl_mtu, session->intr_mtu); +/* + * Asynchronous device registration + * HID device drivers might want to perform I/O during initialization to + * detect device types. Therefore, call device registration in a separate + * worker so the HIDP thread can schedule I/O operations. + * Note that this must be called after the worker thread was initialized + * successfully. This will then add the devices and increase session state + * on success, otherwise it will terminate the session thread. + */ +static void hidp_session_dev_work(struct work_struct *work) +{ +	struct hidp_session *session = container_of(work, +						    struct hidp_session, +						    dev_init); +	int ret; -	session->ctrl_sock = ctrl_sock; -	session->intr_sock = intr_sock; -	session->state     = BT_CONNECTED; +	ret = hidp_session_dev_add(session); +	if (!ret) +		atomic_inc(&session->state); +	else +		hidp_session_terminate(session); +} + +/* + * Create new session object + * Allocate session object, initialize static fields, copy input data into the + * object and take a reference to all sub-objects. + * This returns 0 on success and puts a pointer to the new session object in + * \out. Otherwise, an error code is returned. + * The new session object has an initial ref-count of 1. + */ +static int hidp_session_new(struct hidp_session **out, const bdaddr_t *bdaddr, +			    struct socket *ctrl_sock, +			    struct socket *intr_sock, +			    struct hidp_connadd_req *req, +			    struct l2cap_conn *conn) +{ +	struct hidp_session *session; +	int ret; +	struct bt_sock *ctrl, *intr; -	setup_timer(&session->timer, hidp_idle_timeout, (unsigned long)session); +	ctrl = bt_sk(ctrl_sock->sk); +	intr = bt_sk(intr_sock->sk); + +	session = kzalloc(sizeof(*session), GFP_KERNEL); +	if (!session) +		return -ENOMEM; +	/* object and runtime management */ +	kref_init(&session->ref); +	atomic_set(&session->state, HIDP_SESSION_IDLING); +	init_waitqueue_head(&session->state_queue); +	session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); + +	/* connection management */ +	bacpy(&session->bdaddr, bdaddr); +	session->conn = conn; +	session->user.probe = hidp_session_probe; +	session->user.remove = hidp_session_remove; +	session->ctrl_sock = ctrl_sock; +	session->intr_sock = intr_sock;  	skb_queue_head_init(&session->ctrl_transmit);  	skb_queue_head_init(&session->intr_transmit); - -	session->flags   = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); +	session->ctrl_mtu = min_t(uint, l2cap_pi(ctrl)->chan->omtu, +					l2cap_pi(ctrl)->chan->imtu); +	session->intr_mtu = min_t(uint, l2cap_pi(intr)->chan->omtu, +					l2cap_pi(intr)->chan->imtu);  	session->idle_to = req->idle_to; -	if (req->rd_size > 0) { -		err = hidp_setup_hid(session, req); -		if (err && err != -ENODEV) -			goto purge; -	} +	/* device management */ +	INIT_WORK(&session->dev_init, hidp_session_dev_work); +	setup_timer(&session->timer, hidp_idle_timeout, +		    (unsigned long)session); -	if (!session->hid) { -		err = hidp_setup_input(session, req); -		if (err < 0) -			goto purge; +	/* session data */ +	mutex_init(&session->report_mutex); +	init_waitqueue_head(&session->report_queue); + +	ret = hidp_session_dev_init(session, req); +	if (ret) +		goto err_free; + +	l2cap_conn_get(session->conn); +	get_file(session->intr_sock->file); +	get_file(session->ctrl_sock->file); +	*out = session; +	return 0; + +err_free: +	kfree(session); +	return ret; +} + +/* increase ref-count of the given session by one */ +static void hidp_session_get(struct hidp_session *session) +{ +	kref_get(&session->ref); +} + +/* release callback */ +static void session_free(struct kref *ref) +{ +	struct hidp_session *session = container_of(ref, struct hidp_session, +						    ref); + +	hidp_session_dev_destroy(session); +	skb_queue_purge(&session->ctrl_transmit); +	skb_queue_purge(&session->intr_transmit); +	fput(session->intr_sock->file); +	fput(session->ctrl_sock->file); +	l2cap_conn_put(session->conn); +	kfree(session); +} + +/* decrease ref-count of the given session by one */ +static void hidp_session_put(struct hidp_session *session) +{ +	kref_put(&session->ref, session_free); +} + +/* + * Search the list of active sessions for a session with target address + * \bdaddr. You must hold at least a read-lock on \hidp_session_sem. As long as + * you do not release this lock, the session objects cannot vanish and you can + * safely take a reference to the session yourself. + */ +static struct hidp_session *__hidp_session_find(const bdaddr_t *bdaddr) +{ +	struct hidp_session *session; + +	list_for_each_entry(session, &hidp_session_list, list) { +		if (!bacmp(bdaddr, &session->bdaddr)) +			return session;  	} -	__hidp_link_session(session); +	return NULL; +} -	hidp_set_timer(session); +/* + * Same as __hidp_session_find() but no locks must be held. This also takes a + * reference of the returned session (if non-NULL) so you must drop this + * reference if you no longer use the object. + */ +static struct hidp_session *hidp_session_find(const bdaddr_t *bdaddr) +{ +	struct hidp_session *session; + +	down_read(&hidp_session_sem); -	err = kernel_thread(hidp_session, session, CLONE_KERNEL); -	if (err < 0) -		goto unlink; +	session = __hidp_session_find(bdaddr); +	if (session) +		hidp_session_get(session); -	if (session->input) { -		hidp_send_ctrl_message(session, -			HIDP_TRANS_SET_PROTOCOL | HIDP_PROTO_BOOT, NULL, 0); -		session->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); +	up_read(&hidp_session_sem); + +	return session; +} + +/* + * Start session synchronously + * This starts a session thread and waits until initialization + * is done or returns an error if it couldn't be started. + * If this returns 0 the session thread is up and running. You must call + * hipd_session_stop_sync() before deleting any runtime resources. + */ +static int hidp_session_start_sync(struct hidp_session *session) +{ +	unsigned int vendor, product; -		session->leds = 0xff; -		hidp_input_event(session->input, EV_LED, 0, 0); +	if (session->hid) { +		vendor  = session->hid->vendor; +		product = session->hid->product; +	} else if (session->input) { +		vendor  = session->input->id.vendor; +		product = session->input->id.product; +	} else { +		vendor = 0x0000; +		product = 0x0000;  	} -	up_write(&hidp_session_sem); +	session->task = kthread_run(hidp_session_thread, session, +				    "khidpd_%04x%04x", vendor, product); +	if (IS_ERR(session->task)) +		return PTR_ERR(session->task); + +	while (atomic_read(&session->state) <= HIDP_SESSION_IDLING) +		wait_event(session->state_queue, +			   atomic_read(&session->state) > HIDP_SESSION_IDLING); +  	return 0; +} -unlink: -	hidp_del_timer(session); +/* + * Terminate session thread + * Wake up session thread and notify it to stop. This is asynchronous and + * returns immediately. Call this whenever a runtime error occurs and you want + * the session to stop. + * Note: wake_up_process() performs any necessary memory-barriers for us. + */ +static void hidp_session_terminate(struct hidp_session *session) +{ +	atomic_inc(&session->terminate); +	wake_up_process(session->task); +} + +/* + * Probe HIDP session + * This is called from the l2cap_conn core when our l2cap_user object is bound + * to the hci-connection. We get the session via the \user object and can now + * start the session thread, link it into the global session list and + * schedule HID/input device registration. + * The global session-list owns its own reference to the session object so you + * can drop your own reference after registering the l2cap_user object. + */ +static int hidp_session_probe(struct l2cap_conn *conn, +			      struct l2cap_user *user) +{ +	struct hidp_session *session = container_of(user, +						    struct hidp_session, +						    user); +	struct hidp_session *s; +	int ret; -	__hidp_unlink_session(session); +	down_write(&hidp_session_sem); -	if (session->input) { -		input_unregister_device(session->input); -		session->input = NULL; +	/* check that no other session for this device exists */ +	s = __hidp_session_find(&session->bdaddr); +	if (s) { +		ret = -EEXIST; +		goto out_unlock;  	} -	if (session->hid) { -		hid_destroy_device(session->hid); -		session->hid = NULL; +	if (session->input) { +		ret = hidp_session_dev_add(session); +		if (ret) +			goto out_unlock;  	} -	kfree(session->rd_data); -	session->rd_data = NULL; +	ret = hidp_session_start_sync(session); +	if (ret) +		goto out_del; -purge: -	skb_queue_purge(&session->ctrl_transmit); -	skb_queue_purge(&session->intr_transmit); +	/* HID device registration is async to allow I/O during probe */ +	if (session->input) +		atomic_inc(&session->state); +	else +		schedule_work(&session->dev_init); + +	hidp_session_get(session); +	list_add(&session->list, &hidp_session_list); +	ret = 0; +	goto out_unlock; -failed: +out_del: +	if (session->input) +		hidp_session_dev_del(session); +out_unlock:  	up_write(&hidp_session_sem); +	return ret; +} -	input_free_device(session->input); -	kfree(session); -	return err; +/* + * Remove HIDP session + * Called from the l2cap_conn core when either we explicitly unregistered + * the l2cap_user object or if the underlying connection is shut down. + * We signal the hidp-session thread to shut down, unregister the HID/input + * devices and unlink the session from the global list. + * This drops the reference to the session that is owned by the global + * session-list. + * Note: We _must_ not synchronosly wait for the session-thread to shut down. + * This is, because the session-thread might be waiting for an HCI lock that is + * held while we are called. Therefore, we only unregister the devices and + * notify the session-thread to terminate. The thread itself owns a reference + * to the session object so it can safely shut down. + */ +static void hidp_session_remove(struct l2cap_conn *conn, +				struct l2cap_user *user) +{ +	struct hidp_session *session = container_of(user, +						    struct hidp_session, +						    user); + +	down_write(&hidp_session_sem); + +	hidp_session_terminate(session); + +	cancel_work_sync(&session->dev_init); +	if (session->input || +	    atomic_read(&session->state) > HIDP_SESSION_PREPARING) +		hidp_session_dev_del(session); + +	list_del(&session->list); + +	up_write(&hidp_session_sem); + +	hidp_session_put(session); +} + +/* + * Session Worker + * This performs the actual main-loop of the HIDP worker. We first check + * whether the underlying connection is still alive, then parse all pending + * messages and finally send all outstanding messages. + */ +static void hidp_session_run(struct hidp_session *session) +{ +	struct sock *ctrl_sk = session->ctrl_sock->sk; +	struct sock *intr_sk = session->intr_sock->sk; +	struct sk_buff *skb; + +	for (;;) { +		/* +		 * This thread can be woken up two ways: +		 *  - You call hidp_session_terminate() which sets the +		 *    session->terminate flag and wakes this thread up. +		 *  - Via modifying the socket state of ctrl/intr_sock. This +		 *    thread is woken up by ->sk_state_changed(). +		 * +		 * Note: set_current_state() performs any necessary +		 * memory-barriers for us. +		 */ +		set_current_state(TASK_INTERRUPTIBLE); + +		if (atomic_read(&session->terminate)) +			break; + +		if (ctrl_sk->sk_state != BT_CONNECTED || +		    intr_sk->sk_state != BT_CONNECTED) +			break; + +		/* parse incoming intr-skbs */ +		while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) { +			skb_orphan(skb); +			if (!skb_linearize(skb)) +				hidp_recv_intr_frame(session, skb); +			else +				kfree_skb(skb); +		} + +		/* send pending intr-skbs */ +		hidp_process_transmit(session, &session->intr_transmit, +				      session->intr_sock); + +		/* parse incoming ctrl-skbs */ +		while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) { +			skb_orphan(skb); +			if (!skb_linearize(skb)) +				hidp_recv_ctrl_frame(session, skb); +			else +				kfree_skb(skb); +		} + +		/* send pending ctrl-skbs */ +		hidp_process_transmit(session, &session->ctrl_transmit, +				      session->ctrl_sock); + +		schedule(); +	} + +	atomic_inc(&session->terminate); +	set_current_state(TASK_RUNNING); +} + +/* + * HIDP session thread + * This thread runs the I/O for a single HIDP session. Startup is synchronous + * which allows us to take references to ourself here instead of doing that in + * the caller. + * When we are ready to run we notify the caller and call hidp_session_run(). + */ +static int hidp_session_thread(void *arg) +{ +	struct hidp_session *session = arg; +	wait_queue_t ctrl_wait, intr_wait; + +	BT_DBG("session %p", session); + +	/* initialize runtime environment */ +	hidp_session_get(session); +	__module_get(THIS_MODULE); +	set_user_nice(current, -15); +	hidp_set_timer(session); + +	init_waitqueue_entry(&ctrl_wait, current); +	init_waitqueue_entry(&intr_wait, current); +	add_wait_queue(sk_sleep(session->ctrl_sock->sk), &ctrl_wait); +	add_wait_queue(sk_sleep(session->intr_sock->sk), &intr_wait); +	/* This memory barrier is paired with wq_has_sleeper(). See +	 * sock_poll_wait() for more information why this is needed. */ +	smp_mb(); + +	/* notify synchronous startup that we're ready */ +	atomic_inc(&session->state); +	wake_up(&session->state_queue); + +	/* run session */ +	hidp_session_run(session); + +	/* cleanup runtime environment */ +	remove_wait_queue(sk_sleep(session->intr_sock->sk), &intr_wait); +	remove_wait_queue(sk_sleep(session->intr_sock->sk), &ctrl_wait); +	wake_up_interruptible(&session->report_queue); +	hidp_del_timer(session); + +	/* +	 * If we stopped ourself due to any internal signal, we should try to +	 * unregister our own session here to avoid having it linger until the +	 * parent l2cap_conn dies or user-space cleans it up. +	 * This does not deadlock as we don't do any synchronous shutdown. +	 * Instead, this call has the same semantics as if user-space tried to +	 * delete the session. +	 */ +	l2cap_unregister_user(session->conn, &session->user); +	hidp_session_put(session); + +	module_put_and_exit(0); +	return 0;  } -int hidp_del_connection(struct hidp_conndel_req *req) +static int hidp_verify_sockets(struct socket *ctrl_sock, +			       struct socket *intr_sock)  { +	struct l2cap_chan *ctrl_chan, *intr_chan; +	struct bt_sock *ctrl, *intr;  	struct hidp_session *session; -	int err = 0; -	BT_DBG(""); +	if (!l2cap_is_socket(ctrl_sock) || !l2cap_is_socket(intr_sock)) +		return -EINVAL; -	down_read(&hidp_session_sem); +	ctrl_chan = l2cap_pi(ctrl_sock->sk)->chan; +	intr_chan = l2cap_pi(intr_sock->sk)->chan; + +	if (bacmp(&ctrl_chan->src, &intr_chan->src) || +	    bacmp(&ctrl_chan->dst, &intr_chan->dst)) +		return -ENOTUNIQ; + +	ctrl = bt_sk(ctrl_sock->sk); +	intr = bt_sk(intr_sock->sk); -	session = __hidp_get_session(&req->bdaddr); +	if (ctrl->sk.sk_state != BT_CONNECTED || +	    intr->sk.sk_state != BT_CONNECTED) +		return -EBADFD; + +	/* early session check, we check again during session registration */ +	session = hidp_session_find(&ctrl_chan->dst);  	if (session) { -		if (req->flags & (1 << HIDP_VIRTUAL_CABLE_UNPLUG)) { -			hidp_send_ctrl_message(session, -				HIDP_TRANS_HID_CONTROL | HIDP_CTRL_VIRTUAL_CABLE_UNPLUG, NULL, 0); -		} else { -			/* Flush the transmit queues */ -			skb_queue_purge(&session->ctrl_transmit); -			skb_queue_purge(&session->intr_transmit); - -			/* Wakeup user-space polling for socket errors */ -			session->intr_sock->sk->sk_err = EUNATCH; -			session->ctrl_sock->sk->sk_err = EUNATCH; - -			/* Kill session thread */ -			atomic_inc(&session->terminate); -			hidp_schedule(session); -		} -	} else -		err = -ENOENT; +		hidp_session_put(session); +		return -EEXIST; +	} -	up_read(&hidp_session_sem); -	return err; +	return 0; +} + +int hidp_connection_add(struct hidp_connadd_req *req, +			struct socket *ctrl_sock, +			struct socket *intr_sock) +{ +	struct hidp_session *session; +	struct l2cap_conn *conn; +	struct l2cap_chan *chan = l2cap_pi(ctrl_sock->sk)->chan; +	int ret; + +	ret = hidp_verify_sockets(ctrl_sock, intr_sock); +	if (ret) +		return ret; + +	conn = NULL; +	l2cap_chan_lock(chan); +	if (chan->conn) { +		l2cap_conn_get(chan->conn); +		conn = chan->conn; +	} +	l2cap_chan_unlock(chan); + +	if (!conn) +		return -EBADFD; + +	ret = hidp_session_new(&session, &chan->dst, ctrl_sock, +			       intr_sock, req, conn); +	if (ret) +		goto out_conn; + +	ret = l2cap_register_user(conn, &session->user); +	if (ret) +		goto out_session; + +	ret = 0; + +out_session: +	hidp_session_put(session); +out_conn: +	l2cap_conn_put(conn); +	return ret; +} + +int hidp_connection_del(struct hidp_conndel_req *req) +{ +	struct hidp_session *session; + +	session = hidp_session_find(&req->bdaddr); +	if (!session) +		return -ENOENT; + +	if (req->flags & (1 << HIDP_VIRTUAL_CABLE_UNPLUG)) +		hidp_send_ctrl_message(session, +				       HIDP_TRANS_HID_CONTROL | +				         HIDP_CTRL_VIRTUAL_CABLE_UNPLUG, +				       NULL, 0); +	else +		l2cap_unregister_user(session->conn, &session->user); + +	hidp_session_put(session); + +	return 0;  }  int hidp_get_connlist(struct hidp_connlist_req *req)  { -	struct list_head *p; +	struct hidp_session *session;  	int err = 0, n = 0;  	BT_DBG("");  	down_read(&hidp_session_sem); -	list_for_each(p, &hidp_session_list) { -		struct hidp_session *session; +	list_for_each_entry(session, &hidp_session_list, list) {  		struct hidp_conninfo ci; -		session = list_entry(p, struct hidp_session, list); - -		__hidp_copy_session(session, &ci); +		hidp_copy_session(session, &ci);  		if (copy_to_user(req->ci, &ci, sizeof(ci))) {  			err = -EFAULT; @@ -988,63 +1408,33 @@ int hidp_get_connlist(struct hidp_connlist_req *req)  int hidp_get_conninfo(struct hidp_conninfo *ci)  {  	struct hidp_session *session; -	int err = 0; - -	down_read(&hidp_session_sem); -	session = __hidp_get_session(&ci->bdaddr); -	if (session) -		__hidp_copy_session(session, ci); -	else -		err = -ENOENT; +	session = hidp_session_find(&ci->bdaddr); +	if (session) { +		hidp_copy_session(session, ci); +		hidp_session_put(session); +	} -	up_read(&hidp_session_sem); -	return err; +	return session ? 0 : -ENOENT;  } -static const struct hid_device_id hidp_table[] = { -	{ HID_BLUETOOTH_DEVICE(HID_ANY_ID, HID_ANY_ID) }, -	{ } -}; - -static struct hid_driver hidp_driver = { -	.name = "generic-bluetooth", -	.id_table = hidp_table, -}; -  static int __init hidp_init(void)  { -	int ret; - -	l2cap_load(); -  	BT_INFO("HIDP (Human Interface Emulation) ver %s", VERSION); -	ret = hid_register_driver(&hidp_driver); -	if (ret) -		goto err; - -	ret = hidp_init_sockets(); -	if (ret) -		goto err_drv; - -	return 0; -err_drv: -	hid_unregister_driver(&hidp_driver); -err: -	return ret; +	return hidp_init_sockets();  }  static void __exit hidp_exit(void)  {  	hidp_cleanup_sockets(); -	hid_unregister_driver(&hidp_driver);  }  module_init(hidp_init);  module_exit(hidp_exit);  MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");  MODULE_DESCRIPTION("Bluetooth HIDP ver " VERSION);  MODULE_VERSION(VERSION);  MODULE_LICENSE("GPL");  | 
