diff options
author | Alan Ott <alan@signal11.us> | 2011-01-18 03:04:40 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2011-02-11 09:05:50 -0500 |
commit | 0ff1731a1ae51e8e48cd559d70db536281c47f8e (patch) | |
tree | 1313a34dfbe1840554aeb81dbad74719fff10b5c | |
parent | b4dbde9da8ece42bbe4c70c26bac3b28dd6a3ddb (diff) |
HID: bt: Add support for hidraw HIDIOCGFEATURE and HIDIOCSFEATURE
This patch adds support or getting and setting feature reports for bluetooth
HID devices from HIDRAW.
Signed-off-by: Alan Ott <alan@signal11.us>
Acked-by: Gustavo F. Padovan <padovan@profusion.mobi>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | net/bluetooth/hidp/core.c | 113 | ||||
-rw-r--r-- | net/bluetooth/hidp/hidp.h | 8 |
2 files changed, 117 insertions, 4 deletions
diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 5383e6c7d09d..6df8ea1c6341 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c | |||
@@ -36,6 +36,7 @@ | |||
36 | #include <linux/file.h> | 36 | #include <linux/file.h> |
37 | #include <linux/init.h> | 37 | #include <linux/init.h> |
38 | #include <linux/wait.h> | 38 | #include <linux/wait.h> |
39 | #include <linux/mutex.h> | ||
39 | #include <net/sock.h> | 40 | #include <net/sock.h> |
40 | 41 | ||
41 | #include <linux/input.h> | 42 | #include <linux/input.h> |
@@ -313,6 +314,86 @@ static int hidp_send_report(struct hidp_session *session, struct hid_report *rep | |||
313 | return hidp_queue_report(session, buf, rsize); | 314 | return hidp_queue_report(session, buf, rsize); |
314 | } | 315 | } |
315 | 316 | ||
317 | static int hidp_get_raw_report(struct hid_device *hid, | ||
318 | unsigned char report_number, | ||
319 | unsigned char *data, size_t count, | ||
320 | unsigned char report_type) | ||
321 | { | ||
322 | struct hidp_session *session = hid->driver_data; | ||
323 | struct sk_buff *skb; | ||
324 | size_t len; | ||
325 | int numbered_reports = hid->report_enum[report_type].numbered; | ||
326 | |||
327 | switch (report_type) { | ||
328 | case HID_FEATURE_REPORT: | ||
329 | report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE; | ||
330 | break; | ||
331 | case HID_INPUT_REPORT: | ||
332 | report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT; | ||
333 | break; | ||
334 | case HID_OUTPUT_REPORT: | ||
335 | report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUPUT; | ||
336 | break; | ||
337 | default: | ||
338 | return -EINVAL; | ||
339 | } | ||
340 | |||
341 | if (mutex_lock_interruptible(&session->report_mutex)) | ||
342 | return -ERESTARTSYS; | ||
343 | |||
344 | /* Set up our wait, and send the report request to the device. */ | ||
345 | session->waiting_report_type = report_type & HIDP_DATA_RTYPE_MASK; | ||
346 | session->waiting_report_number = numbered_reports ? report_number : -1; | ||
347 | set_bit(HIDP_WAITING_FOR_RETURN, &session->flags); | ||
348 | data[0] = report_number; | ||
349 | if (hidp_send_ctrl_message(hid->driver_data, report_type, data, 1)) | ||
350 | goto err_eio; | ||
351 | |||
352 | /* Wait for the return of the report. The returned report | ||
353 | gets put in session->report_return. */ | ||
354 | while (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) { | ||
355 | int res; | ||
356 | |||
357 | res = wait_event_interruptible_timeout(session->report_queue, | ||
358 | !test_bit(HIDP_WAITING_FOR_RETURN, &session->flags), | ||
359 | 5*HZ); | ||
360 | if (res == 0) { | ||
361 | /* timeout */ | ||
362 | goto err_eio; | ||
363 | } | ||
364 | if (res < 0) { | ||
365 | /* signal */ | ||
366 | goto err_restartsys; | ||
367 | } | ||
368 | } | ||
369 | |||
370 | skb = session->report_return; | ||
371 | if (skb) { | ||
372 | len = skb->len < count ? skb->len : count; | ||
373 | memcpy(data, skb->data, len); | ||
374 | |||
375 | kfree_skb(skb); | ||
376 | session->report_return = NULL; | ||
377 | } else { | ||
378 | /* Device returned a HANDSHAKE, indicating protocol error. */ | ||
379 | len = -EIO; | ||
380 | } | ||
381 | |||
382 | clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); | ||
383 | mutex_unlock(&session->report_mutex); | ||
384 | |||
385 | return len; | ||
386 | |||
387 | err_restartsys: | ||
388 | clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); | ||
389 | mutex_unlock(&session->report_mutex); | ||
390 | return -ERESTARTSYS; | ||
391 | err_eio: | ||
392 | clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); | ||
393 | mutex_unlock(&session->report_mutex); | ||
394 | return -EIO; | ||
395 | } | ||
396 | |||
316 | static int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, size_t count, | 397 | static int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, size_t count, |
317 | unsigned char report_type) | 398 | unsigned char report_type) |
318 | { | 399 | { |
@@ -409,6 +490,10 @@ static void hidp_process_handshake(struct hidp_session *session, | |||
409 | case HIDP_HSHK_ERR_INVALID_REPORT_ID: | 490 | case HIDP_HSHK_ERR_INVALID_REPORT_ID: |
410 | case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST: | 491 | case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST: |
411 | case HIDP_HSHK_ERR_INVALID_PARAMETER: | 492 | case HIDP_HSHK_ERR_INVALID_PARAMETER: |
493 | if (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) { | ||
494 | clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); | ||
495 | wake_up_interruptible(&session->report_queue); | ||
496 | } | ||
412 | /* FIXME: Call into SET_ GET_ handlers here */ | 497 | /* FIXME: Call into SET_ GET_ handlers here */ |
413 | break; | 498 | break; |
414 | 499 | ||
@@ -451,9 +536,11 @@ static void hidp_process_hid_control(struct hidp_session *session, | |||
451 | } | 536 | } |
452 | } | 537 | } |
453 | 538 | ||
454 | static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, | 539 | /* Returns true if the passed-in skb should be freed by the caller. */ |
540 | static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, | ||
455 | unsigned char param) | 541 | unsigned char param) |
456 | { | 542 | { |
543 | int done_with_skb = 1; | ||
457 | BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param); | 544 | BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param); |
458 | 545 | ||
459 | switch (param) { | 546 | switch (param) { |
@@ -465,7 +552,6 @@ static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, | |||
465 | 552 | ||
466 | if (session->hid) | 553 | if (session->hid) |
467 | hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); | 554 | hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); |
468 | |||
469 | break; | 555 | break; |
470 | 556 | ||
471 | case HIDP_DATA_RTYPE_OTHER: | 557 | case HIDP_DATA_RTYPE_OTHER: |
@@ -477,12 +563,27 @@ static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, | |||
477 | __hidp_send_ctrl_message(session, | 563 | __hidp_send_ctrl_message(session, |
478 | HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); | 564 | HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); |
479 | } | 565 | } |
566 | |||
567 | if (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) && | ||
568 | param == session->waiting_report_type) { | ||
569 | if (session->waiting_report_number < 0 || | ||
570 | session->waiting_report_number == skb->data[0]) { | ||
571 | /* hidp_get_raw_report() is waiting on this report. */ | ||
572 | session->report_return = skb; | ||
573 | done_with_skb = 0; | ||
574 | clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); | ||
575 | wake_up_interruptible(&session->report_queue); | ||
576 | } | ||
577 | } | ||
578 | |||
579 | return done_with_skb; | ||
480 | } | 580 | } |
481 | 581 | ||
482 | static void hidp_recv_ctrl_frame(struct hidp_session *session, | 582 | static void hidp_recv_ctrl_frame(struct hidp_session *session, |
483 | struct sk_buff *skb) | 583 | struct sk_buff *skb) |
484 | { | 584 | { |
485 | unsigned char hdr, type, param; | 585 | unsigned char hdr, type, param; |
586 | int free_skb = 1; | ||
486 | 587 | ||
487 | BT_DBG("session %p skb %p len %d", session, skb, skb->len); | 588 | BT_DBG("session %p skb %p len %d", session, skb, skb->len); |
488 | 589 | ||
@@ -502,7 +603,7 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session, | |||
502 | break; | 603 | break; |
503 | 604 | ||
504 | case HIDP_TRANS_DATA: | 605 | case HIDP_TRANS_DATA: |
505 | hidp_process_data(session, skb, param); | 606 | free_skb = hidp_process_data(session, skb, param); |
506 | break; | 607 | break; |
507 | 608 | ||
508 | default: | 609 | default: |
@@ -511,7 +612,8 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session, | |||
511 | break; | 612 | break; |
512 | } | 613 | } |
513 | 614 | ||
514 | kfree_skb(skb); | 615 | if (free_skb) |
616 | kfree_skb(skb); | ||
515 | } | 617 | } |
516 | 618 | ||
517 | static void hidp_recv_intr_frame(struct hidp_session *session, | 619 | static void hidp_recv_intr_frame(struct hidp_session *session, |
@@ -845,6 +947,7 @@ static int hidp_setup_hid(struct hidp_session *session, | |||
845 | hid->dev.parent = hidp_get_device(session); | 947 | hid->dev.parent = hidp_get_device(session); |
846 | hid->ll_driver = &hidp_hid_driver; | 948 | hid->ll_driver = &hidp_hid_driver; |
847 | 949 | ||
950 | hid->hid_get_raw_report = hidp_get_raw_report; | ||
848 | hid->hid_output_raw_report = hidp_output_raw_report; | 951 | hid->hid_output_raw_report = hidp_output_raw_report; |
849 | 952 | ||
850 | return 0; | 953 | return 0; |
@@ -897,6 +1000,8 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, | |||
897 | skb_queue_head_init(&session->ctrl_transmit); | 1000 | skb_queue_head_init(&session->ctrl_transmit); |
898 | skb_queue_head_init(&session->intr_transmit); | 1001 | skb_queue_head_init(&session->intr_transmit); |
899 | 1002 | ||
1003 | mutex_init(&session->report_mutex); | ||
1004 | init_waitqueue_head(&session->report_queue); | ||
900 | init_waitqueue_head(&session->startup_queue); | 1005 | init_waitqueue_head(&session->startup_queue); |
901 | session->waiting_for_startup = 1; | 1006 | session->waiting_for_startup = 1; |
902 | session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); | 1007 | session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); |
diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h index 92e093e61f27..13de5fa03480 100644 --- a/net/bluetooth/hidp/hidp.h +++ b/net/bluetooth/hidp/hidp.h | |||
@@ -80,6 +80,7 @@ | |||
80 | #define HIDP_VIRTUAL_CABLE_UNPLUG 0 | 80 | #define HIDP_VIRTUAL_CABLE_UNPLUG 0 |
81 | #define HIDP_BOOT_PROTOCOL_MODE 1 | 81 | #define HIDP_BOOT_PROTOCOL_MODE 1 |
82 | #define HIDP_BLUETOOTH_VENDOR_ID 9 | 82 | #define HIDP_BLUETOOTH_VENDOR_ID 9 |
83 | #define HIDP_WAITING_FOR_RETURN 10 | ||
83 | #define HIDP_WAITING_FOR_SEND_ACK 11 | 84 | #define HIDP_WAITING_FOR_SEND_ACK 11 |
84 | 85 | ||
85 | struct hidp_connadd_req { | 86 | struct hidp_connadd_req { |
@@ -155,6 +156,13 @@ struct hidp_session { | |||
155 | struct sk_buff_head ctrl_transmit; | 156 | struct sk_buff_head ctrl_transmit; |
156 | struct sk_buff_head intr_transmit; | 157 | struct sk_buff_head intr_transmit; |
157 | 158 | ||
159 | /* Used in hidp_get_raw_report() */ | ||
160 | int waiting_report_type; /* HIDP_DATA_RTYPE_* */ | ||
161 | int waiting_report_number; /* -1 for not numbered */ | ||
162 | struct mutex report_mutex; | ||
163 | struct sk_buff *report_return; | ||
164 | wait_queue_head_t report_queue; | ||
165 | |||
158 | /* Used in hidp_output_raw_report() */ | 166 | /* Used in hidp_output_raw_report() */ |
159 | int output_report_success; /* boolean */ | 167 | int output_report_success; /* boolean */ |
160 | 168 | ||