Bluetooth: Fix lockdep annotation on ERTM
[safe/jmp/linux-2.6] / net / bluetooth / hidp / core.c
index b2d6da6..bfe641b 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    HIDP implementation for Linux Bluetooth stack (BlueZ).
    Copyright (C) 2003-2004 Marcel Holtmann <marcel@holtmann.org>
 
    HIDP implementation for Linux Bluetooth stack (BlueZ).
    Copyright (C) 2003-2004 Marcel Holtmann <marcel@holtmann.org>
 
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
    IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
    IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
-   CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES 
-   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
-   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
+   CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, 
-   COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS 
+   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+   COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
    SOFTWARE IS DISCLAIMED.
 */
 
    SOFTWARE IS DISCLAIMED.
 */
 
@@ -28,6 +28,7 @@
 #include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/poll.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/fcntl.h>
 #include <linux/skbuff.h>
 #include <linux/socket.h>
@@ -38,6 +39,8 @@
 #include <net/sock.h>
 
 #include <linux/input.h>
 #include <net/sock.h>
 
 #include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/hidraw.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 
 #include "hidp.h"
 
 
 #include "hidp.h"
 
-#ifndef CONFIG_BT_HIDP_DEBUG
-#undef  BT_DBG
-#define BT_DBG(D...)
-#endif
-
-#define VERSION "1.1"
+#define VERSION "1.2"
 
 static DECLARE_RWSEM(hidp_session_sem);
 static LIST_HEAD(hidp_session_list);
 
 static DECLARE_RWSEM(hidp_session_sem);
 static LIST_HEAD(hidp_session_list);
@@ -95,10 +93,14 @@ static void __hidp_link_session(struct hidp_session *session)
 {
        __module_get(THIS_MODULE);
        list_add(&session->list, &hidp_session_list);
 {
        __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)
 {
 }
 
 static void __hidp_unlink_session(struct hidp_session *session)
 {
+       hci_conn_put_device(session->conn);
+
        list_del(&session->list);
        module_put(THIS_MODULE);
 }
        list_del(&session->list);
        module_put(THIS_MODULE);
 }
@@ -124,15 +126,22 @@ static void __hidp_copy_session(struct hidp_session *session, struct hidp_connin
                else
                        strncpy(ci->name, "HID Boot Device", 128);
        }
                else
                        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 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)
                return -1;
 
        if (type != EV_LED)
                return -1;
@@ -164,6 +173,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 = input_get_drvdata(dev);
+       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 = input_get_drvdata(dev);
+
+       return hidp_queue_event(session, dev, type, code, value);
+}
+
 static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
 {
        struct input_dev *dev = session->input;
 static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
 {
        struct input_dev *dev = session->input;
@@ -219,26 +243,6 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
        input_sync(dev);
 }
 
        input_sync(dev);
 }
 
-static void hidp_idle_timeout(unsigned long arg)
-{
-       struct hidp_session *session = (struct hidp_session *) arg;
-
-       atomic_inc(&session->terminate);
-       hidp_schedule(session);
-}
-
-static inline void hidp_set_timer(struct hidp_session *session)
-{
-       if (session->idle_to > 0)
-               mod_timer(&session->timer, jiffies + HZ * session->idle_to);
-}
-
-static inline void hidp_del_timer(struct hidp_session *session)
-{
-       if (session->idle_to > 0)
-               del_timer(&session->timer);
-}
-
 static int __hidp_send_ctrl_message(struct hidp_session *session,
                        unsigned char hdr, unsigned char *data, int size)
 {
 static int __hidp_send_ctrl_message(struct hidp_session *session,
                        unsigned char hdr, unsigned char *data, int size)
 {
@@ -260,7 +264,7 @@ static int __hidp_send_ctrl_message(struct hidp_session *session,
        return 0;
 }
 
        return 0;
 }
 
-static int inline hidp_send_ctrl_message(struct hidp_session *session,
+static inline int hidp_send_ctrl_message(struct hidp_session *session,
                        unsigned char hdr, unsigned char *data, int size)
 {
        int err;
                        unsigned char hdr, unsigned char *data, int size)
 {
        int err;
@@ -272,7 +276,85 @@ static int inline hidp_send_ctrl_message(struct hidp_session *session,
        return err;
 }
 
        return err;
 }
 
-static inline void hidp_process_handshake(struct hidp_session *session, unsigned char param)
+static 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, session->hid, 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 int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, size_t count,
+               unsigned char report_type)
+{
+       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;
+       default:
+               return -EINVAL;
+       }
+
+       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);
+}
+
+static void hidp_set_timer(struct hidp_session *session)
+{
+       if (session->idle_to > 0)
+               mod_timer(&session->timer, jiffies + HZ * session->idle_to);
+}
+
+static inline void hidp_del_timer(struct hidp_session *session)
+{
+       if (session->idle_to > 0)
+               del_timer(&session->timer);
+}
+
+static void hidp_process_handshake(struct hidp_session *session,
+                                       unsigned char param)
 {
        BT_DBG("session %p param 0x%02x", session, param);
 
 {
        BT_DBG("session %p param 0x%02x", session, param);
 
@@ -293,7 +375,7 @@ static inline void hidp_process_handshake(struct hidp_session *session, unsigned
 
        case HIDP_HSHK_ERR_FATAL:
                /* Device requests a reboot, as this is the only way this error
 
        case HIDP_HSHK_ERR_FATAL:
                /* Device requests a reboot, as this is the only way this error
-                * can be recovered. */
+                * can be recovered. */
                __hidp_send_ctrl_message(session,
                        HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0);
                break;
                __hidp_send_ctrl_message(session,
                        HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0);
                break;
@@ -305,38 +387,24 @@ static inline void hidp_process_handshake(struct hidp_session *session, unsigned
        }
 }
 
        }
 }
 
-static inline void hidp_process_hid_control(struct hidp_session *session, unsigned char param)
+static void hidp_process_hid_control(struct hidp_session *session,
+                                       unsigned char param)
 {
        BT_DBG("session %p param 0x%02x", session, param);
 
 {
        BT_DBG("session %p param 0x%02x", session, param);
 
-       switch (param) {
-       case HIDP_CTRL_NOP:
-               break;
-
-       case HIDP_CTRL_VIRTUAL_CABLE_UNPLUG:
+       if (param == HIDP_CTRL_VIRTUAL_CABLE_UNPLUG) {
                /* Flush the transmit queues */
                skb_queue_purge(&session->ctrl_transmit);
                skb_queue_purge(&session->intr_transmit);
 
                /* Kill session thread */
                atomic_inc(&session->terminate);
                /* Flush the transmit queues */
                skb_queue_purge(&session->ctrl_transmit);
                skb_queue_purge(&session->intr_transmit);
 
                /* Kill session thread */
                atomic_inc(&session->terminate);
-               break;
-
-       case HIDP_CTRL_HARD_RESET:
-       case HIDP_CTRL_SOFT_RESET:
-       case HIDP_CTRL_SUSPEND:
-       case HIDP_CTRL_EXIT_SUSPEND:
-               /* FIXME: We have to parse these and return no error */
-               break;
-
-       default:
-               __hidp_send_ctrl_message(session,
-                       HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
-               break;
+               hidp_schedule(session);
        }
 }
 
        }
 }
 
-static inline void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, unsigned char param)
+static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb,
+                               unsigned char param)
 {
        BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param);
 
 {
        BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param);
 
@@ -346,6 +414,10 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf
 
                if (session->input)
                        hidp_input_report(session, skb);
 
                if (session->input)
                        hidp_input_report(session, skb);
+
+               if (session->hid)
+                       hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0);
+
                break;
 
        case HIDP_DATA_RTYPE_OTHER:
                break;
 
        case HIDP_DATA_RTYPE_OTHER:
@@ -359,7 +431,8 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf
        }
 }
 
        }
 }
 
-static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_buff *skb)
+static void hidp_recv_ctrl_frame(struct hidp_session *session,
+                                       struct sk_buff *skb)
 {
        unsigned char hdr, type, param;
 
 {
        unsigned char hdr, type, param;
 
@@ -393,7 +466,8 @@ static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_
        kfree_skb(skb);
 }
 
        kfree_skb(skb);
 }
 
-static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_buff *skb)
+static void hidp_recv_intr_frame(struct hidp_session *session,
+                               struct sk_buff *skb)
 {
        unsigned char hdr;
 
 {
        unsigned char hdr;
 
@@ -404,8 +478,14 @@ static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_
 
        if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
                hidp_set_timer(session);
 
        if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
                hidp_set_timer(session);
+
                if (session->input)
                        hidp_input_report(session, skb);
                if (session->input)
                        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 {
                BT_DBG("Unsupported protocol header 0x%02x", hdr);
        }
        } else {
                BT_DBG("Unsupported protocol header 0x%02x", hdr);
        }
@@ -471,14 +551,18 @@ 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);
        set_user_nice(current, -15);
        daemonize("khidpd_%04x%04x", vendor, product);
        set_user_nice(current, -15);
-       current->flags |= PF_NOFREEZE;
 
        init_waitqueue_entry(&ctrl_wait, current);
        init_waitqueue_entry(&intr_wait, current);
 
        init_waitqueue_entry(&ctrl_wait, current);
        init_waitqueue_entry(&intr_wait, current);
-       add_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
-       add_wait_queue(intr_sk->sk_sleep, &intr_wait);
+       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);
 
        while (!atomic_read(&session->terminate)) {
                set_current_state(TASK_INTERRUPTIBLE);
 
@@ -500,29 +584,38 @@ static int hidp_session(void *arg)
                schedule();
        }
        set_current_state(TASK_RUNNING);
                schedule();
        }
        set_current_state(TASK_RUNNING);
-       remove_wait_queue(intr_sk->sk_sleep, &intr_wait);
-       remove_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
+       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);
 
 
        down_write(&hidp_session_sem);
 
        hidp_del_timer(session);
 
-       if (intr_sk->sk_state != BT_CONNECTED)
-               wait_event_timeout(*(ctrl_sk->sk_sleep), (ctrl_sk->sk_state == BT_CLOSED), HZ);
+       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);
 
 
        fput(session->intr_sock->file);
 
-       wait_event_timeout(*(intr_sk->sk_sleep), (intr_sk->sk_state == BT_CLOSED), HZ);
+       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);
 
 
        fput(session->ctrl_sock->file);
 
        __hidp_unlink_session(session);
 
-       if (session->input) {
-               input_unregister_device(session->input);
-               session->input = NULL;
-       }
-
        up_write(&hidp_session_sem);
 
        kfree(session);
        up_write(&hidp_session_sem);
 
        kfree(session);
@@ -533,28 +626,35 @@ 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;
 {
        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;
        struct hci_dev *hdev;
-       struct hci_conn *conn;
 
        hdev = hci_get_route(dst, src);
        if (!hdev)
                return NULL;
 
 
        hdev = hci_get_route(dst, src);
        if (!hdev)
                return NULL;
 
-       conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
-       if (!conn)
-               return NULL;
+       session->conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
+       if (session->conn)
+               device = &session->conn->dev;
 
        hci_dev_put(hdev);
 
 
        hci_dev_put(hdev);
 
-       return &conn->dev;
+       return device;
 }
 
 }
 
