On Wed, Apr 25, 2012 at 2:57 AM, Oliver Neukum <[email protected]> wrote:
usb_submit_urb()
>>
>> This submit won't happen because HID_OUT_RUNNING is not cleared.
>
> I may be dense, but as far as I can tell a resubmit will happen, exactly if
> HID_OUT_RUNNING is _not_ cleared.
In fact, it should be a double unlink bug, usb_unlink_urb can handle
it correctly
if the lock is held. We also can deal with it easily by checking urb->unlinked,
so how about the below patch?
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index aa1c503..b530463 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -411,10 +411,10 @@ static void hid_irq_out(struct urb *urb)
{
struct hid_device *hid = urb->context;
struct usbhid_device *usbhid = hid->driver_data;
- unsigned long flags;
+ unsigned long status = urb->status;
int unplug = 0;
- switch (urb->status) {
+ switch (status) {
case 0: /* success */
break;
case -ESHUTDOWN: /* unplug */
@@ -428,8 +428,9 @@ static void hid_irq_out(struct urb *urb)
hid_warn(urb->dev, "output irq status %d received\n",
urb->status);
}
-
- spin_lock_irqsave(&usbhid->lock, flags);
+ if (status != -ECONNRESET)
+ spin_lock(&usbhid->unlink_lock);
+ spin_lock(&usbhid->lock);
if (unplug)
usbhid->outtail = usbhid->outhead;
@@ -438,12 +439,16 @@ static void hid_irq_out(struct urb *urb)
if (usbhid->outhead != usbhid->outtail && !hid_submit_out(hid)) {
/* Successfully submitted next urb in queue */
- spin_unlock_irqrestore(&usbhid->lock, flags);
+ spin_unlock(&usbhid->lock);
+ if (status != -ECONNRESET)
+ spin_unlock(&usbhid->unlink_lock);
return;
}
clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
- spin_unlock_irqrestore(&usbhid->lock, flags);
+ spin_unlock(&usbhid->lock);
+ if (status != -ECONNRESET)
+ spin_unlock(&usbhid->unlink_lock);
usb_autopm_put_interface_async(usbhid->intf);
wake_up(&usbhid->wait);
}
@@ -458,6 +463,8 @@ static void hid_ctrl(struct urb *urb)
struct usbhid_device *usbhid = hid->driver_data;
int unplug = 0, status = urb->status;
+ if (status != -ECONNRESET)
+ spin_lock(&usbhid->unlink_lock);
spin_lock(&usbhid->lock);
switch (status) {
@@ -487,11 +494,15 @@ static void hid_ctrl(struct urb *urb)
if (usbhid->ctrlhead != usbhid->ctrltail && !hid_submit_ctrl(hid)) {
/* Successfully submitted next urb in queue */
spin_unlock(&usbhid->lock);
+ if (status != -ECONNRESET)
+ spin_unlock(&usbhid->unlink_lock);
return;
}
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
spin_unlock(&usbhid->lock);
+ if (status != -ECONNRESET)
+ spin_unlock(&usbhid->unlink_lock);
usb_autopm_put_interface_async(usbhid->intf);
wake_up(&usbhid->wait);
}
@@ -546,8 +557,13 @@ static void __usbhid_submit_report(struct
hid_device *hid, struct hid_report *re
* no race because this is called under
* spinlock
*/
- if (time_after(jiffies, usbhid->last_out + HZ * 5))
+
+ if (time_after(jiffies, usbhid->last_out + HZ * 5) &&
+ !usbhid->urbout->unlinked) {
+ spin_unlock(&usbhid->lock);
usb_unlink_urb(usbhid->urbout);
+ spin_lock(&usbhid->lock);
+ }
}
return;
}
@@ -594,8 +610,12 @@ static void __usbhid_submit_report(struct
hid_device *hid, struct hid_report *re
* no race because this is called under
* spinlock
*/
- if (time_after(jiffies, usbhid->last_ctrl + HZ * 5))
+ if (time_after(jiffies, usbhid->last_ctrl + HZ * 5) &&
+ !usbhid->urbctrl->unlinked) {
+ spin_unlock(&usbhid->lock);
usb_unlink_urb(usbhid->urbctrl);
+ spin_lock(&usbhid->lock);
+ }
}
}
@@ -604,9 +624,11 @@ void usbhid_submit_report(struct hid_device *hid,
struct hid_report *report, uns
struct usbhid_device *usbhid = hid->driver_data;
unsigned long flags;
- spin_lock_irqsave(&usbhid->lock, flags);
+ spin_lock_irqsave(&usbhid->unlink_lock, flags);
+ spin_lock(&usbhid->lock);
__usbhid_submit_report(hid, report, dir);
- spin_unlock_irqrestore(&usbhid->lock, flags);
+ spin_unlock(&usbhid->lock);
+ spin_unlock_irqrestore(&usbhid->unlink_lock, flags);
}
EXPORT_SYMBOL_GPL(usbhid_submit_report);
@@ -625,13 +647,15 @@ static void hid_led(struct work_struct *work)
return;
}
- spin_lock_irqsave(&usbhid->lock, flags);
+ spin_lock_irqsave(&usbhid->unlink_lock, flags);
+ spin_lock(&usbhid->lock);
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);
+ spin_unlock(&usbhid->lock);
+ spin_unlock_irqrestore(&usbhid->unlink_lock, flags);
}
static int usb_hidinput_input_event(struct input_dev *dev, unsigned
int type, unsigned int code, int value)
@@ -1299,6 +1323,7 @@ static int usbhid_probe(struct usb_interface
*intf, const struct usb_device_id *
INIT_WORK(&usbhid->reset_work, hid_reset);
setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
spin_lock_init(&usbhid->lock);
+ spin_lock_init(&usbhid->unlink_lock);
INIT_WORK(&usbhid->led_work, hid_led);
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h
index 1883d7b..69af387 100644
--- a/drivers/hid/usbhid/usbhid.h
+++ b/drivers/hid/usbhid/usbhid.h
@@ -90,6 +90,7 @@ struct usbhid_device {
unsigned long last_out;
/* record of last output for timeouts */
spinlock_t lock; /* fifo
spinlock */
+ spinlock_t unlink_lock; /*
unlink spinlock */
unsigned long iofl; /*
I/O flags (CTRL_RUNNING, OUT_RUNNING) */
struct timer_list io_retry; /*
Retry timer */
unsigned long stop_retry; /*
Time to give up, in jiffies */
Thanks,
--
Ming Lei
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html