[PATCH 14/25] USB: EHCI: use hrtimer for controller death

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxxxxxxxxxx>

---

 drivers/usb/host/ehci-hcd.c   |   20 ++++++++++----------
 drivers/usb/host/ehci-timer.c |   26 ++++++++++++++++++++++++++
 drivers/usb/host/ehci.h       |    2 ++
 3 files changed, 38 insertions(+), 10 deletions(-)

Index: usb-3.4/drivers/usb/host/ehci.h
===================================================================
--- usb-3.4.orig/drivers/usb/host/ehci.h
+++ usb-3.4/drivers/usb/host/ehci.h
@@ -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 controlle
 
 	int			PSS_poll_count;
 	int			ASS_poll_count;
+	int			died_poll_count;
 
 	/* glue to PCI and HCD framework */
 	struct ehci_caps __iomem *caps;
Index: usb-3.4/drivers/usb/host/ehci-hcd.c
===================================================================
--- usb-3.4.orig/drivers/usb/host/ehci-hcd.c
+++ usb-3.4/drivers/usb/host/ehci-hcd.c
@@ -888,20 +888,20 @@ static irqreturn_t ehci_irq (struct usb_
 	/* 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: usb-3.4/drivers/usb/host/ehci-timer.c
===================================================================
--- usb-3.4.orig/drivers/usb/host/ehci-timer.c
+++ usb-3.4/drivers/usb/host/ehci-timer.c
@@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struc
 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
 }
 
 
+/* 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(str
 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 */


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux