]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
USB: OHCI: add check for stopped frame counter
authorAlan Stern <stern@rowland.harvard.edu>
Fri, 18 Jul 2014 20:26:17 +0000 (16:26 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 18 Jul 2014 23:34:29 +0000 (16:34 -0700)
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>
drivers/usb/host/ohci-hcd.c
drivers/usb/host/ohci.h

index aba8f19eae4d4e0fc2394c6aca357f954494421e..46987735a2e36483ab09742253ed389628c5a158 100644 (file)
@@ -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);
index 0548f5ca18e28027cbbd22381aec5dfaa9b72489..59f424567a8d687c814e4bbd1835d506d27215c3 100644 (file)
@@ -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;