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); |