diff options
| author | Marcel Holtmann <marcel@holtmann.org> | 2007-02-17 17:58:49 -0500 |
|---|---|---|
| committer | David S. Miller <davem@sunset.davemloft.net> | 2007-02-26 14:42:38 -0500 |
| commit | e1aaadd4d8162a2c33e41dd5a72234ea4d3b014f (patch) | |
| tree | 5725bc4ce77c42fcf9fd6b3adc173f8403e492ef | |
| parent | a83d6c0de8811d7bcc4eb67ed199d1120ca6cad8 (diff) | |
[Bluetooth] Add support for using the HID subsystem
This patch extends the current Bluetooth HID support to use the new
HID subsystem and adds full report mode support.
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
| -rw-r--r-- | net/bluetooth/hidp/Kconfig | 1 | ||||
| -rw-r--r-- | net/bluetooth/hidp/core.c | 170 | ||||
| -rw-r--r-- | net/bluetooth/hidp/hidp.h | 2 |
3 files changed, 164 insertions, 9 deletions
diff --git a/net/bluetooth/hidp/Kconfig b/net/bluetooth/hidp/Kconfig index c6abf2a5a932..98fdfa1fbddd 100644 --- a/net/bluetooth/hidp/Kconfig +++ b/net/bluetooth/hidp/Kconfig | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | config BT_HIDP | 1 | config BT_HIDP |
| 2 | tristate "HIDP protocol support" | 2 | tristate "HIDP protocol support" |
| 3 | depends on BT && BT_L2CAP && INPUT | 3 | depends on BT && BT_L2CAP && INPUT |
| 4 | select HID | ||
| 4 | help | 5 | help |
| 5 | HIDP (Human Interface Device Protocol) is a transport layer | 6 | HIDP (Human Interface Device Protocol) is a transport layer |
| 6 | for HID reports. HIDP is required for the Bluetooth Human | 7 | for HID reports. HIDP is required for the Bluetooth Human |
diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 4b99c5e4478d..05e23bbcb0a1 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c | |||
| @@ -38,6 +38,7 @@ | |||
| 38 | #include <net/sock.h> | 38 | #include <net/sock.h> |
| 39 | 39 | ||
| 40 | #include <linux/input.h> | 40 | #include <linux/input.h> |
| 41 | #include <linux/hid.h> | ||
| 41 | 42 | ||
| 42 | #include <net/bluetooth/bluetooth.h> | 43 | #include <net/bluetooth/bluetooth.h> |
| 43 | #include <net/bluetooth/hci_core.h> | 44 | #include <net/bluetooth/hci_core.h> |
| @@ -50,7 +51,7 @@ | |||
| 50 | #define BT_DBG(D...) | 51 | #define BT_DBG(D...) |
| 51 | #endif | 52 | #endif |
| 52 | 53 | ||
| 53 | #define VERSION "1.1" | 54 | #define VERSION "1.2" |
| 54 | 55 | ||
| 55 | static DECLARE_RWSEM(hidp_session_sem); | 56 | static DECLARE_RWSEM(hidp_session_sem); |
| 56 | static LIST_HEAD(hidp_session_list); | 57 | static LIST_HEAD(hidp_session_list); |
| @@ -124,15 +125,22 @@ static void __hidp_copy_session(struct hidp_session *session, struct hidp_connin | |||
| 124 | else | 125 | else |
| 125 | strncpy(ci->name, "HID Boot Device", 128); | 126 | strncpy(ci->name, "HID Boot Device", 128); |
| 126 | } | 127 | } |
| 128 | |||
| 129 | if (session->hid) { | ||
| 130 | ci->vendor = session->hid->vendor; | ||
| 131 | ci->product = session->hid->product; | ||
| 132 | ci->version = session->hid->version; | ||
| 133 | strncpy(ci->name, session->hid->name, 128); | ||
| 134 | } | ||
| 127 | } | 135 | } |
| 128 | 136 | ||
| 129 | static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) | 137 | static inline int hidp_queue_event(struct hidp_session *session, struct input_dev *dev, |
| 138 | unsigned int type, unsigned int code, int value) | ||
| 130 | { | 139 | { |
| 131 | struct hidp_session *session = dev->private; | ||
| 132 | struct sk_buff *skb; | ||
| 133 | unsigned char newleds; | 140 | unsigned char newleds; |
| 141 | struct sk_buff *skb; | ||
| 134 | 142 | ||
| 135 | BT_DBG("input %p type %d code %d value %d", dev, type, code, value); | 143 | BT_DBG("session %p type %d code %d value %d", session, type, code, value); |
| 136 | 144 | ||
| 137 | if (type != EV_LED) | 145 | if (type != EV_LED) |
| 138 | return -1; | 146 | return -1; |
| @@ -164,6 +172,21 @@ static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned i | |||
| 164 | return 0; | 172 | return 0; |
| 165 | } | 173 | } |
| 166 | 174 | ||
| 175 | static int hidp_hidinput_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) | ||
| 176 | { | ||
| 177 | struct hid_device *hid = dev->private; | ||
| 178 | struct hidp_session *session = hid->driver_data; | ||
| 179 | |||
| 180 | return hidp_queue_event(session, dev, type, code, value); | ||
| 181 | } | ||
| 182 | |||
| 183 | static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) | ||
| 184 | { | ||
| 185 | struct hidp_session *session = dev->private; | ||
| 186 | |||
| 187 | return hidp_queue_event(session, dev, type, code, value); | ||
| 188 | } | ||
| 189 | |||
| 167 | static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) | 190 | static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) |
| 168 | { | 191 | { |
| 169 | struct input_dev *dev = session->input; | 192 | struct input_dev *dev = session->input; |
| @@ -219,6 +242,42 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) | |||
| 219 | input_sync(dev); | 242 | input_sync(dev); |
| 220 | } | 243 | } |
| 221 | 244 | ||
| 245 | static inline int hidp_queue_report(struct hidp_session *session, unsigned char *data, int size) | ||
| 246 | { | ||
| 247 | struct sk_buff *skb; | ||
| 248 | |||
| 249 | BT_DBG("session %p hid %p data %p size %d", session, device, data, size); | ||
| 250 | |||
| 251 | if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) { | ||
| 252 | BT_ERR("Can't allocate memory for new frame"); | ||
| 253 | return -ENOMEM; | ||
| 254 | } | ||
| 255 | |||
| 256 | *skb_put(skb, 1) = 0xa2; | ||
| 257 | if (size > 0) | ||
| 258 | memcpy(skb_put(skb, size), data, size); | ||
| 259 | |||
| 260 | skb_queue_tail(&session->intr_transmit, skb); | ||
| 261 | |||
| 262 | hidp_schedule(session); | ||
| 263 | |||
| 264 | return 0; | ||
| 265 | } | ||
| 266 | |||
| 267 | static int hidp_send_report(struct hidp_session *session, struct hid_report *report) | ||
| 268 | { | ||
| 269 | unsigned char buf[32]; | ||
| 270 | int rsize; | ||
| 271 | |||
| 272 | rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0); | ||
| 273 | if (rsize > sizeof(buf)) | ||
| 274 | return -EIO; | ||
| 275 | |||
| 276 | hid_output_report(report, buf); | ||
| 277 | |||
| 278 | return hidp_queue_report(session, buf, rsize); | ||
| 279 | } | ||
| 280 | |||
| 222 | static void hidp_idle_timeout(unsigned long arg) | 281 | static void hidp_idle_timeout(unsigned long arg) |
| 223 | { | 282 | { |
| 224 | struct hidp_session *session = (struct hidp_session *) arg; | 283 | struct hidp_session *session = (struct hidp_session *) arg; |
| @@ -346,6 +405,10 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf | |||
| 346 | 405 | ||
| 347 | if (session->input) | 406 | if (session->input) |
| 348 | hidp_input_report(session, skb); | 407 | hidp_input_report(session, skb); |
| 408 | |||
| 409 | if (session->hid) | ||
| 410 | hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); | ||
| 411 | |||
| 349 | break; | 412 | break; |
| 350 | 413 | ||
| 351 | case HIDP_DATA_RTYPE_OTHER: | 414 | case HIDP_DATA_RTYPE_OTHER: |
| @@ -404,8 +467,14 @@ static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_ | |||
| 404 | 467 | ||
| 405 | if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { | 468 | if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { |
| 406 | hidp_set_timer(session); | 469 | hidp_set_timer(session); |
| 470 | |||
| 407 | if (session->input) | 471 | if (session->input) |
| 408 | hidp_input_report(session, skb); | 472 | hidp_input_report(session, skb); |
| 473 | |||
| 474 | if (session->hid) { | ||
| 475 | hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1); | ||
| 476 | BT_DBG("report len %d", skb->len); | ||
| 477 | } | ||
| 409 | } else { | 478 | } else { |
| 410 | BT_DBG("Unsupported protocol header 0x%02x", hdr); | 479 | BT_DBG("Unsupported protocol header 0x%02x", hdr); |
| 411 | } | 480 | } |
| @@ -471,6 +540,11 @@ static int hidp_session(void *arg) | |||
| 471 | product = session->input->id.product; | 540 | product = session->input->id.product; |
| 472 | } | 541 | } |
| 473 | 542 | ||
| 543 | if (session->hid) { | ||
| 544 | vendor = session->hid->vendor; | ||
| 545 | product = session->hid->product; | ||
| 546 | } | ||
| 547 | |||
| 474 | daemonize("khidpd_%04x%04x", vendor, product); | 548 | daemonize("khidpd_%04x%04x", vendor, product); |
| 475 | set_user_nice(current, -15); | 549 | set_user_nice(current, -15); |
| 476 | current->flags |= PF_NOFREEZE; | 550 | current->flags |= PF_NOFREEZE; |
| @@ -521,6 +595,12 @@ static int hidp_session(void *arg) | |||
| 521 | session->input = NULL; | 595 | session->input = NULL; |
| 522 | } | 596 | } |
| 523 | 597 | ||
| 598 | if (session->hid) { | ||
| 599 | if (session->hid->claimed & HID_CLAIMED_INPUT) | ||
| 600 | hidinput_disconnect(session->hid); | ||
| 601 | hid_free_device(session->hid); | ||
| 602 | } | ||
| 603 | |||
| 524 | up_write(&hidp_session_sem); | 604 | up_write(&hidp_session_sem); |
| 525 | 605 | ||
| 526 | kfree(session); | 606 | kfree(session); |
| @@ -590,6 +670,44 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co | |||
| 590 | input_register_device(input); | 670 | input_register_device(input); |
| 591 | } | 671 | } |
| 592 | 672 | ||
| 673 | static inline void hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req) | ||
| 674 | { | ||
| 675 | struct hid_device *hid = session->hid; | ||
| 676 | struct hid_report *report; | ||
| 677 | bdaddr_t src, dst; | ||
| 678 | |||
| 679 | baswap(&src, &bt_sk(session->ctrl_sock->sk)->src); | ||
| 680 | baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst); | ||
| 681 | |||
| 682 | hid->driver_data = session; | ||
| 683 | |||
| 684 | hid->country = req->country; | ||
| 685 | |||
| 686 | hid->bus = BUS_BLUETOOTH; | ||
| 687 | hid->vendor = req->vendor; | ||
| 688 | hid->product = req->product; | ||
| 689 | hid->version = req->version; | ||
| 690 | |||
| 691 | strncpy(hid->name, req->name, 128); | ||
| 692 | strncpy(hid->phys, batostr(&src), 64); | ||
| 693 | strncpy(hid->uniq, batostr(&dst), 64); | ||
| 694 | |||
| 695 | hid->dev = hidp_get_device(session); | ||
| 696 | |||
| 697 | hid->hidinput_input_event = hidp_hidinput_event; | ||
| 698 | |||
| 699 | list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list) | ||
| 700 | hidp_send_report(session, report); | ||
| 701 | |||
| 702 | list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list) | ||
| 703 | hidp_send_report(session, report); | ||
| 704 | |||
| 705 | if (hidinput_connect(hid) == 0) { | ||
| 706 | hid->claimed |= HID_CLAIMED_INPUT; | ||
| 707 | hid_ff_init(hid); | ||
| 708 | } | ||
| 709 | } | ||
| 710 | |||
| 593 | int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock) | 711 | int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock) |
| 594 | { | 712 | { |
| 595 | struct hidp_session *session, *s; | 713 | struct hidp_session *session, *s; |
| @@ -605,10 +723,38 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, | |||
| 605 | if (!session) | 723 | if (!session) |
| 606 | return -ENOMEM; | 724 | return -ENOMEM; |
| 607 | 725 | ||
| 608 | session->input = input_allocate_device(); | 726 | BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size); |
| 609 | if (!session->input) { | 727 | |
| 610 | kfree(session); | 728 | if (req->rd_size > 0) { |
| 611 | return -ENOMEM; | 729 | unsigned char *buf = kmalloc(req->rd_size, GFP_KERNEL); |
| 730 | |||
| 731 | if (!buf) { | ||
| 732 | kfree(session); | ||
| 733 | return -ENOMEM; | ||
| 734 | } | ||
| 735 | |||
| 736 | if (copy_from_user(buf, req->rd_data, req->rd_size)) { | ||
| 737 | kfree(buf); | ||
| 738 | kfree(session); | ||
| 739 | return -EFAULT; | ||
| 740 | } | ||
| 741 | |||
| 742 | session->hid = hid_parse_report(buf, req->rd_size); | ||
| 743 | |||
| 744 | kfree(buf); | ||
| 745 | |||
| 746 | if (!session->hid) { | ||
| 747 | kfree(session); | ||
| 748 | return -EINVAL; | ||
| 749 | } | ||
| 750 | } | ||
| 751 | |||
| 752 | if (!session->hid) { | ||
| 753 | session->input = input_allocate_device(); | ||
| 754 | if (!session->input) { | ||
| 755 | kfree(session); | ||
| 756 | return -ENOMEM; | ||
| 757 | } | ||
| 612 | } | 758 | } |
| 613 | 759 | ||
| 614 | down_write(&hidp_session_sem); | 760 | down_write(&hidp_session_sem); |
| @@ -644,6 +790,9 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, | |||
| 644 | if (session->input) | 790 | if (session->input) |
| 645 | hidp_setup_input(session, req); | 791 | hidp_setup_input(session, req); |
| 646 | 792 | ||
| 793 | if (session->hid) | ||
| 794 | hidp_setup_hid(session, req); | ||
| 795 | |||
| 647 | __hidp_link_session(session); | 796 | __hidp_link_session(session); |
| 648 | 797 | ||
| 649 | hidp_set_timer(session); | 798 | hidp_set_timer(session); |
| @@ -677,6 +826,9 @@ unlink: | |||
| 677 | failed: | 826 | failed: |
| 678 | up_write(&hidp_session_sem); | 827 | up_write(&hidp_session_sem); |
| 679 | 828 | ||
| 829 | if (session->hid) | ||
| 830 | hid_free_device(session->hid); | ||
| 831 | |||
| 680 | kfree(session->input); | 832 | kfree(session->input); |
| 681 | kfree(session); | 833 | kfree(session); |
| 682 | return err; | 834 | return err; |
diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h index a326601c8f41..343fb0566b3e 100644 --- a/net/bluetooth/hidp/hidp.h +++ b/net/bluetooth/hidp/hidp.h | |||
| @@ -145,6 +145,8 @@ struct hidp_session { | |||
| 145 | 145 | ||
| 146 | struct input_dev *input; | 146 | struct input_dev *input; |
| 147 | 147 | ||
| 148 | struct hid_device *hid; | ||
| 149 | |||
| 148 | struct timer_list timer; | 150 | struct timer_list timer; |
| 149 | 151 | ||
| 150 | struct sk_buff_head ctrl_transmit; | 152 | struct sk_buff_head ctrl_transmit; |
