Commit 01a7c984 authored by Alan Stern's avatar Alan Stern Committed by Jiri Kosina

HID: usbhid: fix autosuspend calls

This patch (as1593) fixes some logic errors in the usbhid driver
relating to runtime PM.  The driver does not balance its calls to
usb_autopm_get_interface_async() and usb_autopm_put_interface_async().

For example, when the control queue is restarted the driver does a
_get.  But the resume won't happen immediately, so the driver leaves
the queue stopped.  When the resume does occur, the queue is restarted
and a second _get occurs, with no balancing _put.

The patch fixes the problem by rearranging the logic for restarting
the queues.  All the _get/_put calls and bitflag settings in
__usbhid_submit_report() are moved into the queue-restart routines.  A
balancing _put call is added for the case where the queue is still
suspended.  A call to irq_out_pump_restart(), which doesn't take all
the right actions for restarting the irq-OUT queue, is replaced by a
call to usbhid_restart_out_queue(), which does.  Similarly for
ctrl_pump_restart().

Finally, new code is added to prevent an autosuspend from happening
every time an URB is cancelled, and the comments explaining what
happens when an URB needs to be cancelled are expanded and clarified.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
CC: Oliver Neukum <oliver@neukum.org>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 668160e5
...@@ -213,9 +213,20 @@ static int usbhid_restart_out_queue(struct usbhid_device *usbhid) ...@@ -213,9 +213,20 @@ static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
if ((kicked = (usbhid->outhead != usbhid->outtail))) { if ((kicked = (usbhid->outhead != usbhid->outtail))) {
hid_dbg(hid, "Kicking head %d tail %d", usbhid->outhead, usbhid->outtail); hid_dbg(hid, "Kicking head %d tail %d", usbhid->outhead, usbhid->outtail);
/* Try to wake up from autosuspend... */
r = usb_autopm_get_interface_async(usbhid->intf); r = usb_autopm_get_interface_async(usbhid->intf);
if (r < 0) if (r < 0)
return r; return r;
/*
* If still suspended, don't submit. Submission will
* occur if/when resume drains the queue.
*/
if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
usb_autopm_put_interface_no_suspend(usbhid->intf);
return r;
}
/* Asynchronously flush queue. */ /* Asynchronously flush queue. */
set_bit(HID_OUT_RUNNING, &usbhid->iofl); set_bit(HID_OUT_RUNNING, &usbhid->iofl);
if (hid_submit_out(hid)) { if (hid_submit_out(hid)) {
...@@ -240,9 +251,20 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid) ...@@ -240,9 +251,20 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) { if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
hid_dbg(hid, "Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail); hid_dbg(hid, "Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail);
/* Try to wake up from autosuspend... */
r = usb_autopm_get_interface_async(usbhid->intf); r = usb_autopm_get_interface_async(usbhid->intf);
if (r < 0) if (r < 0)
return r; return r;
/*
* If still suspended, don't submit. Submission will
* occur if/when resume drains the queue.
*/
if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
usb_autopm_put_interface_no_suspend(usbhid->intf);
return r;
}
/* Asynchronously flush queue. */ /* Asynchronously flush queue. */
set_bit(HID_CTRL_RUNNING, &usbhid->iofl); set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
if (hid_submit_ctrl(hid)) { if (hid_submit_ctrl(hid)) {
...@@ -546,49 +568,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re ...@@ -546,49 +568,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
usbhid->out[usbhid->outhead].report = report; usbhid->out[usbhid->outhead].report = report;
usbhid->outhead = head; usbhid->outhead = head;
/* Try to awake from autosuspend... */ /* If the queue isn't running, restart it */
if (usb_autopm_get_interface_async(usbhid->intf) < 0) if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
return; usbhid_restart_out_queue(usbhid);
/* /* Otherwise see if an earlier request has timed out */
* But if still suspended, leave urb enqueued, don't submit. } else if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
* Submission will occur if/when resume() drains the queue.
*/ /* Prevent autosuspend following the unlink */
if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) usb_autopm_get_interface_no_resume(usbhid->intf);
return;
if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
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 * Prevent resubmission in case the URB completes
* but an earlier request may be stuck * before we can unlink it. We don't want to cancel
* we may need to time out * the wrong transfer!
* no race because the URB is blocked under
* spinlock
*/ */
if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
usb_block_urb(usbhid->urbout); usb_block_urb(usbhid->urbout);
/* drop lock to not deadlock if the callback is called */
/* Drop lock to avoid deadlock if the callback runs */
spin_unlock(&usbhid->lock); spin_unlock(&usbhid->lock);
usb_unlink_urb(usbhid->urbout); usb_unlink_urb(usbhid->urbout);
spin_lock(&usbhid->lock); spin_lock(&usbhid->lock);
usb_unblock_urb(usbhid->urbout); 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);
/* Unlink might have stopped the queue */
if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl))
usbhid_restart_out_queue(usbhid);
} /* Now we can allow autosuspend again */
usb_autopm_put_interface_async(usbhid->intf);
} }
return; return;
} }
...@@ -610,47 +619,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re ...@@ -610,47 +619,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
usbhid->ctrl[usbhid->ctrlhead].dir = dir; usbhid->ctrl[usbhid->ctrlhead].dir = dir;
usbhid->ctrlhead = head; usbhid->ctrlhead = head;
/* Try to awake from autosuspend... */ /* If the queue isn't running, restart it */
if (usb_autopm_get_interface_async(usbhid->intf) < 0) if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
return; usbhid_restart_ctrl_queue(usbhid);
/* /* Otherwise see if an earlier request has timed out */
* If already suspended, leave urb enqueued, but don't submit. } else if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
* Submission will occur if/when resume() drains the queue.
*/ /* Prevent autosuspend following the unlink */
if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) usb_autopm_get_interface_no_resume(usbhid->intf);
return;
if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
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 * Prevent resubmission in case the URB completes
* but an earlier request may be stuck * before we can unlink it. We don't want to cancel
* we may need to time out * the wrong transfer!
* no race because the URB is blocked under
* spinlock
*/ */
if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
usb_block_urb(usbhid->urbctrl); usb_block_urb(usbhid->urbctrl);
/* drop lock to not deadlock if the callback is called */
/* Drop lock to avoid deadlock if the callback runs */
spin_unlock(&usbhid->lock); spin_unlock(&usbhid->lock);
usb_unlink_urb(usbhid->urbctrl); usb_unlink_urb(usbhid->urbctrl);
spin_lock(&usbhid->lock); spin_lock(&usbhid->lock);
usb_unblock_urb(usbhid->urbctrl); usb_unblock_urb(usbhid->urbctrl);
/*
* if the unlinking has already completed /* Unlink might have stopped the queue */
* the pump will have been stopped
* it must be restarted now
*/
if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
if (!ctrl_pump_restart(hid)) usbhid_restart_ctrl_queue(usbhid);
set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
} /* Now we can allow autosuspend again */
usb_autopm_put_interface_async(usbhid->intf);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment