summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2014-07-18 16:26:17 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-07-18 16:34:29 -0700
commit499b3803d3e2f062f73bf22372b38393369ffcbf (patch)
tree4f33ecaf596af157df8a4cdccbf1fd2ea5f6e3f0
parent81e38333513cec155c720432226dabe9f9f76a77 (diff)
downloadlinux-499b3803d3e2f062f73bf22372b38393369ffcbf.tar.bz2
USB: OHCI: add check for stopped frame counter
This patch adds an extra check to ohci-hcd's I/O watchdog routine. If the controller stops updating the frame counter, we will assume it is dead. But there has to be an exception: Some controllers stop the frame counter when no ports are connected. Check to make sure there is at least one active port before deciding the controller is dead. (This test may appear racy, but it isn't. Enabling a newly connected port takes several milliseconds, during which time the frame counter must advance.) Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Tested-by: Dennis New <dennisn@dennisn.linuxd.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/host/ohci-hcd.c36
-rw-r--r--drivers/usb/host/ohci.h1
2 files changed, 35 insertions, 2 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index aba8f19eae4d..46987735a2e3 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -72,7 +72,7 @@
static const char hcd_name [] = "ohci_hcd";
#define STATECHANGE_DELAY msecs_to_jiffies(300)
-#define IO_WATCHDOG_DELAY msecs_to_jiffies(250)
+#define IO_WATCHDOG_DELAY msecs_to_jiffies(250)
#include "ohci.h"
#include "pci-quirks.h"
@@ -230,9 +230,11 @@ static int ohci_urb_enqueue (
/* Start up the I/O watchdog timer, if it's not running */
if (!timer_pending(&ohci->io_watchdog) &&
- list_empty(&ohci->eds_in_use))
+ list_empty(&ohci->eds_in_use)) {
+ ohci->prev_frame_no = ohci_frame_no(ohci);
mod_timer(&ohci->io_watchdog,
jiffies + IO_WATCHDOG_DELAY);
+ }
list_add(&ed->in_use_list, &ohci->eds_in_use);
if (ed->type == PIPE_ISOCHRONOUS) {
@@ -727,6 +729,7 @@ static void io_watchdog_func(unsigned long _ohci)
u32 head;
struct ed *ed;
struct td *td, *td_start, *td_next;
+ unsigned frame_no;
unsigned long flags;
spin_lock_irqsave(&ohci->lock, flags);
@@ -742,6 +745,7 @@ static void io_watchdog_func(unsigned long _ohci)
if (!(status & OHCI_INTR_WDH) && ohci->wdh_cnt == ohci->prev_wdh_cnt) {
if (ohci->prev_donehead) {
ohci_err(ohci, "HcDoneHead not written back; disabled\n");
+ died:
usb_hc_died(ohci_to_hcd(ohci));
ohci_dump(ohci);
ohci_shutdown(ohci_to_hcd(ohci));
@@ -802,7 +806,35 @@ static void io_watchdog_func(unsigned long _ohci)
ohci_work(ohci);
if (ohci->rh_state == OHCI_RH_RUNNING) {
+
+ /*
+ * Sometimes a controller just stops working. We can tell
+ * by checking that the frame counter has advanced since
+ * the last time we ran.
+ *
+ * But be careful: Some controllers violate the spec by
+ * stopping their frame counter when no ports are active.
+ */
+ frame_no = ohci_frame_no(ohci);
+ if (frame_no == ohci->prev_frame_no) {
+ int active_cnt = 0;
+ int i;
+ unsigned tmp;
+
+ for (i = 0; i < ohci->num_ports; ++i) {
+ tmp = roothub_portstatus(ohci, i);
+ /* Enabled and not suspended? */
+ if ((tmp & RH_PS_PES) && !(tmp & RH_PS_PSS))
+ ++active_cnt;
+ }
+
+ if (active_cnt > 0) {
+ ohci_err(ohci, "frame counter not updating; disabled\n");
+ goto died;
+ }
+ }
if (!list_empty(&ohci->eds_in_use)) {
+ ohci->prev_frame_no = frame_no;
ohci->prev_wdh_cnt = ohci->wdh_cnt;
ohci->prev_donehead = ohci_readl(ohci,
&ohci->regs->donehead);
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index 0548f5ca18e2..59f424567a8d 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -421,6 +421,7 @@ struct ohci_hcd {
// there are also chip quirks/bugs in init logic
+ unsigned prev_frame_no;
unsigned wdh_cnt, prev_wdh_cnt;
u32 prev_donehead;
struct timer_list io_watchdog;