diff options
| author | Daniel Kurtz <djkurtz@chromium.org> | 2011-11-17 06:23:50 -0500 |
|---|---|---|
| committer | Jiri Kosina <jkosina@suse.cz> | 2011-12-21 05:18:35 -0500 |
| commit | 4371ea8202e98c8ef77ca887de3b19affbb3498f (patch) | |
| tree | fc69c8be52b10dd549237ac117870e1fb872dfe8 | |
| parent | f0befcd64bc57e6a0b7a96c37c55f79e6b999af7 (diff) | |
HID: usbhid: defer LED setting to a workqueue
Defer LED setting action to a workqueue.
This is more likely to send all LED change events in a single URB.
Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Acked-by: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
| -rw-r--r-- | drivers/hid/hid-input.c | 42 | ||||
| -rw-r--r-- | drivers/hid/usbhid/hid-core.c | 47 | ||||
| -rw-r--r-- | drivers/hid/usbhid/usbhid.h | 2 | ||||
| -rw-r--r-- | include/linux/hid.h | 2 |
4 files changed, 82 insertions, 11 deletions
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index b9b8c75a6f9a..c6ee632bfd68 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c | |||
| @@ -976,6 +976,48 @@ int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int | |||
| 976 | } | 976 | } |
| 977 | EXPORT_SYMBOL_GPL(hidinput_find_field); | 977 | EXPORT_SYMBOL_GPL(hidinput_find_field); |
| 978 | 978 | ||
| 979 | struct hid_field *hidinput_get_led_field(struct hid_device *hid) | ||
| 980 | { | ||
| 981 | struct hid_report *report; | ||
| 982 | struct hid_field *field; | ||
| 983 | int i, j; | ||
| 984 | |||
| 985 | list_for_each_entry(report, | ||
| 986 | &hid->report_enum[HID_OUTPUT_REPORT].report_list, | ||
| 987 | list) { | ||
| 988 | for (i = 0; i < report->maxfield; i++) { | ||
| 989 | field = report->field[i]; | ||
| 990 | for (j = 0; j < field->maxusage; j++) | ||
| 991 | if (field->usage[j].type == EV_LED) | ||
| 992 | return field; | ||
| 993 | } | ||
| 994 | } | ||
| 995 | return NULL; | ||
| 996 | } | ||
| 997 | EXPORT_SYMBOL_GPL(hidinput_get_led_field); | ||
| 998 | |||
| 999 | unsigned int hidinput_count_leds(struct hid_device *hid) | ||
| 1000 | { | ||
| 1001 | struct hid_report *report; | ||
| 1002 | struct hid_field *field; | ||
| 1003 | int i, j; | ||
| 1004 | unsigned int count = 0; | ||
| 1005 | |||
| 1006 | list_for_each_entry(report, | ||
| 1007 | &hid->report_enum[HID_OUTPUT_REPORT].report_list, | ||
| 1008 | list) { | ||
| 1009 | for (i = 0; i < report->maxfield; i++) { | ||
| 1010 | field = report->field[i]; | ||
| 1011 | for (j = 0; j < field->maxusage; j++) | ||
| 1012 | if (field->usage[j].type == EV_LED && | ||
| 1013 | field->value[j]) | ||
| 1014 | count += 1; | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | return count; | ||
| 1018 | } | ||
| 1019 | EXPORT_SYMBOL_GPL(hidinput_count_leds); | ||
| 1020 | |||
| 979 | static int hidinput_open(struct input_dev *dev) | 1021 | static int hidinput_open(struct input_dev *dev) |
| 980 | { | 1022 | { |
| 981 | struct hid_device *hid = input_get_drvdata(dev); | 1023 | struct hid_device *hid = input_get_drvdata(dev); |
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 719f6b02fab1..5bf91dbad59d 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c | |||
| @@ -602,6 +602,30 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns | |||
| 602 | } | 602 | } |
| 603 | EXPORT_SYMBOL_GPL(usbhid_submit_report); | 603 | EXPORT_SYMBOL_GPL(usbhid_submit_report); |
| 604 | 604 | ||
| 605 | /* Workqueue routine to send requests to change LEDs */ | ||
| 606 | static void hid_led(struct work_struct *work) | ||
| 607 | { | ||
| 608 | struct usbhid_device *usbhid = | ||
| 609 | container_of(work, struct usbhid_device, led_work); | ||
| 610 | struct hid_device *hid = usbhid->hid; | ||
| 611 | struct hid_field *field; | ||
| 612 | unsigned long flags; | ||
| 613 | |||
| 614 | field = hidinput_get_led_field(hid); | ||
| 615 | if (!field) { | ||
| 616 | hid_warn(hid, "LED event field not found\n"); | ||
| 617 | return; | ||
| 618 | } | ||
| 619 | |||
| 620 | spin_lock_irqsave(&usbhid->lock, flags); | ||
| 621 | if (!test_bit(HID_DISCONNECTED, &usbhid->iofl)) { | ||
| 622 | usbhid->ledcount = hidinput_count_leds(hid); | ||
| 623 | hid_dbg(usbhid->hid, "New ledcount = %u\n", usbhid->ledcount); | ||
| 624 | __usbhid_submit_report(hid, field->report, USB_DIR_OUT); | ||
| 625 | } | ||
| 626 | spin_unlock_irqrestore(&usbhid->lock, flags); | ||
| 627 | } | ||
| 628 | |||
| 605 | static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) | 629 | static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
| 606 | { | 630 | { |
| 607 | struct hid_device *hid = input_get_drvdata(dev); | 631 | struct hid_device *hid = input_get_drvdata(dev); |
| @@ -621,17 +645,15 @@ static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, un | |||
| 621 | return -1; | 645 | return -1; |
| 622 | } | 646 | } |
| 623 | 647 | ||
| 648 | spin_lock_irqsave(&usbhid->lock, flags); | ||
| 624 | hid_set_field(field, offset, value); | 649 | hid_set_field(field, offset, value); |
| 625 | if (value) { | 650 | spin_unlock_irqrestore(&usbhid->lock, flags); |
| 626 | spin_lock_irqsave(&usbhid->lock, flags); | 651 | |
| 627 | usbhid->ledcount++; | 652 | /* |
| 628 | spin_unlock_irqrestore(&usbhid->lock, flags); | 653 | * Defer performing requested LED action. |
| 629 | } else { | 654 | * This is more likely gather all LED changes into a single URB. |
| 630 | spin_lock_irqsave(&usbhid->lock, flags); | 655 | */ |
| 631 | usbhid->ledcount--; | 656 | schedule_work(&usbhid->led_work); |
| 632 | spin_unlock_irqrestore(&usbhid->lock, flags); | ||
| 633 | } | ||
| 634 | usbhid_submit_report(hid, field->report, USB_DIR_OUT); | ||
| 635 | 657 | ||
| 636 | return 0; | 658 | return 0; |
| 637 | } | 659 | } |
| @@ -1126,7 +1148,7 @@ static void usbhid_stop(struct hid_device *hid) | |||
| 1126 | return; | 1148 | return; |
| 1127 | 1149 | ||
| 1128 | clear_bit(HID_STARTED, &usbhid->iofl); | 1150 | clear_bit(HID_STARTED, &usbhid->iofl); |
| 1129 | spin_lock_irq(&usbhid->lock); /* Sync with error handler */ | 1151 | spin_lock_irq(&usbhid->lock); /* Sync with error and led handlers */ |
| 1130 | set_bit(HID_DISCONNECTED, &usbhid->iofl); | 1152 | set_bit(HID_DISCONNECTED, &usbhid->iofl); |
| 1131 | spin_unlock_irq(&usbhid->lock); | 1153 | spin_unlock_irq(&usbhid->lock); |
| 1132 | usb_kill_urb(usbhid->urbin); | 1154 | usb_kill_urb(usbhid->urbin); |
| @@ -1260,6 +1282,8 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * | |||
| 1260 | setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid); | 1282 | setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid); |
| 1261 | spin_lock_init(&usbhid->lock); | 1283 | spin_lock_init(&usbhid->lock); |
| 1262 | 1284 | ||
| 1285 | INIT_WORK(&usbhid->led_work, hid_led); | ||
| 1286 | |||
| 1263 | ret = hid_add_device(hid); | 1287 | ret = hid_add_device(hid); |
| 1264 | if (ret) { | 1288 | if (ret) { |
| 1265 | if (ret != -ENODEV) | 1289 | if (ret != -ENODEV) |
| @@ -1292,6 +1316,7 @@ static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid) | |||
| 1292 | { | 1316 | { |
| 1293 | del_timer_sync(&usbhid->io_retry); | 1317 | del_timer_sync(&usbhid->io_retry); |
| 1294 | cancel_work_sync(&usbhid->reset_work); | 1318 | cancel_work_sync(&usbhid->reset_work); |
| 1319 | cancel_work_sync(&usbhid->led_work); | ||
| 1295 | } | 1320 | } |
| 1296 | 1321 | ||
| 1297 | static void hid_cease_io(struct usbhid_device *usbhid) | 1322 | static void hid_cease_io(struct usbhid_device *usbhid) |
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h index 2d8957c11d2d..cb8f703efde5 100644 --- a/drivers/hid/usbhid/usbhid.h +++ b/drivers/hid/usbhid/usbhid.h | |||
| @@ -96,6 +96,8 @@ struct usbhid_device { | |||
| 96 | struct work_struct reset_work; /* Task context for resets */ | 96 | struct work_struct reset_work; /* Task context for resets */ |
| 97 | wait_queue_head_t wait; /* For sleeping */ | 97 | wait_queue_head_t wait; /* For sleeping */ |
| 98 | int ledcount; /* counting the number of active leds */ | 98 | int ledcount; /* counting the number of active leds */ |
| 99 | |||
| 100 | struct work_struct led_work; /* Task context for setting LEDs */ | ||
| 99 | }; | 101 | }; |
| 100 | 102 | ||
| 101 | #define hid_to_usb_dev(hid_dev) \ | 103 | #define hid_to_usb_dev(hid_dev) \ |
diff --git a/include/linux/hid.h b/include/linux/hid.h index 7f344c3da767..999a54c72b20 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h | |||
| @@ -727,6 +727,8 @@ extern void hidinput_disconnect(struct hid_device *); | |||
| 727 | int hid_set_field(struct hid_field *, unsigned, __s32); | 727 | int hid_set_field(struct hid_field *, unsigned, __s32); |
| 728 | int hid_input_report(struct hid_device *, int type, u8 *, int, int); | 728 | int hid_input_report(struct hid_device *, int type, u8 *, int, int); |
| 729 | int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field); | 729 | int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field); |
| 730 | struct hid_field *hidinput_get_led_field(struct hid_device *hid); | ||
| 731 | unsigned int hidinput_count_leds(struct hid_device *hid); | ||
| 730 | void hid_output_report(struct hid_report *report, __u8 *data); | 732 | void hid_output_report(struct hid_report *report, __u8 *data); |
| 731 | struct hid_device *hid_allocate_device(void); | 733 | struct hid_device *hid_allocate_device(void); |
| 732 | struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); | 734 | struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); |
