From c196adf87514560f867492978ae350d4bbced0bd Mon Sep 17 00:00:00 2001 From: Willem Penninckx Date: Wed, 23 Nov 2011 11:25:34 +0100 Subject: HID: usbkbd: synchronize LED URB submission usb_kbd_event() and usb_kbd_led() can be called concurrently, but they are not synchronized. They both readwrite kbd->leds, and usb_kbd_event() originally just checked the URB status field, while urb.h states that "It [status field] should not be examined before the URB is returned to the completion handler." To fix this unsynchronized behavior, this patch introduces a boolean representing whether the URB is submitted, and a spinlock. Signed-off-by: Willem Penninckx Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/usbkbd.c | 63 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index 065817329f0..052346f3ccb 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -64,6 +64,32 @@ static const unsigned char usb_kbd_keycode[256] = { 150,158,159,128,136,177,178,176,142,152,173,140 }; + +/** + * struct usb_kbd - state of each attached keyboard + * @dev: input device associated with this keyboard + * @usbdev: usb device associated with this keyboard + * @old: data received in the past from the @irq URB representing which + * keys were pressed. By comparing with the current list of keys + * that are pressed, we are able to see key releases. + * @irq: URB for receiving a list of keys that are pressed when a + * new key is pressed or a key that was pressed is released. + * @led: URB for sending LEDs (e.g. numlock, ...) + * @newleds: data that will be sent with the @led URB representing which LEDs + should be on + * @name: Name of the keyboard. @dev's name field points to this buffer + * @phys: Physical path of the keyboard. @dev's phys field points to this + * buffer + * @new: Buffer for the @irq URB + * @cr: Control request for @led URB + * @leds: Buffer for the @led URB + * @new_dma: DMA address for @irq URB + * @leds_dma: DMA address for @led URB + * @leds_lock: spinlock that protects @leds, @newleds, and @led_urb_submitted + * @led_urb_submitted: indicates whether @led is in progress, i.e. it has been + * submitted and its completion handler has not returned yet + * without resubmitting @led + */ struct usb_kbd { struct input_dev *dev; struct usb_device *usbdev; @@ -78,6 +104,10 @@ struct usb_kbd { unsigned char *leds; dma_addr_t new_dma; dma_addr_t leds_dma; + + spinlock_t leds_lock; + bool led_urb_submitted; + }; static void usb_kbd_irq(struct urb *urb) @@ -136,44 +166,66 @@ resubmit: static int usb_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { + unsigned long flags; struct usb_kbd *kbd = input_get_drvdata(dev); if (type != EV_LED) return -1; + spin_lock_irqsave(&kbd->leds_lock, flags); kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) | (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) | (!!test_bit(LED_NUML, dev->led)); - if (kbd->led->status == -EINPROGRESS) + if (kbd->led_urb_submitted){ + spin_unlock_irqrestore(&kbd->leds_lock, flags); return 0; + } - if (*(kbd->leds) == kbd->newleds) + if (*(kbd->leds) == kbd->newleds){ + spin_unlock_irqrestore(&kbd->leds_lock, flags); return 0; + } *(kbd->leds) = kbd->newleds; + kbd->led->dev = kbd->usbdev; if (usb_submit_urb(kbd->led, GFP_ATOMIC)) pr_err("usb_submit_urb(leds) failed\n"); - + else + kbd->led_urb_submitted = true; + + spin_unlock_irqrestore(&kbd->leds_lock, flags); + return 0; } static void usb_kbd_led(struct urb *urb) { + unsigned long flags; struct usb_kbd *kbd = urb->context; if (urb->status) hid_warn(urb->dev, "led urb status %d received\n", urb->status); - if (*(kbd->leds) == kbd->newleds) + spin_lock_irqsave(&kbd->leds_lock, flags); + + if (*(kbd->leds) == kbd->newleds){ + kbd->led_urb_submitted = false; + spin_unlock_irqrestore(&kbd->leds_lock, flags); return; + } *(kbd->leds) = kbd->newleds; + kbd->led->dev = kbd->usbdev; - if (usb_submit_urb(kbd->led, GFP_ATOMIC)) + if (usb_submit_urb(kbd->led, GFP_ATOMIC)){ hid_err(urb->dev, "usb_submit_urb(leds) failed\n"); + kbd->led_urb_submitted = false; + } + spin_unlock_irqrestore(&kbd->leds_lock, flags); + } static int usb_kbd_open(struct input_dev *dev) @@ -252,6 +304,7 @@ static int usb_kbd_probe(struct usb_interface *iface, kbd->usbdev = dev; kbd->dev = input_dev; + spin_lock_init(&kbd->leds_lock); if (dev->manufacturer) strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); -- cgit v1.2.3 From a2b2c20ba2f6e22c065f401d63f7f883779cf642 Mon Sep 17 00:00:00 2001 From: Willem Penninckx Date: Wed, 23 Nov 2011 11:26:45 +0100 Subject: HID: usbkbd: kill LED URB on disconnect The LED URB was left unkilled when the USB device is disconnected. Signed-off-by: Willem Penninckx Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/usbkbd.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index 052346f3ccb..a7b925aeaf2 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -387,6 +387,7 @@ static void usb_kbd_disconnect(struct usb_interface *intf) if (kbd) { usb_kill_urb(kbd->irq); input_unregister_device(kbd->dev); + usb_kill_urb(kbd->led); usb_kbd_free_mem(interface_to_usbdev(intf), kbd); kfree(kbd); } -- cgit v1.2.3 From b7ea95ff9baab144dacdc30d752307938c5ab6bf Mon Sep 17 00:00:00 2001 From: Aaron Tian Date: Thu, 15 Dec 2011 11:09:06 +0800 Subject: HID: multitouch: support PixArt optical touch screen This patch modifies hid-multitouch driver for supporting PixArt optical touch screen. Because of the device does not have to set initial report, we apply "HID_QUIRK_NO_INIT_REPORTS" quirk and add the device into hid_blacklist[] Signed-off-by: Aaron Tian Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-core.c | 3 +++ drivers/hid/hid-ids.h | 5 +++++ drivers/hid/hid-multitouch.c | 15 +++++++++++++++ drivers/hid/usbhid/hid-quirks.c | 3 +++ 5 files changed, 27 insertions(+) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index ee14f78139f..5d6eb4e5af3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -349,6 +349,7 @@ config HID_MULTITOUCH - Lumio CrystalTouch panels - MosArt dual-touch panels - PenMount dual touch panels + - PixArt optical touch screen - Pixcir dual touch panels - Quanta panels - eGalax dual-touch panels, including the Joojoo and Wetab tablets diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 972f6040181..2f8ad34a6a9 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1501,6 +1501,9 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_WKB2000) }, { HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_PCI) }, { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2) }, { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index cddb639654c..96e139f2351 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -577,6 +577,11 @@ #define USB_VENDOR_ID_PI_ENGINEERING 0x05f3 #define USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL 0xff +#define USB_VENDOR_ID_PIXART 0x093a +#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN 0x8001 +#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1 0x8002 +#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2 0x8003 + #define USB_VENDOR_ID_PLAYDOTCOM 0x0b43 #define USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII 0x0003 diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index de447d7fdc5..513329d9833 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -93,6 +93,7 @@ struct mt_device { #define MT_CLS_DUAL_INRANGE_CONTACTID 0x0006 #define MT_CLS_DUAL_INRANGE_CONTACTNUMBER 0x0007 #define MT_CLS_DUAL_NSMU_CONTACTID 0x0008 +#define MT_CLS_INRANGE_CONTACTNUMBER 0x0009 /* vendor specific classes */ #define MT_CLS_3M 0x0101 @@ -159,6 +160,9 @@ struct mt_class mt_classes[] = { .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP | MT_QUIRK_SLOT_IS_CONTACTID, .maxcontacts = 2 }, + { .name = MT_CLS_INRANGE_CONTACTNUMBER, + .quirks = MT_QUIRK_VALID_IS_INRANGE | + MT_QUIRK_SLOT_IS_CONTACTNUMBER }, /* * vendor specific classes @@ -784,6 +788,17 @@ static const struct hid_device_id mt_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_PCI) }, + /* PixArt optical touch screen */ + { .driver_data = MT_CLS_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_PIXART, + USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN) }, + { .driver_data = MT_CLS_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_PIXART, + USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1) }, + { .driver_data = MT_CLS_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_PIXART, + USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2) }, + /* PixCir-based panels */ { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, HID_USB_DEVICE(USB_VENDOR_ID_HANVON, diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index 4ea464151c3..b7962afa0b8 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -66,6 +66,9 @@ static const struct hid_blacklist { { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_AXIS_295, HID_QUIRK_NOGET }, { USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET }, { USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS }, + { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1, HID_QUIRK_NO_INIT_REPORTS }, + { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS, HID_QUIRK_NOGET }, { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NOGET }, { USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE, HID_QUIRK_NOGET }, -- cgit v1.2.3 From cd07655e732b265eb12a5804ddf7ffae945c9ab9 Mon Sep 17 00:00:00 2001 From: Ignaz Forster Date: Sat, 17 Dec 2011 13:16:57 +0100 Subject: HID: Enable HID_QUIRK_MULTI_INPUT for Trio Linker Plus II Add quirk for the Trio Linker Plus II - the adapter supports several controllers simultaneously, generating a new HID entry for each connected device. Signed-off-by: Ignaz Forster Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-quirks.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index 5028d60a22a..4d256a7d287 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -47,6 +47,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016, HID_QUIRK_FULLSPEED_INTERVAL }, + { USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK, HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS }, -- cgit v1.2.3 From ede6a8b239736acd55ad8a219b2bd2ae7f551fb7 Mon Sep 17 00:00:00 2001 From: Daniel Kurtz Date: Thu, 17 Nov 2011 19:23:48 +0800 Subject: HID: usbhid: remove LED_ON LED_ON was defined in the original version of the hid-core autosuspend patch. However, during review, the setting and clearing of it was redone using ledcount. The test was left in accidentally. Signed-off-by: Daniel Kurtz Acked-by: Oliver Neukum Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 10 ---------- drivers/hid/usbhid/usbhid.h | 1 - 2 files changed, 11 deletions(-) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index b403fcef0b8..66061349be8 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1367,16 +1367,6 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) return -EIO; } - if (!ignoreled && PMSG_IS_AUTO(message)) { - spin_lock_irq(&usbhid->lock); - if (test_bit(HID_LED_ON, &usbhid->iofl)) { - spin_unlock_irq(&usbhid->lock); - usbhid_mark_busy(usbhid); - return -EBUSY; - } - spin_unlock_irq(&usbhid->lock); - } - hid_cancel_delayed_stuff(usbhid); hid_cease_io(usbhid); diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h index 1673cac93d7..2d8957c11d2 100644 --- a/drivers/hid/usbhid/usbhid.h +++ b/drivers/hid/usbhid/usbhid.h @@ -55,7 +55,6 @@ struct usb_interface *usbhid_find_interface(int minor); #define HID_STARTED 8 #define HID_REPORTED_IDLE 9 #define HID_KEYS_PRESSED 10 -#define HID_LED_ON 11 /* * USB-specific HID struct, to be pointed to -- cgit v1.2.3 From f0befcd64bc57e6a0b7a96c37c55f79e6b999af7 Mon Sep 17 00:00:00 2001 From: Daniel Kurtz Date: Thu, 17 Nov 2011 19:23:49 +0800 Subject: HID: usbhid: hid-core: submit queued urbs before suspend If any userspace program has opened a keyboard device, the input core de-activates the keyboard's LEDs upon suspend(). It does this by sending individual EV_LED[LED_X]=0 events to the underlying device driver by directly calling the driver's registered event() handler. The usb-hid driver event() handler processes each request by immediately attempting to submit a CTRL URB to turn off the LED. USB URB submission is asynchronous. First the URB is added to the head of the ctrl queue. Then, if the CTRL_RUNNING flag is false, the URB is submitted immediately (and CTRL_RUNNING is set). If the CTRL_RUNNING flag was already true, then the newly queued URB is submitted in the ctrl completion handler when all previously submitted URBs have completed. When all queued URBs have been submitted, the completion handler clears the CTRL_RUNNING flag. In the 2-LED suspend case, at input suspend(), 2 LED event CTRL URBs get queued, with only the first actually submitted. Soon after input suspend() handler finishes, the usb-hid suspend() handler gets called. Since this is NOT a PM_EVENT_AUTO suspend, the handler sets REPORTED_IDLE, then waits for io to complete. Unfortunately, this usually happens while the first LED request is actually still being processed. Thus when the completion handler tries to submit the second LED request it fails, since REPORTED_IDLE is already set! This REPORTED_IDLE check failure causes the completion handler to complete, however without clearing the CTRL_RUNNING flag. This, in turn, means that the suspend() handler's wait_io() condition is never satisfied, and instead it times out after 10 seconds, aborting the original system suspend. This patch changes the behavior to the following: (1) allow completion handler to finish submitting all queued URBs, even if REPORTED_IDLE is set. This guarantees that all URBs queued before the hid-core suspend() call will be submitted before the system is suspended. (2) if REPORTED_IDLE is set and the URB queue is empty, queue, but don't submit, new URB submission requests. These queued requests get submitted when resume() flushes the URB queue. This is similar to the existing behavior, however, any requests that arrive while the queue is not yet empty will still get submitted before suspend. (3) set the RUNNING flag when flushing the URB queue in resume(). This keeps URBs that were queued in (2) from colliding with any new URBs that are being submitted during the resume process. The new URB submission requests upon resume get properly queued behind the ones being flushed instead of the current situation where they collide, causing memory corruption and oopses. Signed-off-by: Daniel Kurtz Acked-by: Oliver Neukum Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 184 ++++++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 79 deletions(-) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 66061349be8..719f6b02fab 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -197,16 +197,24 @@ static int usbhid_restart_out_queue(struct usbhid_device *usbhid) { struct hid_device *hid = usb_get_intfdata(usbhid->intf); int kicked; + int r; if (!hid) return 0; if ((kicked = (usbhid->outhead != usbhid->outtail))) { dbg("Kicking head %d tail %d", usbhid->outhead, usbhid->outtail); + + r = usb_autopm_get_interface_async(usbhid->intf); + if (r < 0) + return r; + /* Asynchronously flush queue. */ + set_bit(HID_OUT_RUNNING, &usbhid->iofl); if (hid_submit_out(hid)) { clear_bit(HID_OUT_RUNNING, &usbhid->iofl); - wake_up(&usbhid->wait); + usb_autopm_put_interface_async(usbhid->intf); } + wake_up(&usbhid->wait); } return kicked; } @@ -215,6 +223,7 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid) { struct hid_device *hid = usb_get_intfdata(usbhid->intf); int kicked; + int r; WARN_ON(hid == NULL); if (!hid) @@ -222,10 +231,17 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid) if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) { dbg("Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail); + + r = usb_autopm_get_interface_async(usbhid->intf); + if (r < 0) + return r; + /* Asynchronously flush queue. */ + set_bit(HID_CTRL_RUNNING, &usbhid->iofl); if (hid_submit_ctrl(hid)) { clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); - wake_up(&usbhid->wait); + usb_autopm_put_interface_async(usbhid->intf); } + wake_up(&usbhid->wait); } return kicked; } @@ -304,30 +320,21 @@ static int hid_submit_out(struct hid_device *hid) report = usbhid->out[usbhid->outtail].report; raw_report = usbhid->out[usbhid->outtail].raw_report; - r = usb_autopm_get_interface_async(usbhid->intf); - if (r < 0) - return -1; + usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + + 1 + (report->id > 0); + usbhid->urbout->dev = hid_to_usb_dev(hid); + memcpy(usbhid->outbuf, raw_report, + usbhid->urbout->transfer_buffer_length); + kfree(raw_report); - /* - * if the device hasn't been woken, we leave the output - * to resume() - */ - if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) { - usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); - usbhid->urbout->dev = hid_to_usb_dev(hid); - memcpy(usbhid->outbuf, raw_report, usbhid->urbout->transfer_buffer_length); - kfree(raw_report); - - dbg_hid("submitting out urb\n"); + dbg_hid("submitting out urb\n"); - if (usb_submit_urb(usbhid->urbout, GFP_ATOMIC)) { - hid_err(hid, "usb_submit_urb(out) failed\n"); - usb_autopm_put_interface_async(usbhid->intf); - return -1; - } - usbhid->last_out = jiffies; + r = usb_submit_urb(usbhid->urbout, GFP_ATOMIC); + if (r < 0) { + hid_err(hid, "usb_submit_urb(out) failed: %d\n", r); + return r; } - + usbhid->last_out = jiffies; return 0; } @@ -343,50 +350,48 @@ static int hid_submit_ctrl(struct hid_device *hid) raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report; dir = usbhid->ctrl[usbhid->ctrltail].dir; - r = usb_autopm_get_interface_async(usbhid->intf); - if (r < 0) - return -1; - if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) { - len = ((report->size - 1) >> 3) + 1 + (report->id > 0); - if (dir == USB_DIR_OUT) { - usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); - usbhid->urbctrl->transfer_buffer_length = len; - memcpy(usbhid->ctrlbuf, raw_report, len); - kfree(raw_report); - } else { - int maxpacket, padlen; - - usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0); - maxpacket = usb_maxpacket(hid_to_usb_dev(hid), usbhid->urbctrl->pipe, 0); - if (maxpacket > 0) { - padlen = DIV_ROUND_UP(len, maxpacket); - padlen *= maxpacket; - if (padlen > usbhid->bufsize) - padlen = usbhid->bufsize; - } else - padlen = 0; - usbhid->urbctrl->transfer_buffer_length = padlen; - } - usbhid->urbctrl->dev = hid_to_usb_dev(hid); - - usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir; - usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : HID_REQ_GET_REPORT; - usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | report->id); - usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum); - usbhid->cr->wLength = cpu_to_le16(len); - - dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n", - usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" : "Get_Report", - usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength); - - if (usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC)) { - usb_autopm_put_interface_async(usbhid->intf); - hid_err(hid, "usb_submit_urb(ctrl) failed\n"); - return -1; - } - usbhid->last_ctrl = jiffies; + len = ((report->size - 1) >> 3) + 1 + (report->id > 0); + if (dir == USB_DIR_OUT) { + usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); + usbhid->urbctrl->transfer_buffer_length = len; + memcpy(usbhid->ctrlbuf, raw_report, len); + kfree(raw_report); + } else { + int maxpacket, padlen; + + usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0); + maxpacket = usb_maxpacket(hid_to_usb_dev(hid), + usbhid->urbctrl->pipe, 0); + if (maxpacket > 0) { + padlen = DIV_ROUND_UP(len, maxpacket); + padlen *= maxpacket; + if (padlen > usbhid->bufsize) + padlen = usbhid->bufsize; + } else + padlen = 0; + usbhid->urbctrl->transfer_buffer_length = padlen; } - + usbhid->urbctrl->dev = hid_to_usb_dev(hid); + + usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir; + usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : + HID_REQ_GET_REPORT; + usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | + report->id); + usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum); + usbhid->cr->wLength = cpu_to_le16(len); + + dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n", + usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" : + "Get_Report", + usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength); + + r = usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC); + if (r < 0) { + hid_err(hid, "usb_submit_urb(ctrl) failed: %d\n", r); + return r; + } + usbhid->last_ctrl = jiffies; return 0; } @@ -423,11 +428,8 @@ static void hid_irq_out(struct urb *urb) else usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1); - if (usbhid->outhead != usbhid->outtail) { - if (hid_submit_out(hid)) { - clear_bit(HID_OUT_RUNNING, &usbhid->iofl); - wake_up(&usbhid->wait); - } + if (usbhid->outhead != usbhid->outtail && !hid_submit_out(hid)) { + /* Successfully submitted next urb in queue */ spin_unlock_irqrestore(&usbhid->lock, flags); return; } @@ -474,13 +476,9 @@ static void hid_ctrl(struct urb *urb) else usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1); - if (usbhid->ctrlhead != usbhid->ctrltail) { - if (hid_submit_ctrl(hid)) { - clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); - wake_up(&usbhid->wait); - } + if (usbhid->ctrlhead != usbhid->ctrltail && !hid_submit_ctrl(hid)) { + /* Successfully submitted next urb in queue */ spin_unlock(&usbhid->lock); - usb_autopm_put_interface_async(usbhid->intf); return; } @@ -515,9 +513,23 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re usbhid->out[usbhid->outhead].report = report; usbhid->outhead = head; + /* Try to awake from autosuspend... */ + if (usb_autopm_get_interface_async(usbhid->intf) < 0) + return; + + /* + * But if still suspended, leave urb enqueued, don't submit. + * Submission will occur if/when resume() drains the queue. + */ + if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) + return; + if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) { - if (hid_submit_out(hid)) + if (hid_submit_out(hid)) { clear_bit(HID_OUT_RUNNING, &usbhid->iofl); + usb_autopm_put_interface_async(usbhid->intf); + } + wake_up(&usbhid->wait); } else { /* * the queue is known to run @@ -549,9 +561,23 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re usbhid->ctrl[usbhid->ctrlhead].dir = dir; usbhid->ctrlhead = head; + /* Try to awake from autosuspend... */ + if (usb_autopm_get_interface_async(usbhid->intf) < 0) + return; + + /* + * If already suspended, leave urb enqueued, but don't submit. + * Submission will occur if/when resume() drains the queue. + */ + if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) + return; + if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) { - if (hid_submit_ctrl(hid)) + if (hid_submit_ctrl(hid)) { clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); + usb_autopm_put_interface_async(usbhid->intf); + } + wake_up(&usbhid->wait); } else { /* * the queue is known to run -- cgit v1.2.3 From 4371ea8202e98c8ef77ca887de3b19affbb3498f Mon Sep 17 00:00:00 2001 From: Daniel Kurtz Date: Thu, 17 Nov 2011 19:23:50 +0800 Subject: 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 Acked-by: Oliver Neukum Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 42 ++++++++++++++++++++++++++++++++++++++ drivers/hid/usbhid/hid-core.c | 47 +++++++++++++++++++++++++++++++++---------- drivers/hid/usbhid/usbhid.h | 2 ++ include/linux/hid.h | 2 ++ 4 files changed, 82 insertions(+), 11 deletions(-) (limited to 'drivers/hid/usbhid') diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index b9b8c75a6f9..c6ee632bfd6 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 } 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) { 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 719f6b02fab..5bf91dbad59 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 } 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) { 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; } + spin_lock_irqsave(&usbhid->lock, flags); hid_set_field(field, offset, value); - if (value) { - spin_lock_irqsave(&usbhid->lock, flags); - usbhid->ledcount++; - spin_unlock_irqrestore(&usbhid->lock, flags); - } else { - spin_lock_irqsave(&usbhid->lock, flags); - usbhid->ledcount--; - spin_unlock_irqrestore(&usbhid->lock, flags); - } - usbhid_submit_report(hid, field->report, USB_DIR_OUT); + spin_unlock_irqrestore(&usbhid->lock, flags); + + /* + * Defer performing requested LED action. + * This is more likely gather all LED changes into a single URB. + */ + schedule_work(&usbhid->led_work); return 0; } @@ -1126,7 +1148,7 @@ static void usbhid_stop(struct hid_device *hid) return; 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); spin_unlock_irq(&usbhid->lock); 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); spin_lock_init(&usbhid->lock); + INIT_WORK(&usbhid->led_work, hid_led); + ret = hid_add_device(hid); if (ret) { if (ret != -ENODEV) @@ -1292,6 +1316,7 @@ static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid) { del_timer_sync(&usbhid->io_retry); cancel_work_sync(&usbhid->reset_work); + cancel_work_sync(&usbhid->led_work); } static void hid_cease_io(struct usbhid_device *usbhid) diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h index 2d8957c11d2..cb8f703efde 100644 --- a/drivers/hid/usbhid/usbhid.h +++ b/drivers/hid/usbhid/usbhid.h @@ -96,6 +96,8 @@ struct usbhid_device { struct work_struct reset_work; /* Task context for resets */ wait_queue_head_t wait; /* For sleeping */ 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) \ diff --git a/include/linux/hid.h b/include/linux/hid.h index 7f344c3da76..999a54c72b2 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -727,6 +727,8 @@ extern void hidinput_disconnect(struct hid_device *); int hid_set_field(struct hid_field *, unsigned, __s32); 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); +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); struct hid_device *hid_allocate_device(void); struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); -- cgit v1.2.3