-static inline void hidp_setup_input(struct hidp_session *session, struct hidp_connadd_req *req)
+static int hidp_setup_input(struct hidp_session *session,
+                               struct hidp_connadd_req *req)
 {
 {
-       struct input_dev *input = session->input;
-       int i;
+       struct input_dev *input;
+       int err, i;
+
+       input = input_allocate_device();
+       if (!input)
+               return -ENOMEM;
+
+       session->input = input;
 
 
-       input->private = session;
+       input_set_drvdata(input, session);
 
        input->name = "Bluetooth HID Boot Protocol Device";
 
 
        input->name = "Bluetooth HID Boot Protocol Device";
 
@@ -580,18 +680,140 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co
        }
 
        if (req->subclass & 0x80) {
        }
 
        if (req->subclass & 0x80) {
-               input->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
-               input->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
-               input->relbit[0] = BIT(REL_X) | BIT(REL_Y);
-               input->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
-               input->relbit[0] |= BIT(REL_WHEEL);
+               input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
+               input->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
+                       BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
+               input->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
+               input->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
+                       BIT_MASK(BTN_EXTRA);
+               input->relbit[0] |= BIT_MASK(REL_WHEEL);
        }
 
        }
 
-       input->cdev.dev = hidp_get_device(session);
+       input->dev.parent = hidp_get_device(session);
 
        input->event = hidp_input_event;
 
 
        input->event = hidp_input_event;
 
-       input_register_device(input);
+       err = input_register_device(input);
+       if (err < 0) {
+               hci_conn_put_device(session->conn);
+               return err;
+       }
+
+       return 0;
+}
+
+static int hidp_open(struct hid_device *hid)
+{
+       return 0;
+}
+
+static void hidp_close(struct hid_device *hid)
+{
+}
+
+static int hidp_parse(struct hid_device *hid)
+{
+       struct hidp_session *session = hid->driver_data;
+
+       return hid_parse_report(session->hid, session->rd_data,
+                       session->rd_size);
+}
+
+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;
+}
+
+static void hidp_stop(struct hid_device *hid)
+{
+       struct hidp_session *session = hid->driver_data;
+
+       skb_queue_purge(&session->ctrl_transmit);
+       skb_queue_purge(&session->intr_transmit);
+
+       hid->claimed = 0;
+}
+
+static struct hid_ll_driver hidp_hid_driver = {
+       .parse = hidp_parse,
+       .start = hidp_start,
+       .stop = hidp_stop,
+       .open  = hidp_open,
+       .close = hidp_close,
+       .hidinput_input_event = hidp_hidinput_event,
+};
+
+static int hidp_setup_hid(struct hidp_session *session,
+                               struct hidp_connadd_req *req)
+{
+       struct hid_device *hid;
+       bdaddr_t src, dst;
+       int err;
+
+       session->rd_data = kzalloc(req->rd_size, GFP_KERNEL);
+       if (!session->rd_data)
+               return -ENOMEM;
+
+       if (copy_from_user(session->rd_data, req->rd_data, req->rd_size)) {
+               err = -EFAULT;
+               goto fault;
+       }
+       session->rd_size = req->rd_size;
+
+       hid = hid_allocate_device();
+       if (IS_ERR(hid)) {
+               err = PTR_ERR(hid);
+               goto fault;
+       }
+
+       session->hid = hid;
+
+       hid->driver_data = session;
+
+       baswap(&src, &bt_sk(session->ctrl_sock->sk)->src);
+       baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst);
+
+       hid->bus     = BUS_BLUETOOTH;
+       hid->vendor  = req->vendor;
+       hid->product = req->product;
+       hid->version = req->version;
+       hid->country = req->country;
+
+       strncpy(hid->name, req->name, 128);
+       strncpy(hid->phys, batostr(&src), 64);
+       strncpy(hid->uniq, batostr(&dst), 64);
+
+       hid->dev.parent = hidp_get_device(session);
+       hid->ll_driver = &hidp_hid_driver;
+
+       hid->hid_output_raw_report = hidp_output_raw_report;
+
+       err = hid_add_device(hid);
+       if (err < 0)
+               goto failed;
+
+       return 0;
+
+failed:
+       hid_destroy_device(hid);
+       session->hid = NULL;
+
+fault:
+       kfree(session->rd_data);
+       session->rd_data = NULL;
+
+       return err;
 }
 
 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)
