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>
This commit is contained in:
		
					parent
					
						
							
								f0befcd64b
							
						
					
				
			
			
				commit
				
					
						4371ea8202
					
				
			
		
					 4 changed files with 82 additions and 11 deletions
				
			
		| 
						 | 
					@ -976,6 +976,48 @@ int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(hidinput_find_field);
 | 
					EXPORT_SYMBOL_GPL(hidinput_find_field);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct hid_field *hidinput_get_led_field(struct hid_device *hid)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct hid_report *report;
 | 
				
			||||||
 | 
						struct hid_field *field;
 | 
				
			||||||
 | 
						int i, j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list_for_each_entry(report,
 | 
				
			||||||
 | 
								    &hid->report_enum[HID_OUTPUT_REPORT].report_list,
 | 
				
			||||||
 | 
								    list) {
 | 
				
			||||||
 | 
							for (i = 0; i < report->maxfield; i++) {
 | 
				
			||||||
 | 
								field = report->field[i];
 | 
				
			||||||
 | 
								for (j = 0; j < field->maxusage; j++)
 | 
				
			||||||
 | 
									if (field->usage[j].type == EV_LED)
 | 
				
			||||||
 | 
										return field;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(hidinput_get_led_field);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unsigned int hidinput_count_leds(struct hid_device *hid)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct hid_report *report;
 | 
				
			||||||
 | 
						struct hid_field *field;
 | 
				
			||||||
 | 
						int i, j;
 | 
				
			||||||
 | 
						unsigned int count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list_for_each_entry(report,
 | 
				
			||||||
 | 
								    &hid->report_enum[HID_OUTPUT_REPORT].report_list,
 | 
				
			||||||
 | 
								    list) {
 | 
				
			||||||
 | 
							for (i = 0; i < report->maxfield; i++) {
 | 
				
			||||||
 | 
								field = report->field[i];
 | 
				
			||||||
 | 
								for (j = 0; j < field->maxusage; j++)
 | 
				
			||||||
 | 
									if (field->usage[j].type == EV_LED &&
 | 
				
			||||||
 | 
									    field->value[j])
 | 
				
			||||||
 | 
										count += 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(hidinput_count_leds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int hidinput_open(struct input_dev *dev)
 | 
					static int hidinput_open(struct input_dev *dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct hid_device *hid = input_get_drvdata(dev);
 | 
						struct hid_device *hid = input_get_drvdata(dev);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -602,6 +602,30 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(usbhid_submit_report);
 | 
					EXPORT_SYMBOL_GPL(usbhid_submit_report);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Workqueue routine to send requests to change LEDs */
 | 
				
			||||||
 | 
					static void hid_led(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct usbhid_device *usbhid =
 | 
				
			||||||
 | 
							container_of(work, struct usbhid_device, led_work);
 | 
				
			||||||
 | 
						struct hid_device *hid = usbhid->hid;
 | 
				
			||||||
 | 
						struct hid_field *field;
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						field = hidinput_get_led_field(hid);
 | 
				
			||||||
 | 
						if (!field) {
 | 
				
			||||||
 | 
							hid_warn(hid, "LED event field not found\n");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&usbhid->lock, flags);
 | 
				
			||||||
 | 
						if (!test_bit(HID_DISCONNECTED, &usbhid->iofl)) {
 | 
				
			||||||
 | 
							usbhid->ledcount = hidinput_count_leds(hid);
 | 
				
			||||||
 | 
							hid_dbg(usbhid->hid, "New ledcount = %u\n", usbhid->ledcount);
 | 
				
			||||||
 | 
							__usbhid_submit_report(hid, field->report, USB_DIR_OUT);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&usbhid->lock, flags);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 | 
					static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct hid_device *hid = input_get_drvdata(dev);
 | 
						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
 | 
				
			||||||
		return -1;
 | 
							return -1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&usbhid->lock, flags);
 | 
				
			||||||
	hid_set_field(field, offset, value);
 | 
						hid_set_field(field, offset, value);
 | 
				
			||||||
	if (value) {
 | 
					 | 
				
			||||||
		spin_lock_irqsave(&usbhid->lock, flags);
 | 
					 | 
				
			||||||
		usbhid->ledcount++;
 | 
					 | 
				
			||||||
	spin_unlock_irqrestore(&usbhid->lock, flags);
 | 
						spin_unlock_irqrestore(&usbhid->lock, flags);
 | 
				
			||||||
	} else {
 | 
					
 | 
				
			||||||
		spin_lock_irqsave(&usbhid->lock, flags);
 | 
						/*
 | 
				
			||||||
		usbhid->ledcount--;
 | 
						 * Defer performing requested LED action.
 | 
				
			||||||
		spin_unlock_irqrestore(&usbhid->lock, flags);
 | 
						 * This is more likely gather all LED changes into a single URB.
 | 
				
			||||||
	}
 | 
						 */
 | 
				
			||||||
	usbhid_submit_report(hid, field->report, USB_DIR_OUT);
 | 
						schedule_work(&usbhid->led_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1126,7 +1148,7 @@ static void usbhid_stop(struct hid_device *hid)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clear_bit(HID_STARTED, &usbhid->iofl);
 | 
						clear_bit(HID_STARTED, &usbhid->iofl);
 | 
				
			||||||
	spin_lock_irq(&usbhid->lock);	/* Sync with error handler */
 | 
						spin_lock_irq(&usbhid->lock);	/* Sync with error and led handlers */
 | 
				
			||||||
	set_bit(HID_DISCONNECTED, &usbhid->iofl);
 | 
						set_bit(HID_DISCONNECTED, &usbhid->iofl);
 | 
				
			||||||
	spin_unlock_irq(&usbhid->lock);
 | 
						spin_unlock_irq(&usbhid->lock);
 | 
				
			||||||
	usb_kill_urb(usbhid->urbin);
 | 
						usb_kill_urb(usbhid->urbin);
 | 
				
			||||||
| 
						 | 
					@ -1260,6 +1282,8 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
 | 
				
			||||||
	setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
 | 
						setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
 | 
				
			||||||
	spin_lock_init(&usbhid->lock);
 | 
						spin_lock_init(&usbhid->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						INIT_WORK(&usbhid->led_work, hid_led);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = hid_add_device(hid);
 | 
						ret = hid_add_device(hid);
 | 
				
			||||||
	if (ret) {
 | 
						if (ret) {
 | 
				
			||||||
		if (ret != -ENODEV)
 | 
							if (ret != -ENODEV)
 | 
				
			||||||
| 
						 | 
					@ -1292,6 +1316,7 @@ static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	del_timer_sync(&usbhid->io_retry);
 | 
						del_timer_sync(&usbhid->io_retry);
 | 
				
			||||||
	cancel_work_sync(&usbhid->reset_work);
 | 
						cancel_work_sync(&usbhid->reset_work);
 | 
				
			||||||
 | 
						cancel_work_sync(&usbhid->led_work);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void hid_cease_io(struct usbhid_device *usbhid)
 | 
					static void hid_cease_io(struct usbhid_device *usbhid)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,6 +96,8 @@ struct usbhid_device {
 | 
				
			||||||
	struct work_struct reset_work;                                  /* Task context for resets */
 | 
						struct work_struct reset_work;                                  /* Task context for resets */
 | 
				
			||||||
	wait_queue_head_t wait;						/* For sleeping */
 | 
						wait_queue_head_t wait;						/* For sleeping */
 | 
				
			||||||
	int ledcount;							/* counting the number of active leds */
 | 
						int ledcount;							/* counting the number of active leds */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct work_struct led_work;					/* Task context for setting LEDs */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define	hid_to_usb_dev(hid_dev) \
 | 
					#define	hid_to_usb_dev(hid_dev) \
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -727,6 +727,8 @@ extern void hidinput_disconnect(struct hid_device *);
 | 
				
			||||||
int hid_set_field(struct hid_field *, unsigned, __s32);
 | 
					int hid_set_field(struct hid_field *, unsigned, __s32);
 | 
				
			||||||
int hid_input_report(struct hid_device *, int type, u8 *, int, int);
 | 
					int hid_input_report(struct hid_device *, int type, u8 *, int, int);
 | 
				
			||||||
int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field);
 | 
					int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field);
 | 
				
			||||||
 | 
					struct hid_field *hidinput_get_led_field(struct hid_device *hid);
 | 
				
			||||||
 | 
					unsigned int hidinput_count_leds(struct hid_device *hid);
 | 
				
			||||||
void hid_output_report(struct hid_report *report, __u8 *data);
 | 
					void hid_output_report(struct hid_report *report, __u8 *data);
 | 
				
			||||||
struct hid_device *hid_allocate_device(void);
 | 
					struct hid_device *hid_allocate_device(void);
 | 
				
			||||||
struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id);
 | 
					struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue