usbhid: prevent deadlock during timeout

On some HCDs usb_unlink_urb() can directly call the
completion handler. That limits the spinlocks that can
be taken in the handler to locks not held while calling
usb_unlink_urb()
To prevent a race with resubmission, this patch exposes
usbcore's infrastructure for blocking submission, uses it
and so drops the lock without causing a race in usbhid.

Signed-off-by: Oliver Neukum <[email protected]>
Acked-by: Jiri Kosina <[email protected]>
Cc: stable <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
This commit is contained in:
Oliver Neukum 2012-04-30 09:13:46 +02:00 • committed by Greg Kroah-Hartman
commit 8815bb09af
3 changed files with 79 additions and 6 deletions

View file

@ -399,6 +399,16 @@ static int hid_submit_ctrl(struct hid_device *hid)
* Output interrupt completion handler.
*/
static int irq_out_pump_restart(struct hid_device *hid)
{
struct usbhid_device *usbhid = hid->driver_data;
if (usbhid->outhead != usbhid->outtail)
return hid_submit_out(hid);
else
return -1;
}
static void hid_irq_out(struct urb *urb)
{
struct hid_device *hid = urb->context;
@ -428,7 +438,7 @@ static void hid_irq_out(struct urb *urb)
else
usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
if (usbhid->outhead != usbhid->outtail && !hid_submit_out(hid)) {
if (!irq_out_pump_restart(hid)) {
/* Successfully submitted next urb in queue */
spin_unlock_irqrestore(&usbhid->lock, flags);
return;
@ -443,6 +453,15 @@ static void hid_irq_out(struct urb *urb)
/*
* Control pipe completion handler.
*/
static int ctrl_pump_restart(struct hid_device *hid)
{
struct usbhid_device *usbhid = hid->driver_data;
if (usbhid->ctrlhead != usbhid->ctrltail)
return hid_submit_ctrl(hid);
else
return -1;
}
static void hid_ctrl(struct urb *urb)
{
@ -476,7 +495,7 @@ static void hid_ctrl(struct urb *urb)
else
usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
if (usbhid->ctrlhead != usbhid->ctrltail && !hid_submit_ctrl(hid)) {
if (!ctrl_pump_restart(hid)) {
/* Successfully submitted next urb in queue */
spin_unlock(&usbhid->lock);
return;
@ -535,11 +554,27 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
* the queue is known to run
* but an earlier request may be stuck
* we may need to time out
* no race because this is called under
* no race because the URB is blocked under
* spinlock
*/
if (time_after(jiffies, usbhid->last_out + HZ * 5))
if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
usb_block_urb(usbhid->urbout);
/* drop lock to not deadlock if the callback is called */
spin_unlock(&usbhid->lock);
usb_unlink_urb(usbhid->urbout);
spin_lock(&usbhid->lock);
usb_unblock_urb(usbhid->urbout);
/*
* if the unlinking has already completed
* the pump will have been stopped
* it must be restarted now
*/
if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl))
if (!irq_out_pump_restart(hid))
set_bit(HID_OUT_RUNNING, &usbhid->iofl);
}
}
return;
}
@ -583,11 +618,25 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
* the queue is known to run
* but an earlier request may be stuck
* we may need to time out
* no race because this is called under
* no race because the URB is blocked under
* spinlock
*/
if (time_after(jiffies, usbhid->last_ctrl + HZ * 5))
if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
usb_block_urb(usbhid->urbctrl);
/* drop lock to not deadlock if the callback is called */
spin_unlock(&usbhid->lock);
usb_unlink_urb(usbhid->urbctrl);
spin_lock(&usbhid->lock);
usb_unblock_urb(usbhid->urbctrl);
/*
* if the unlinking has already completed
* the pump will have been stopped
* it must be restarted now
*/
if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
if (!ctrl_pump_restart(hid))
set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
}
}
}