Commit bf6387bc authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: EHCI: use hrtimer for controller death

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent df202255
...@@ -888,20 +888,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) ...@@ -888,20 +888,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
/* PCI errors [4.15.2.4] */ /* PCI errors [4.15.2.4] */
if (unlikely ((status & STS_FATAL) != 0)) { if (unlikely ((status & STS_FATAL) != 0)) {
ehci_err(ehci, "fatal error\n"); ehci_err(ehci, "fatal error\n");
ehci->rh_state = EHCI_RH_STOPPING;
dbg_cmd(ehci, "fatal", cmd); dbg_cmd(ehci, "fatal", cmd);
dbg_status(ehci, "fatal", status); dbg_status(ehci, "fatal", status);
ehci_halt(ehci);
dead: 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); usb_hc_died(hcd);
/* generic layer kills/unlinks all urbs, then
* uses ehci_stop to clean up the rest /* Don't let the controller do anything more */
*/ ehci->rh_state = EHCI_RH_STOPPING;
bh = 1; 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) if (bh)
......
...@@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) ...@@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit)
static unsigned event_delays_ns[] = { static unsigned event_delays_ns[] = {
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */
1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */
10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */
15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */
...@@ -193,6 +194,30 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) ...@@ -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 */ /* Handle unlinked interrupt QHs once they are gone from the hardware */
static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci)
{ {
...@@ -233,6 +258,7 @@ 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 *) = { static void (*event_handlers[])(struct ehci_hcd *) = {
ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */
ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */
ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */
ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */
ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */
ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */
......
...@@ -81,6 +81,7 @@ enum ehci_rh_state { ...@@ -81,6 +81,7 @@ enum ehci_rh_state {
enum ehci_hrtimer_event { enum ehci_hrtimer_event {
EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */
EHCI_HRTIMER_POLL_PSS, /* Poll for periodic 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_UNLINK_INTR, /* Wait for interrupt QH unlink */
EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */
EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */
...@@ -97,6 +98,7 @@ struct ehci_hcd { /* one per controller */ ...@@ -97,6 +98,7 @@ struct ehci_hcd { /* one per controller */
int PSS_poll_count; int PSS_poll_count;
int ASS_poll_count; int ASS_poll_count;
int died_poll_count;
/* glue to PCI and HCD framework */ /* glue to PCI and HCD framework */
struct ehci_caps __iomem *caps; struct ehci_caps __iomem *caps;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment