diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2009-08-19 20:22:44 +0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-09-23 17:46:37 +0400 |
commit | a448c9d8c58ff7d3f8cc2a8f835065460099b22d (patch) | |
tree | 418610891b8dc271bbd798ff5c47d921be5f806d /drivers/usb/host/ehci-sched.c | |
parent | 3a44494e233c0fdd818d485cfea8998500543589 (diff) | |
download | linux-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.c | 36 |
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; |