fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / net / bluetooth / hidp / core.c
index 7e8e7ba..6678201 100644 (file)
    SOFTWARE IS DISCLAIMED.
 */
 
-#include <linux/config.h>
 #include <linux/module.h>
 
 #include <linux/types.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
-#include <linux/major.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/poll.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 <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
 #include <net/bluetooth/l2cap.h>
 
 #include "hidp.h"
@@ -50,7 +50,7 @@
 #define BT_DBG(D...)
 #endif
 
-#define VERSION "1.0"
+#define VERSION "1.1"
 
 static DECLARE_RWSEM(hidp_session_sem);
 static LIST_HEAD(hidp_session_list);
@@ -74,6 +74,8 @@ static unsigned char hidp_keycode[256] = {
        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;
@@ -130,7 +132,7 @@ static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned i
        struct sk_buff *skb;
        unsigned char newleds;
 
-       BT_DBG("session %p hid %p data %p size %d", session, device, data, size);
+       BT_DBG("input %p type %d code %d value %d", dev, type, code, value);
 
        if (type != EV_LED)
                return -1;
@@ -151,7 +153,7 @@ static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned i
                return -ENOMEM;
        }
 
-       *skb_put(skb, 1) = 0xa2;
+       *skb_put(skb, 1) = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT;
        *skb_put(skb, 1) = 0x01;
        *skb_put(skb, 1) = newleds;
 
@@ -175,6 +177,11 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
                for (i = 0; i < 8; i++)
                        input_report_key(dev, hidp_keycode[i + 224], (udata[0] >> i) & 1);
 
