diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2012-07-11 11:22:31 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-07-16 19:54:25 -0400 |
commit | bf6387bcd16975ba8952b094f262a359d74e1c8a (patch) | |
tree | ac578ff256a5cd972c5866525c65e44e4360974e /drivers/usb | |
parent | df2022553dd8d34d49e16c19d851ea619438f0ef (diff) |
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: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 20 | ||||
-rw-r--r-- | drivers/usb/host/ehci-timer.c | 26 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 2 |
3 files changed, 38 insertions, 10 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index edcfd2c4295e..1676c66b8530 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c | |||
@@ -888,20 +888,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) | |||
888 | /* PCI errors [4.15.2.4] */ | 888 | /* PCI errors [4.15.2.4] */ |
889 | if (unlikely ((status & STS_FATAL) != 0)) { | 889 | if (unlikely ((status & STS_FATAL) != 0)) { |
890 | ehci_err(ehci, "fatal error\n"); | 890 | ehci_err(ehci, "fatal error\n"); |
891 | ehci->rh_state = EHCI_RH_STOPPING; | ||
892 | dbg_cmd(ehci, "fatal", cmd); | 891 | dbg_cmd(ehci, "fatal", cmd); |
893 | dbg_status(ehci, "fatal", status); | 892 | dbg_status(ehci, "fatal", status); |
894 | ehci_halt(ehci); | ||
895 | dead: | 893 | dead: |
896 | ehci->enabled_hrtimer_events = 0; | ||
897 | hrtimer_try_to_cancel(&ehci->hrtimer); | ||
898 | ehci_reset(ehci); | ||
899 | ehci_writel(ehci, 0, &ehci->regs->configured_flag); | ||
900 | usb_hc_died(hcd); | 894 | usb_hc_died(hcd); |
901 | /* generic layer kills/unlinks all urbs, then | 895 | |
902 | * uses ehci_stop to clean up the rest | 896 | /* Don't let the controller do anything more */ |
903 | */ | 897 | ehci->rh_state = EHCI_RH_STOPPING; |
904 | bh = 1; | 898 | ehci->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); |
899 | ehci_writel(ehci, ehci->command, &ehci->regs->command); | ||
900 | ehci_writel(ehci, 0, &ehci->regs->intr_enable); | ||
901 | ehci_handle_controller_death(ehci); | ||
902 | |||
903 | /* Handle completions when the controller stops */ | ||
904 | bh = 0; | ||
905 | } | 905 | } |
906 | 906 | ||
907 | if (bh) | 907 | if (bh) |
diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index bd8b591771b0..0c5326a8883c 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c | |||
@@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) | |||
69 | static unsigned event_delays_ns[] = { | 69 | static unsigned event_delays_ns[] = { |
70 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ | 70 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ |
71 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ | 71 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ |
72 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ | ||
72 | 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ | 73 | 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ |
73 | 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ | 74 | 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ |
74 | 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ | 75 | 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ |
@@ -193,6 +194,30 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) | |||
193 | } | 194 | } |
194 | 195 | ||
195 | 196 | ||
197 | /* Poll the STS_HALT status bit; see when a dead controller stops */ | ||
198 | static void ehci_handle_controller_death(struct ehci_hcd *ehci) | ||
199 | { | ||
200 | if (!(ehci_readl(ehci, &ehci->regs->status) & STS_HALT)) { | ||
201 | |||
202 | /* Give up after a few milliseconds */ | ||
203 | if (ehci->died_poll_count++ < 5) { | ||
204 | /* Try again later */ | ||
205 | ehci_enable_event(ehci, EHCI_HRTIMER_POLL_DEAD, true); | ||
206 | return; | ||
207 | } | ||
208 | ehci_warn(ehci, "Waited too long for the controller to stop, giving up\n"); | ||
209 | } | ||
210 | |||
211 | /* Clean up the mess */ | ||
212 | ehci->rh_state = EHCI_RH_HALTED; | ||
213 | ehci_writel(ehci, 0, &ehci->regs->configured_flag); | ||
214 | ehci_writel(ehci, 0, &ehci->regs->intr_enable); | ||
215 | ehci_work(ehci); | ||
216 | |||
217 | /* Not in process context, so don't try to reset the controller */ | ||
218 | } | ||
219 | |||
220 | |||
196 | /* Handle unlinked interrupt QHs once they are gone from the hardware */ | 221 | /* Handle unlinked interrupt QHs once they are gone from the hardware */ |
197 | static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) | 222 | static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) |
198 | { | 223 | { |
@@ -233,6 +258,7 @@ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) | |||
233 | static void (*event_handlers[])(struct ehci_hcd *) = { | 258 | static void (*event_handlers[])(struct ehci_hcd *) = { |
234 | ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ | 259 | ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ |
235 | ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ | 260 | ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ |
261 | ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ | ||
236 | ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ | 262 | ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ |
237 | ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ | 263 | ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ |
238 | ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ | 264 | ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ |
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index f36f1f85d7fd..6874d89b0b64 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h | |||
@@ -81,6 +81,7 @@ enum ehci_rh_state { | |||
81 | enum ehci_hrtimer_event { | 81 | enum ehci_hrtimer_event { |
82 | EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ | 82 | EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ |
83 | EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ | 83 | EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ |
84 | EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ | ||
84 | EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ | 85 | EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ |
85 | EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ | 86 | EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ |
86 | EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ | 87 | EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ |
@@ -97,6 +98,7 @@ struct ehci_hcd { /* one per controller */ | |||
97 | 98 | ||
98 | int PSS_poll_count; | 99 | int PSS_poll_count; |
99 | int ASS_poll_count; | 100 | int ASS_poll_count; |
101 | int died_poll_count; | ||
100 | 102 | ||
101 | /* glue to PCI and HCD framework */ | 103 | /* glue to PCI and HCD framework */ |
102 | struct ehci_caps __iomem *caps; | 104 | struct ehci_caps __iomem *caps; |