@@ -609,11 +831,7 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
        if (!session)
                return -ENOMEM;
 
        if (!session)
                return -ENOMEM;
 
-       session->input = input_allocate_device();
-       if (!session->input) {
-               kfree(session);
-               return -ENOMEM;
-       }
+       BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size);
 
        down_write(&hidp_session_sem);
 
 
        down_write(&hidp_session_sem);
 
@@ -634,10 +852,7 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
        session->intr_sock = intr_sock;
        session->state     = BT_CONNECTED;
 
        session->intr_sock = intr_sock;
        session->state     = BT_CONNECTED;
 
-       init_timer(&session->timer);
-
-       session->timer.function = hidp_idle_timeout;
-       session->timer.data     = (unsigned long) session;
+       setup_timer(&session->timer, hidp_idle_timeout, (unsigned long)session);
 
        skb_queue_head_init(&session->ctrl_transmit);
        skb_queue_head_init(&session->intr_transmit);
 
        skb_queue_head_init(&session->ctrl_transmit);
        skb_queue_head_init(&session->intr_transmit);
@@ -645,8 +860,17 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
        session->flags   = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID);
        session->idle_to = req->idle_to;
 
        session->flags   = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID);
        session->idle_to = req->idle_to;
 
-       if (session->input)
-               hidp_setup_input(session, req);
+       if (req->rd_size > 0) {
+               err = hidp_setup_hid(session, req);
+               if (err && err != -ENODEV)
+                       goto purge;
+       }
+
+       if (!session->hid) {
+               err = hidp_setup_input(session, req);
+               if (err < 0)
+                       goto purge;
+       }
 
        __hidp_link_session(session);
 
 
        __hidp_link_session(session);
 
@@ -675,13 +899,25 @@ unlink:
 
        if (session->input) {
                input_unregister_device(session->input);
 
        if (session->input) {
                input_unregister_device(session->input);
-               session->input = NULL; /* don't try to free it here */
+               session->input = NULL;
+       }
+
+       if (session->hid) {
+               hid_destroy_device(session->hid);
+               session->hid = NULL;
        }
 
        }
 
+       kfree(session->rd_data);
+       session->rd_data = NULL;
+
+purge:
+       skb_queue_purge(&session->ctrl_transmit);
+       skb_queue_purge(&session->intr_transmit);
+
 failed:
        up_write(&hidp_session_sem);
 
 failed:
        up_write(&hidp_session_sem);
 
-       kfree(session->input);
+       input_free_device(session->input);
        kfree(session);
        return err;
 }
        kfree(session);
        return err;
 }
@@ -705,6 +941,10 @@ int hidp_del_connection(struct hidp_conndel_req *req)
                        skb_queue_purge(&session->ctrl_transmit);
                        skb_queue_purge(&session->intr_transmit);
 
                        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);
                        /* Kill session thread */
                        atomic_inc(&session->terminate);
                        hidp_schedule(session);
@@ -766,18 +1006,43 @@ int hidp_get_conninfo(struct hidp_conninfo *ci)
        return err;
 }
 
        return err;
 }
 
+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)
 {
 static int __init hidp_init(void)
 {
+       int ret;
+
        l2cap_load();
 
        BT_INFO("HIDP (Human Interface Emulation) ver %s", VERSION);
 
        l2cap_load();
 
        BT_INFO("HIDP (Human Interface Emulation) ver %s", VERSION);
 
-       return hidp_init_sockets();
+       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;
 }
 
 static void __exit hidp_exit(void)
 {
        hidp_cleanup_sockets();
 }
 
 static void __exit hidp_exit(void)
 {
        hidp_cleanup_sockets();
+       hid_unregister_driver(&hidp_driver);
 }
 
 module_init(hidp_init);
 }
 
 module_init(hidp_init);