summaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/ehci-q.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/ehci-q.c')
-rw-r--r--drivers/usb/host/ehci-q.c104
1 files changed, 80 insertions, 24 deletions
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index aad0777240d3..eca3710d8fc4 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -394,6 +394,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
goto retry_xacterr;
}
stopped = 1;
+ qh->unlink_reason |= QH_UNLINK_HALTED;
/* magic dummy for some short reads; qh won't advance.
* that silicon quirk can kick in with this dummy too.
@@ -408,6 +409,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
&& !(qtd->hw_alt_next
& EHCI_LIST_END(ehci))) {
stopped = 1;
+ qh->unlink_reason |= QH_UNLINK_SHORT_READ;
}
/* stop scanning when we reach qtds the hc is using */
@@ -420,8 +422,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
stopped = 1;
/* cancel everything if we halt, suspend, etc */
- if (ehci->rh_state < EHCI_RH_RUNNING)
+ if (ehci->rh_state < EHCI_RH_RUNNING) {
last_status = -ESHUTDOWN;
+ qh->unlink_reason |= QH_UNLINK_SHUTDOWN;
+ }
/* this qtd is active; skip it unless a previous qtd
* for its urb faulted, or its urb was canceled.
@@ -538,10 +542,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
* except maybe high bandwidth ...
*/
if (stopped != 0 || hw->hw_qtd_next == EHCI_LIST_END(ehci))
- qh->exception = 1;
+ qh->unlink_reason |= QH_UNLINK_DUMMY_OVERLAY;
/* Let the caller know if the QH needs to be unlinked. */
- return qh->exception;
+ return qh->unlink_reason;
}
/*-------------------------------------------------------------------------*/
@@ -1003,7 +1007,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
qh->qh_state = QH_STATE_LINKED;
qh->xacterrs = 0;
- qh->exception = 0;
+ qh->unlink_reason = 0;
/* qtd completions reported later by interrupt */
enable_async(ehci);
@@ -1279,17 +1283,13 @@ static void single_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh)
static void start_iaa_cycle(struct ehci_hcd *ehci)
{
- /* Do nothing if an IAA cycle is already running */
- if (ehci->iaa_in_progress)
- return;
- ehci->iaa_in_progress = true;
-
/* If the controller isn't running, we don't have to wait for it */
if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) {
end_unlink_async(ehci);
- /* Otherwise start a new IAA cycle */
- } else if (likely(ehci->rh_state == EHCI_RH_RUNNING)) {
+ /* Otherwise start a new IAA cycle if one isn't already running */
+ } else if (ehci->rh_state == EHCI_RH_RUNNING &&
+ !ehci->iaa_in_progress) {
/* Make sure the unlinks are all visible to the hardware */
wmb();
@@ -1297,17 +1297,13 @@ static void start_iaa_cycle(struct ehci_hcd *ehci)
ehci_writel(ehci, ehci->command | CMD_IAAD,
&ehci->regs->command);
ehci_readl(ehci, &ehci->regs->command);
+ ehci->iaa_in_progress = true;
ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true);
}
}
-/* the async qh for the qtds being unlinked are now gone from the HC */
-
-static void end_unlink_async(struct ehci_hcd *ehci)
+static void end_iaa_cycle(struct ehci_hcd *ehci)
{
- struct ehci_qh *qh;
- bool early_exit;
-
if (ehci->has_synopsys_hc_bug)
ehci_writel(ehci, (u32) ehci->async->qh_dma,
&ehci->regs->async_next);
@@ -1315,6 +1311,16 @@ static void end_unlink_async(struct ehci_hcd *ehci)
/* The current IAA cycle has ended */
ehci->iaa_in_progress = false;
+ end_unlink_async(ehci);
+}
+
+/* See if the async qh for the qtds being unlinked are now gone from the HC */
+
+static void end_unlink_async(struct ehci_hcd *ehci)
+{
+ struct ehci_qh *qh;
+ bool early_exit;
+
if (list_empty(&ehci->async_unlink))
return;
qh = list_first_entry(&ehci->async_unlink, struct ehci_qh,
@@ -1335,14 +1341,60 @@ static void end_unlink_async(struct ehci_hcd *ehci)
* after the IAA interrupt occurs. In self-defense, always go
* through two IAA cycles for each QH.
*/
- else if (qh->qh_state == QH_STATE_UNLINK_WAIT) {
+ else if (qh->qh_state == QH_STATE_UNLINK) {
+ /*
+ * Second IAA cycle has finished. Process only the first
+ * waiting QH (NVIDIA (?) bug).
+ */
+ list_move_tail(&qh->unlink_node, &ehci->async_idle);
+ }
+
+ /*
+ * AMD/ATI (?) bug: The HC can continue to use an active QH long
+ * after the IAA interrupt occurs. To prevent problems, QHs that
+ * may still be active will wait until 2 ms have passed with no
+ * change to the hw_current and hw_token fields (this delay occurs
+ * between the two IAA cycles).
+ *
+ * The EHCI spec (4.8.2) says that active QHs must not be removed
+ * from the async schedule and recommends waiting until the QH
+ * goes inactive. This is ridiculous because the QH will _never_
+ * become inactive if the endpoint NAKs indefinitely.
+ */
+
+ /* Some reasons for unlinking guarantee the QH can't be active */
+ else if (qh->unlink_reason & (QH_UNLINK_HALTED |
+ QH_UNLINK_SHORT_READ | QH_UNLINK_DUMMY_OVERLAY))
+ goto DelayDone;
+
+ /* The QH can't be active if the queue was and still is empty... */
+ else if ((qh->unlink_reason & QH_UNLINK_QUEUE_EMPTY) &&
+ list_empty(&qh->qtd_list))
+ goto DelayDone;
+
+ /* ... or if the QH has halted */
+ else if (qh->hw->hw_token & cpu_to_hc32(ehci, QTD_STS_HALT))
+ goto DelayDone;
+
+ /* Otherwise we have to wait until the QH stops changing */
+ else {
+ __hc32 qh_current, qh_token;
+
+ qh_current = qh->hw->hw_current;
+ qh_token = qh->hw->hw_token;
+ if (qh_current != ehci->old_current ||
+ qh_token != ehci->old_token) {
+ ehci->old_current = qh_current;
+ ehci->old_token = qh_token;
+ ehci_enable_event(ehci,
+ EHCI_HRTIMER_ACTIVE_UNLINK, true);
+ return;
+ }
+ DelayDone:
qh->qh_state = QH_STATE_UNLINK;
early_exit = true;
}
-
- /* Otherwise process only the first waiting QH (NVIDIA bug?) */
- else
- list_move_tail(&qh->unlink_node, &ehci->async_idle);
+ ehci->old_current = ~0; /* Prepare for next QH */
/* Start a new IAA cycle if any QHs are waiting for it */
if (!list_empty(&ehci->async_unlink))
@@ -1395,6 +1447,7 @@ static void unlink_empty_async(struct ehci_hcd *ehci)
/* If nothing else is being unlinked, unlink the last empty QH */
if (list_empty(&ehci->async_unlink) && qh_to_unlink) {
+ qh_to_unlink->unlink_reason |= QH_UNLINK_QUEUE_EMPTY;
start_unlink_async(ehci, qh_to_unlink);
--count;
}
@@ -1406,8 +1459,10 @@ static void unlink_empty_async(struct ehci_hcd *ehci)
}
}
+#ifdef CONFIG_PM
+
/* The root hub is suspended; unlink all the async QHs */
-static void __maybe_unused unlink_empty_async_suspended(struct ehci_hcd *ehci)
+static void unlink_empty_async_suspended(struct ehci_hcd *ehci)
{
struct ehci_qh *qh;
@@ -1416,9 +1471,10 @@ static void __maybe_unused unlink_empty_async_suspended(struct ehci_hcd *ehci)
WARN_ON(!list_empty(&qh->qtd_list));
single_unlink_async(ehci, qh);
}
- start_iaa_cycle(ehci);
}
+#endif
+
/* makes sure the async qh will become idle */
/* caller must own ehci->lock */
OpenPOWER on IntegriCloud