Freezer: make kernel threads nonfreezable by default
[safe/jmp/linux-2.6] / net / bluetooth / hidp / core.c
index 9a562cf..64d89ca 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    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
-   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.
 
-   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.
 */
 
@@ -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>
@@ -38,6 +39,7 @@
 #include <net/sock.h>
 
 #include <linux/input.h>
+#include <linux/hid.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
@@ -50,7 +52,7 @@
 #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);
@@ -124,15 +126,22 @@ static void __hidp_copy_session(struct hidp_session *session, struct hidp_connin
                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 inline int hidp_queue_event(struct hidp_session *session, struct input_dev *dev,
+                                       unsigned int type, unsigned int code, int value)
 {
-       struct hidp_session *session = dev->private;
-       struct sk_buff *skb;
        unsigned char newleds;
+       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;
@@ -164,6 +173,21 @@ static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned i
        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;
@@ -219,6 +243,42 @@ 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)
+{
+       struct sk_buff *skb;
+
+       BT_DBG("session %p hid %p data %p size %d", session, device, data, size);
+
+       if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) {
+               BT_ERR("Can't allocate memory for new frame");
+               return -ENOMEM;
+       }
+
+       *skb_put(skb, 1) = 0xa2;
+       if (size > 0)
+               memcpy(skb_put(skb, size), data, size);
+
+       skb_queue_tail(&session->intr_transmit, skb);
+
+       hidp_schedule(session);
+
+       return 0;
+}
+
+static int hidp_send_report(struct hidp_session *session, struct hid_report *report)
+{
+       unsigned char buf[32];
+       int rsize;
+
+       rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0);
+       if (rsize > sizeof(buf))
+               return -EIO;
+
+       hid_output_report(report, buf);
+
+       return hidp_queue_report(session, buf, rsize);
+}
+
 static void hidp_idle_timeout(unsigned long arg)
 {
        struct hidp_session *session = (struct hidp_session *) arg;
@@ -260,7 +320,7 @@ static int __hidp_send_ctrl_message(struct hidp_session *session,
        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;
@@ -293,7 +353,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
-                * can be recovered. */
+                * can be recovered. */
                __hidp_send_ctrl_message(session,
                        HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0);
                break;
@@ -346,6 +406,10 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf
 
                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:
@@ -404,8 +468,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 (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);
        }
@@ -471,9 +541,13 @@ static int hidp_session(void *arg)
                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);
-       current->flags |= PF_NOFREEZE;
 
        init_waitqueue_entry(&ctrl_wait, current);
        init_waitqueue_entry(&intr_wait, current);
@@ -507,22 +581,26 @@ static int hidp_session(void *arg)
 
        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) {
+               if (session->hid->claimed & HID_CLAIMED_INPUT)
+                       hidinput_disconnect(session->hid);
+               hid_free_device(session->hid);
+       }
 
        fput(session->intr_sock->file);
 
-       wait_event_timeout(*(intr_sk->sk_sleep), (intr_sk->sk_state == BT_CLOSED), HZ);
+       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;
-       }
-
        up_write(&hidp_session_sem);
 
        kfree(session);
@@ -552,7 +630,7 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co
        struct input_dev *input = session->input;
        int i;
 
-       input->private = session;
+       input_set_drvdata(input, session);
 
        input->name = "Bluetooth HID Boot Protocol Device";
 
@@ -585,13 +663,84 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co
                input->relbit[0] |= BIT(REL_WHEEL);
        }
 
-       input->cdev.dev = hidp_get_device(session);
+       input->dev.parent = hidp_get_device(session);
 
        input->event = hidp_input_event;
 
        input_register_device(input);
 }
 
+static int hidp_open(struct hid_device *hid)
+{
+       return 0;
+}
+
+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 },
+
+       { }     /* Terminating entry */
+};
+
+static void hidp_setup_quirks(struct hid_device *hid)
+{
+       unsigned int n;
+
+       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;
+}
+
+static inline void hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req)
+{
+       struct hid_device *hid = session->hid;
+       struct hid_report *report;
+       bdaddr_t src, dst;
+
+       baswap(&src, &bt_sk(session->ctrl_sock->sk)->src);
+       baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst);
+
+       hid->driver_data = session;
+
+       hid->country = req->country;
+
+       hid->bus     = BUS_BLUETOOTH;
+       hid->vendor  = req->vendor;
+       hid->product = req->product;
+       hid->version = req->version;
+
+       strncpy(hid->name, req->name, 128);
+       strncpy(hid->phys, batostr(&src), 64);
+       strncpy(hid->uniq, batostr(&dst), 64);
+
+       hid->dev = hidp_get_device(session);
+
+       hid->hid_open  = hidp_open;
+       hid->hid_close = hidp_close;
+
+       hid->hidinput_input_event = hidp_hidinput_event;
+
+       hidp_setup_quirks(hid);
+
+       list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
+               hidp_send_report(session, report);
+
+       list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
+               hidp_send_report(session, report);
+
+       if (hidinput_connect(hid) == 0)
+               hid->claimed |= HID_CLAIMED_INPUT;
+}
+
 int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock)
 {
        struct hidp_session *session, *s;
@@ -607,10 +756,38 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
        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);
+
+       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);
@@ -646,6 +823,9 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
        if (session->input)
                hidp_setup_input(session, req);
 
+       if (session->hid)
+               hidp_setup_hid(session, req);
+
        __hidp_link_session(session);
 
        hidp_set_timer(session);
@@ -679,7 +859,10 @@ unlink:
 failed:
        up_write(&hidp_session_sem);
 
-       kfree(session->input);
+       if (session->hid)
+               hid_free_device(session->hid);
+
+       input_free_device(session->input);
        kfree(session);
        return err;
 }