USB: EHCI: add new root-hub state: STOPPING
This patch (as1571) adds a new state for ehci-hcd's root hubs:
EHCI_RH_STOPPING. This value is used at times when the root hub is
being stopped and we don't know whether or not the hardware has
finished all its DMA yet.
Although the purpose may not be apparent, this distinction will come
in useful later on. Future patches will avoid actions that depend on
the root hub being operational (like turning on the async or periodic
schedules) when they see the state is EHCI_RH_STOPPING.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c
index 7612095..f0c00de 100644
--- a/drivers/usb/host/ehci-dbg.c
+++ b/drivers/usb/host/ehci-dbg.c
@@ -706,6 +706,8 @@
return "suspended";
case EHCI_RH_RUNNING:
return "running";
+ case EHCI_RH_STOPPING:
+ return "stopping";
}
return "?";
}
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 8b75e42..bc94822 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -357,10 +357,8 @@
{
u32 temp;
-#ifdef DEBUG
if (ehci->rh_state != EHCI_RH_RUNNING)
- BUG ();
-#endif
+ return;
/* wait for any schedule enables/disables to take effect */
temp = (ehci->command << 10) & (STS_ASS | STS_PSS);
@@ -494,6 +492,7 @@
del_timer_sync(&ehci->iaa_watchdog);
spin_lock_irq(&ehci->lock);
+ ehci->rh_state = EHCI_RH_STOPPING;
ehci_silence_controller(ehci);
spin_unlock_irq(&ehci->lock);
}
@@ -562,8 +561,7 @@
del_timer_sync(&ehci->iaa_watchdog);
spin_lock_irq(&ehci->lock);
- if (ehci->rh_state == EHCI_RH_RUNNING)
- ehci_quiesce (ehci);
+ ehci_quiesce(ehci);
ehci_silence_controller(ehci);
ehci_reset (ehci);
@@ -951,6 +949,7 @@
/* PCI errors [4.15.2.4] */
if (unlikely ((status & STS_FATAL) != 0)) {
ehci_err(ehci, "fatal error\n");
+ ehci->rh_state = EHCI_RH_STOPPING;
dbg_cmd(ehci, "fatal", cmd);
dbg_status(ehci, "fatal", status);
ehci_halt(ehci);
@@ -1026,7 +1025,7 @@
static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
{
/* failfast */
- if (ehci->rh_state != EHCI_RH_RUNNING && ehci->async_unlink)
+ if (ehci->rh_state < EHCI_RH_RUNNING && ehci->async_unlink)
end_unlink_async(ehci);
/* If the QH isn't linked then there's nothing we can do
@@ -1148,7 +1147,7 @@
goto idle_timeout;
}
- if (ehci->rh_state != EHCI_RH_RUNNING)
+ if (ehci->rh_state < EHCI_RH_RUNNING)
qh->qh_state = QH_STATE_IDLE;
switch (qh->qh_state) {
case QH_STATE_LINKED:
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 77d3324..fb1b99e 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -227,8 +227,7 @@
}
/* stop schedules, clean any completed work */
- if (ehci->rh_state == EHCI_RH_RUNNING)
- ehci_quiesce (ehci);
+ ehci_quiesce(ehci);
ehci_work(ehci);
/* Unlike other USB host controller types, EHCI doesn't have
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 5193612..285d5a0 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -433,7 +433,7 @@
/* stop scanning when we reach qtds the hc is using */
} else if (likely (!stopped
- && ehci->rh_state == EHCI_RH_RUNNING)) {
+ && ehci->rh_state >= EHCI_RH_RUNNING)) {
break;
/* scan the whole queue for unlinks whenever it stops */
@@ -441,7 +441,7 @@
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;
/* this qtd is active; skip it unless a previous qtd
@@ -1241,7 +1241,7 @@
wmb ();
/* If the controller isn't running, we don't have to wait for it */
- if (unlikely(ehci->rh_state != EHCI_RH_RUNNING)) {
+ if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) {
/* if (unlikely (qh->unlink_next != 0))
* this will recurse, probably not much
*/
@@ -1263,7 +1263,7 @@
enum ehci_timer_action action = TIMER_IO_WATCHDOG;
timer_action_done (ehci, TIMER_ASYNC_SHRINK);
- stopped = (ehci->rh_state != EHCI_RH_RUNNING);
+ stopped = (ehci->rh_state < EHCI_RH_RUNNING);
ehci->qh_scan_next = ehci->async->qh_next.qh;
while (ehci->qh_scan_next) {
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index 027df3d..3429b8a3 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -2299,7 +2299,7 @@
* Touches as few pages as possible: cache-friendly.
*/
now_uframe = ehci->next_uframe;
- if (ehci->rh_state == EHCI_RH_RUNNING) {
+ if (ehci->rh_state >= EHCI_RH_RUNNING) {
clock = ehci_read_frame_index(ehci);
clock_frame = (clock >> 3) & (ehci->periodic_size - 1);
} else {
@@ -2334,7 +2334,7 @@
union ehci_shadow temp;
int live;
- live = (ehci->rh_state == EHCI_RH_RUNNING);
+ live = (ehci->rh_state >= EHCI_RH_RUNNING);
switch (hc32_to_cpu(ehci, type)) {
case Q_TYPE_QH:
/* handle any completions */
@@ -2459,7 +2459,7 @@
* We can't advance our scan without collecting the ISO
* transfers that are still pending in this frame.
*/
- if (incomplete && ehci->rh_state == EHCI_RH_RUNNING) {
+ if (incomplete && ehci->rh_state >= EHCI_RH_RUNNING) {
ehci->next_uframe = now_uframe;
break;
}
@@ -2475,7 +2475,7 @@
if (now_uframe == clock) {
unsigned now;
- if (ehci->rh_state != EHCI_RH_RUNNING
+ if (ehci->rh_state < EHCI_RH_RUNNING
|| ehci->periodic_sched == 0)
break;
ehci->next_uframe = now_uframe;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 475f23e..9e8e82e 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -62,10 +62,15 @@
#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */
+/*
+ * ehci_rh_state values of EHCI_RH_RUNNING or above mean that the
+ * controller may be doing DMA. Lower values mean there's no DMA.
+ */
enum ehci_rh_state {
EHCI_RH_HALTED,
EHCI_RH_SUSPENDED,
- EHCI_RH_RUNNING
+ EHCI_RH_RUNNING,
+ EHCI_RH_STOPPING
};
struct ehci_hcd { /* one per controller */