X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fbluetooth%2Fhidp%2Fcore.c;h=66782010f82cbacf442eba7a2d6c64f17d8c0964;hb=refs%2Fheads%2Fvserver;hp=7e8e7ba7b34f4c41c6c37c410e4fdb1502d60d08;hpb=5fc42a6ed0ec81088c37caadb45898ae6cd0ad2c;p=linux-2.6.git diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 7e8e7ba7b..66782010f 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -20,13 +20,11 @@ SOFTWARE IS DISCLAIMED. */ -#include #include #include #include #include -#include #include #include #include @@ -36,11 +34,13 @@ #include #include #include +#include #include #include #include +#include #include #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);