Merge branch 'linus' into cont_syslog
[safe/jmp/linux-2.6] / net / bluetooth / hidp / core.c
index ceadfcf..bfe641b 100644 (file)
@@ -28,6 +28,7 @@
 #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>
@@ -39,6 +40,7 @@
 
 #include <linux/input.h>
 #include <linux/hid.h>
+#include <linux/hidraw.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 
 #include "hidp.h"
 
-#ifndef CONFIG_BT_HIDP_DEBUG
-#undef  BT_DBG
-#define BT_DBG(D...)
-#endif
-
 #define VERSION "1.2"
 
 static DECLARE_RWSEM(hidp_session_sem);
@@ -96,10 +93,14 @@ 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);
 }
@@ -134,8 +135,8 @@ static void __hidp_copy_session(struct hidp_session *session, struct hidp_connin
        }
 }
 
-static inline int hidp_queue_event(struct hidp_session *session, 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)
 {
        unsigned char newleds;
        struct sk_buff *skb;
@@ -242,11 +243,45 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
        input_sync(dev);
 }
 
-static inline int hidp_queue_report(struct hidp_session *session, unsigned char *data, int size)
+static int __hidp_send_ctrl_message(struct hidp_session *session,
+                       unsigned char hdr, unsigned char *data, int size)
+{
+       struct sk_buff *skb;
+
+       BT_DBG("session %p data %p size %d", session, 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) = hdr;
+       if (data && size > 0)
+               memcpy(skb_put(skb, size), data, size);
+
+       skb_queue_tail(&session->ctrl_transmit, skb);
+
+       return 0;
+}
+
+static inline int hidp_send_ctrl_message(struct hidp_session *session,
+                       unsigned char hdr, unsigned char *data, int size)
+{
+       int err;
+
+       err = __hidp_send_ctrl_message(session, hdr, data, size);
+
+       hidp_schedule(session);
+
+       return err;
+}
+
+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, device, data, size);
+       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");
@@ -278,6 +313,26 @@ static int hidp_send_report(struct hidp_session *session, struct hid_report *rep
        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;
@@ -286,7 +341,7 @@ static void hidp_idle_timeout(unsigned long arg)
        hidp_schedule(session);
 }
 
-static inline void hidp_set_timer(struct hidp_session *session)
+static void hidp_set_timer(struct hidp_session *session)
 {
        if (session->idle_to > 0)
                mod_timer(&session->timer, jiffies + HZ * session->idle_to);
@@ -298,40 +353,8 @@ static inline void hidp_del_timer(struct hidp_session *session)
                del_timer(&session->timer);
 }
 
-static int __hidp_send_ctrl_message(struct hidp_session *session,
-                       unsigned char hdr, unsigned char *data, int size)
-{
-       struct sk_buff *skb;
-
-       BT_DBG("session %p data %p size %d", session, 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) = hdr;
-       if (data && size > 0)
-               memcpy(skb_put(skb, size), data, size);
-
-       skb_queue_tail(&session->ctrl_transmit, skb);
-
-       return 0;
-}
-
-static inline int hidp_send_ctrl_message(struct hidp_session *session,
-                       unsigned char hdr, unsigned char *data, int size)
-{
-       int err;
-
-       err = __hidp_send_ctrl_message(session, hdr, data, size);
-
-       hidp_schedule(session);
-
-       return err;
-}
-
-static inline void hidp_process_handshake(struct hidp_session *session, unsigned char param)
+static void hidp_process_handshake(struct hidp_session *session,
+                                       unsigned char param)
 {
        BT_DBG("session %p param 0x%02x", session, param);
 
@@ -364,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);
 
-       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);
-               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);
 
@@ -422,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;
 
@@ -456,7 +466,8 @@ static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_
        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;
 
@@ -547,12 +558,11 @@ static int hidp_session(void *arg)
 
        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);
-       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);
 
@@ -574,33 +584,38 @@ static int hidp_session(void *arg)
                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);
 
-       fput(session->intr_sock->file);
-
-       wait_event_timeout(*(ctrl_sk->sk_sleep),
-               (ctrl_sk->sk_state == BT_CLOSED), msecs_to_jiffies(500));
-
-       fput(session->ctrl_sock->file);
-
-       __hidp_unlink_session(session);
-
        if (session->input) {
                input_unregister_device(session->input);
                session->input = NULL;
        }
 
        if (session->hid) {
-               if (session->hid->claimed & HID_CLAIMED_INPUT)
-                       hidinput_disconnect(session->hid);
-               hid_free_device(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);
@@ -611,24 +626,33 @@ 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;
-       struct hci_conn *conn;
 
        hdev = hci_get_route(dst, src);
        if (!hdev)
                return NULL;
 
-       conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
+       session->conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
+       if (session->conn)
+               device = &session->conn->dev;
 
        hci_dev_put(hdev);
 
-       return conn ? &conn->dev : NULL;
+       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_set_drvdata(input, session);
 
@@ -656,18 +680,26 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co
        }
 
        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->dev.parent = hidp_get_device(session);
 
        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)
@@ -679,66 +711,109 @@ static void hidp_close(struct hid_device *hid)
 {
 }
 
-static const struct {
-       __u16 idVendor;
-       __u16 idProduct;
-       unsigned quirks;
-} hidp_blacklist[] = {
-       /* Apple wireless Mighty Mouse */
-       { 0x05ac, 0x030c, HID_QUIRK_MIGHTYMOUSE | HID_QUIRK_INVERT_HWHEEL },
+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);
+}
 
-       { }     /* Terminating entry */
-};
+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_setup_quirks(struct hid_device *hid)
+static void hidp_stop(struct hid_device *hid)
 {
-       unsigned int n;
+       struct hidp_session *session = hid->driver_data;
+
+       skb_queue_purge(&session->ctrl_transmit);
+       skb_queue_purge(&session->intr_transmit);
 
-       for (n = 0; hidp_blacklist[n].idVendor; n++)
-               if (hidp_blacklist[n].idVendor == le16_to_cpu(hid->vendor) &&
-                               hidp_blacklist[n].idProduct == le16_to_cpu(hid->product))
-                       hid->quirks = hidp_blacklist[n].quirks;
+       hid->claimed = 0;
 }
 
-static inline void hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req)
+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 = session->hid;
-       struct hid_report *report;
+       struct hid_device *hid;
        bdaddr_t src, dst;
+       int err;
 
-       baswap(&src, &bt_sk(session->ctrl_sock->sk)->src);
-       baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst);
+       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;
 
-       hid->country = req->country;
+       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 = hidp_get_device(session);
+       hid->dev.parent = hidp_get_device(session);
+       hid->ll_driver = &hidp_hid_driver;
 
-       hid->hid_open  = hidp_open;
-       hid->hid_close = hidp_close;
+       hid->hid_output_raw_report = hidp_output_raw_report;
 
-       hid->hidinput_input_event = hidp_hidinput_event;
+       err = hid_add_device(hid);
+       if (err < 0)
+               goto failed;
 
-       hidp_setup_quirks(hid);
+       return 0;
 
-       list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
-               hidp_send_report(session, report);
+failed:
+       hid_destroy_device(hid);
+       session->hid = NULL;
 
-       list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
-               hidp_send_report(session, report);
+fault:
+       kfree(session->rd_data);
+       session->rd_data = NULL;
 
-       if (hidinput_connect(hid) == 0)
-               hid->claimed |= HID_CLAIMED_INPUT;
+       return err;
 }
 
 int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock)
@@ -758,38 +833,6 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
 
        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();
-               if (!session->input) {
-                       kfree(session);
-                       return -ENOMEM;
-               }
-       }
-
        down_write(&hidp_session_sem);
 
        s = __hidp_get_session(&bt_sk(ctrl_sock->sk)->dst);
@@ -809,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;
 
-       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);
@@ -820,11 +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;
 
-       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)
-               hidp_setup_hid(session, req);
+       if (!session->hid) {
+               err = hidp_setup_input(session, req);
+               if (err < 0)
+                       goto purge;
+       }
 
        __hidp_link_session(session);
 
@@ -853,15 +899,24 @@ unlink:
 
        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);
 
-       if (session->hid)
-               hid_free_device(session->hid);
-
        input_free_device(session->input);
        kfree(session);
        return err;
@@ -886,6 +941,10 @@ int hidp_del_connection(struct hidp_conndel_req *req)
                        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);
@@ -947,18 +1006,43 @@ int hidp_get_conninfo(struct hidp_conninfo *ci)
        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)
 {
+       int ret;
+
        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();
+       hid_unregister_driver(&hidp_driver);
 }
 
 module_init(hidp_init);