On Wed, Jul 11, 2012 at 11:22 PM, Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> wrote: > 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)) { If ehci_stop is runing when the EHCI_HRTIMER_POLL_DEAD event is pending, there may be one race since ehci->enabled_hrtimer_events is cleared in ehci_stop. > + > + /* 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 Thanks, -- Ming Lei -- 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