USB: EHCI: use hrtimer for controller death
authorAlan Stern <stern@rowland.harvard.edu>
Wed, 11 Jul 2012 15:22:31 +0000 (11:22 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 16 Jul 2012 23:54:25 +0000 (16:54 -0700)
This patch (as1578) adds an hrtimer event to handle the death of an
EHCI controller.  When a controller dies, it doesn't necessarily stop
running right away.  The new event polls at 1-ms intervals to see when
all activity has safely stopped.  This replaces a busy-wait polling
loop in the current code.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-timer.c
drivers/usb/host/ehci.h

index edcfd2c4295ec9ce2a1748d9cb44664d50a34e37..1676c66b85303ed65d5d507492ec39fb92d9e058 100644 (file)
@@ -888,20 +888,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
        /* 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);
 dead:
-               ehci->enabled_hrtimer_events = 0;
-               hrtimer_try_to_cancel(&ehci->hrtimer);
-               ehci_reset(ehci);
-               ehci_writel(ehci, 0, &ehci->regs->configured_flag);
                usb_hc_died(hcd);
-               /* generic layer kills/unlinks all urbs, then
-                * uses ehci_stop to clean up the rest
-                */
-               bh = 1;
+
+               /* Don't let the controller do anything more */
+               ehci->rh_state = EHCI_RH_STOPPING;
+               ehci->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE);
+               ehci_writel(ehci, ehci->command, &ehci->regs->command);
+               ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+               ehci_handle_controller_death(ehci);
+
+               /* Handle completions when the controller stops */
+               bh = 0;
        }
 
        if (bh)
index bd8b591771b08de2921e6b7da81a410fa4cb0a09..0c5326a8883c49330386267aaff3c66faa5777af 100644 (file)
@@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit)
 static unsigned event_delays_ns[] = {
        1 * NSEC_PER_MSEC,      /* EHCI_HRTIMER_POLL_ASS */
        1 * NSEC_PER_MSEC,      /* EHCI_HRTIMER_POLL_PSS */
+       1 * NSEC_PER_MSEC,      /* EHCI_HRTIMER_POLL_DEAD */
        1125 * NSEC_PER_USEC,   /* EHCI_HRTIMER_UNLINK_INTR */
        10 * NSEC_PER_MSEC,     /* EHCI_HRTIMER_DISABLE_PERIODIC */
        15 * NSEC_PER_MSEC,     /* EHCI_HRTIMER_DISABLE_ASYNC */
@@ -193,6 +194,30 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci)
 }
 
 
+/* Poll the STS_HALT status bit; see when a dead controller stops */
+static void ehci_handle_controller_death(struct ehci_hcd *ehci)
+{
+       if (!(ehci_readl(ehci, &ehci->regs->status) & STS_HALT)) {
+
+               /* Give up after a few milliseconds */
+               if (ehci->died_poll_count++ < 5) {
+                       /* Try again later */
+                       ehci_enable_event(ehci, EHCI_HRTIMER_POLL_DEAD, true);
+                       return;
+               }
+               ehci_warn(ehci, "Waited too long for the controller to stop, giving up\n");
+       }
+
+       /* Clean up the mess */
+       ehci->rh_state = EHCI_RH_HALTED;
+       ehci_writel(ehci, 0, &ehci->regs->configured_flag);
+       ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+       ehci_work(ehci);
+
+       /* Not in process context, so don't try to reset the controller */
+}
+
+
 /* Handle unlinked interrupt QHs once they are gone from the hardware */
 static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci)
 {
@@ -233,6 +258,7 @@ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci)
 static void (*event_handlers[])(struct ehci_hcd *) = {
        ehci_poll_ASS,                  /* EHCI_HRTIMER_POLL_ASS */
        ehci_poll_PSS,                  /* EHCI_HRTIMER_POLL_PSS */
+       ehci_handle_controller_death,   /* EHCI_HRTIMER_POLL_DEAD */
        ehci_handle_intr_unlinks,       /* EHCI_HRTIMER_UNLINK_INTR */
        ehci_disable_PSE,               /* EHCI_HRTIMER_DISABLE_PERIODIC */
        ehci_disable_ASE,               /* EHCI_HRTIMER_DISABLE_ASYNC */
index f36f1f85d7fda16288381cbc144c954fe782ce71..6874d89b0b64bd5158613dfa5ba798062d55e6fe 100644 (file)
@@ -81,6 +81,7 @@ enum ehci_rh_state {
 enum ehci_hrtimer_event {
        EHCI_HRTIMER_POLL_ASS,          /* Poll for async schedule off */
        EHCI_HRTIMER_POLL_PSS,          /* Poll for periodic schedule off */
+       EHCI_HRTIMER_POLL_DEAD,         /* Wait for dead controller to stop */
        EHCI_HRTIMER_UNLINK_INTR,       /* Wait for interrupt QH unlink */
        EHCI_HRTIMER_DISABLE_PERIODIC,  /* Wait to disable periodic sched */
        EHCI_HRTIMER_DISABLE_ASYNC,     /* Wait to disable async sched */
@@ -97,6 +98,7 @@ struct ehci_hcd {                     /* one per controller */
 
        int                     PSS_poll_count;
        int                     ASS_poll_count;
+       int                     died_poll_count;
 
        /* glue to PCI and HCD framework */
        struct ehci_caps __iomem *caps;