summaryrefslogtreecommitdiff
path: root/drivers/usb/host/ehci-sched.c
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2009-08-19 20:22:44 +0400
committerGreg Kroah-Hartman <gregkh@suse.de>2009-09-23 17:46:37 +0400
commita448c9d8c58ff7d3f8cc2a8f835065460099b22d (patch)
tree418610891b8dc271bbd798ff5c47d921be5f806d /drivers/usb/host/ehci-sched.c
parent3a44494e233c0fdd818d485cfea8998500543589 (diff)
downloadlinux-a448c9d8c58ff7d3f8cc2a8f835065460099b22d.tar.xz
USB: EHCI: change deschedule logic for interrupt QHs
This patch (as1281) changes the way ehci-hcd deschedules interrupt QHs, copying the approach used for async QHs. The caller is no longer responsible for rescheduling the QH if its queue is non-empty; instead the reschedule is done directly by intr_deschedule(), after calling qh_completions(). This is exactly the same as how end_unlink_async() works. ehci_urb_dequeue() and intr_deschedule() now correctly handle the case where they are called while another interrupt URB for the same QH is being given back. This was a surprisingly large blind spot. And scan_periodic() now respects the new needs_rescan flag. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> CC: David Brownell <david-b@pacbell.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/ehci-sched.c')
-rw-r--r--drivers/usb/host/ehci-sched.c36
1 files changed, 33 insertions, 3 deletions
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index 327437af2122..3ea05936851f 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -615,8 +615,19 @@ static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
{
- unsigned wait;
- struct ehci_qh_hw *hw = qh->hw;
+ unsigned wait;
+ struct ehci_qh_hw *hw = qh->hw;
+ int rc;
+
+ /* If the QH isn't linked then there's nothing we can do
+ * unless we were called during a giveback, in which case
+ * qh_completions() has to deal with it.
+ */
+ if (qh->qh_state != QH_STATE_LINKED) {
+ if (qh->qh_state == QH_STATE_COMPLETING)
+ qh->needs_rescan = 1;
+ return;
+ }
qh_unlink_periodic (ehci, qh);
@@ -636,6 +647,24 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
qh->qh_state = QH_STATE_IDLE;
hw->hw_next = EHCI_LIST_END(ehci);
wmb ();
+
+ qh_completions(ehci, qh);
+
+ /* reschedule QH iff another request is queued */
+ if (!list_empty(&qh->qtd_list) &&
+ HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) {
+ rc = qh_schedule(ehci, qh);
+
+ /* An error here likely indicates handshake failure
+ * or no space left in the schedule. Neither fault
+ * should happen often ...
+ *
+ * FIXME kill the now-dysfunctional queued urbs
+ */
+ if (rc != 0)
+ ehci_err(ehci, "can't reschedule qh %p, err %d\n",
+ qh, rc);
+ }
}
/*-------------------------------------------------------------------------*/
@@ -2213,7 +2242,8 @@ restart:
type = Q_NEXT_TYPE(ehci, q.qh->hw->hw_next);
q = q.qh->qh_next;
modified = qh_completions (ehci, temp.qh);
- if (unlikely (list_empty (&temp.qh->qtd_list)))
+ if (unlikely(list_empty(&temp.qh->qtd_list) ||
+ temp.qh->needs_rescan))
intr_deschedule (ehci, temp.qh);
qh_put (temp.qh);
break;