+               /* If all the key codes have been set to 0x01, it means
+                * too many keys were pressed at the same time. */
+               if (!memcmp(udata + 2, hidp_mkeyspat, 6))
+                       break;
+
                for (i = 2; i < 8; i++) {
                        if (keys[i] > 3 && memscan(udata + 2, keys[i], 6) == udata + 8) {
                                if (hidp_keycode[keys[i]])
@@ -232,36 +239,171 @@ static inline void hidp_del_timer(struct hidp_session *session)
                del_timer(&session->timer);
 }
 
-static inline void hidp_send_message(struct hidp_session *session, unsigned char hdr)
+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", session);
+       BT_DBG("session %p data %p size %d", session, data, size);
 
-       if (!(skb = alloc_skb(1, GFP_ATOMIC))) {
-               BT_ERR("Can't allocate memory for message");
-               return;
+       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 int inline 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)
+{
+       BT_DBG("session %p param 0x%02x", session, param);
+
+       switch (param) {
+       case HIDP_HSHK_SUCCESSFUL:
+               /* FIXME: Call into SET_ GET_ handlers here */
+               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:
+               /* FIXME: Call into SET_ GET_ handlers here */
+               break;
+
+       case HIDP_HSHK_ERR_UNKNOWN:
+               break;
+
+       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_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0);
+               break;
+
+       default:
+               __hidp_send_ctrl_message(session,
+                       HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
+               break;
+       }
+}
+
+static inline 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:
+               /* 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;
+       }
+}
+
+static inline 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);
+
+       switch (param) {
+       case HIDP_DATA_RTYPE_INPUT:
+               hidp_set_timer(session);
+
+               if (session->input)
+                       hidp_input_report(session, skb);
+               break;
+
+       case HIDP_DATA_RTYPE_OTHER:
+       case HIDP_DATA_RTYPE_OUPUT:
+       case HIDP_DATA_RTYPE_FEATURE:
+               break;
+
+       default:
+               __hidp_send_ctrl_message(session,
+                       HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
+       }
 }
 
-static inline int hidp_recv_frame(struct hidp_session *session, struct sk_buff *skb)
+static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_buff *skb)
 {
-       __u8 hdr;
+       unsigned char hdr, type, param;
 
        BT_DBG("session %p skb %p len %d", session, skb, skb->len);
 
        hdr = skb->data[0];
        skb_pull(skb, 1);
 
-       if (hdr == 0xa1) {
-               hidp_set_timer(session);
+       type = hdr & HIDP_HEADER_TRANS_MASK;
+       param = hdr & HIDP_HEADER_PARAM_MASK;
+
+       switch (type) {
+       case HIDP_TRANS_HANDSHAKE:
+               hidp_process_handshake(session, param);
+               break;
+
+       case HIDP_TRANS_HID_CONTROL:
+               hidp_process_hid_control(session, param);
+               break;
 
+       case HIDP_TRANS_DATA:
+               hidp_process_data(session, skb, param);
+               break;
+
+       default:
+               __hidp_send_ctrl_message(session,
+                       HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_UNSUPPORTED_REQUEST, NULL, 0);
+               break;
+       }
+
+       kfree_skb(skb);
+}
+
+static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_buff *skb)
+{
+       unsigned char hdr;
+
+       BT_DBG("session %p skb %p len %d", session, skb, skb->len);
+
+       hdr = skb->data[0];
+       skb_pull(skb, 1);
+
+       if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
+               hidp_set_timer(session);
                if (session->input)
                        hidp_input_report(session, skb);
        } else {
@@ -269,12 +411,11 @@ static inline int hidp_recv_frame(struct hidp_session *session, struct sk_buff *
        }
 
        kfree_skb(skb);
-       return 0;
 }
 
 static int hidp_send_frame(struct socket *sock, unsigned char *data, int len)
 {
-       struct iovec iv = { data, len };
+       struct kvec iv = { data, len };
        struct msghdr msg;
 
        BT_DBG("sock %p data %p len %d", sock, data, len);
@@ -283,13 +424,11 @@ static int hidp_send_frame(struct socket *sock, unsigned char *data, int len)
                return 0;
 
        memset(&msg, 0, sizeof(msg));
-       msg.msg_iovlen = 1;
-       msg.msg_iov = &iv;
 
-       return sock_sendmsg(sock, &msg, len);
+       return kernel_sendmsg(sock, &msg, &iv, 1, len);
 }
 
-static int hidp_process_transmit(struct hidp_session *session)
+static void hidp_process_transmit(struct hidp_session *session)
 {
        struct sk_buff *skb;
 
@@ -314,9 +453,6 @@ static int hidp_process_transmit(struct hidp_session *session)
                hidp_set_timer(session);
                kfree_skb(skb);
        }
-
-       return skb_queue_len(&session->ctrl_transmit) +
-                               skb_queue_len(&session->intr_transmit);
 }
 
 static int hidp_session(void *arg)
@@ -327,7 +463,6 @@ static int hidp_session(void *arg)
        struct sk_buff *skb;
        int vendor = 0x0000, product = 0x0000;
        wait_queue_t ctrl_wait, intr_wait;
-       unsigned long timeo = HZ;
 
        BT_DBG("session %p", session);
 
@@ -340,8 +475,6 @@ static int hidp_session(void *arg)
        set_user_nice(current, -15);
        current->flags |= PF_NOFREEZE;
 
-       set_fs(KERNEL_DS);
-
        init_waitqueue_entry(&ctrl_wait, current);
        init_waitqueue_entry(&intr_wait, current);
        add_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
@@ -354,12 +487,12 @@ static int hidp_session(void *arg)
 
                while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) {
                        skb_orphan(skb);
-                       hidp_recv_frame(session, skb);
+                       hidp_recv_ctrl_frame(session, skb);
                }
 
                while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) {
                        skb_orphan(skb);
-                       hidp_recv_frame(session, skb);
+                       hidp_recv_intr_frame(session, skb);
                }
 
                hidp_process_transmit(session);
@@ -374,36 +507,18 @@ static int hidp_session(void *arg)
 
        hidp_del_timer(session);
 
-       if (intr_sk->sk_state != BT_CONNECTED) {
-               init_waitqueue_entry(&ctrl_wait, current);
-               add_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
-               while (timeo && ctrl_sk->sk_state != BT_CLOSED) {
-                       set_current_state(TASK_INTERRUPTIBLE);
-                       timeo = schedule_timeout(timeo);
-               }
-               set_current_state(TASK_RUNNING);
-               remove_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
-               timeo = HZ;
-       }
+       fput(session->intr_sock->file);
 
-       fput(session->ctrl_sock->file);
+       wait_event_timeout(*(ctrl_sk->sk_sleep),
+               (ctrl_sk->sk_state == BT_CLOSED), msecs_to_jiffies(500));
 
-       init_waitqueue_entry(&intr_wait, current);
-       add_wait_queue(intr_sk->sk_sleep, &intr_wait);
-       while (timeo && intr_sk->sk_state != BT_CLOSED) {
-               set_current_state(TASK_INTERRUPTIBLE);
-               timeo = schedule_timeout(timeo);
-       }
-       set_current_state(TASK_RUNNING);
-       remove_wait_queue(intr_sk->sk_sleep, &intr_wait);
-
-       fput(session->intr_sock->file);
+       fput(session->ctrl_sock->file);
 
        __hidp_unlink_session(session);
 
        if (session->input) {
                input_unregister_device(session->input);
-               kfree(session->input);
+               session->input = NULL;
        }
 
        up_write(&hidp_session_sem);
@@ -412,6 +527,24 @@ static int hidp_session(void *arg)
        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 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);
+
+       hci_dev_put(hdev);
+
+       return conn ? &conn->dev : NULL;
+}
+
 static inline void hidp_setup_input(struct hidp_session *session, struct hidp_connadd_req *req)
 {
        struct input_dev *input = session->input;
@@ -419,6 +552,8 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co
 
        input->private = session;
 
+       input->name = "Bluetooth HID Boot Protocol Device";
+
        input->id.bustype = BUS_BLUETOOTH;
        input->id.vendor  = req->vendor;
        input->id.product = req->product;
@@ -448,6 +583,8 @@ 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->event = hidp_input_event;
 
        input_register_device(input);
@@ -464,17 +601,15 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
                        bacmp(&bt_sk(ctrl_sock->sk)->dst, &bt_sk(intr_sock->sk)->dst))
                return -ENOTUNIQ;
 
-       session = kmalloc(sizeof(struct hidp_session), GFP_KERNEL);
-       if (!session) 
+       session = kzalloc(sizeof(struct hidp_session), GFP_KERNEL);
+       if (!session)
                return -ENOMEM;
-       memset(session, 0, sizeof(struct hidp_session));
 
-       session->input = kmalloc(sizeof(struct input_dev), GFP_KERNEL);
+       session->input = input_allocate_device();
        if (!session->input) {
                kfree(session);
                return -ENOMEM;
        }
-       memset(session->input, 0, sizeof(struct input_dev));
 
        down_write(&hidp_session_sem);
 
@@ -518,7 +653,8 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
                goto unlink;
 
        if (session->input) {
-               hidp_send_message(session, 0x70);
+               hidp_send_ctrl_message(session,
+                       HIDP_TRANS_SET_PROTOCOL | HIDP_PROTO_BOOT, NULL, 0);
                session->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE);
 
                session->leds = 0xff;
@@ -533,15 +669,15 @@ unlink:
 
        __hidp_unlink_session(session);
 
-       if (session->input)
+       if (session->input) {
                input_unregister_device(session->input);
+               session->input = NULL; /* don't try to free it here */
+       }
 
 failed:
        up_write(&hidp_session_sem);
 
-       if (session->input)
-               kfree(session->input);
-
+       kfree(session->input);
        kfree(session);
        return err;
 }
@@ -558,7 +694,8 @@ int hidp_del_connection(struct hidp_conndel_req *req)
        session = __hidp_get_session(&req->bdaddr);
        if (session) {
                if (req->flags & (1 << HIDP_VIRTUAL_CABLE_UNPLUG)) {
-                       hidp_send_message(session, 0x15);
+                       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);