summaryrefslogtreecommitdiff
path: root/drivers/usb/host/ehci-sched.c
diff options
context:
space:
mode:
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;