]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
USB: EHCI: unlink unused QHs when the controller is stopped
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 5 Apr 2011 17:36:15 +0000 (13:36 -0400)
committerPaul Gortmaker <paul.gortmaker@windriver.com>
Wed, 14 Mar 2012 14:57:16 +0000 (10:57 -0400)
commit 94ae4976e253757e9b03a44d27d41b20f1829d80 upstream.

This patch (as1458) fixes a problem affecting ultra-reliable systems:
When hardware failover of an EHCI controller occurs, the data
structures do not get released correctly.  This is because the routine
responsible for removing unused QHs from the async schedule assumes
the controller is running properly (the frame counter is used in
determining how long the QH has been idle) -- but when a failover
causes the controller to be electronically disconnected from the PCI
bus, obviously it stops running.

The solution is simple: Allow scan_async() to remove a QH from the
async schedule if it has been idle for long enough _or_ if the
controller is stopped.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Reported-and-Tested-by: Dan Duval <dan.duval@stratus.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
drivers/usb/host/ehci-q.c

index d1d4bb10779cce47cd736c5c91bb5c87ba7a0722..781c573af2e5a4c7432172bd0a3d19d7d3e475a4 100644 (file)
@@ -1246,24 +1246,27 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
 
 static void scan_async (struct ehci_hcd *ehci)
 {
+       bool                    stopped;
        struct ehci_qh          *qh;
        enum ehci_timer_action  action = TIMER_IO_WATCHDOG;
 
        ehci->stamp = ehci_readl(ehci, &ehci->regs->frame_index);
        timer_action_done (ehci, TIMER_ASYNC_SHRINK);
 rescan:
+       stopped = !HC_IS_RUNNING(ehci_to_hcd(ehci)->state);
        qh = ehci->async->qh_next.qh;
        if (likely (qh != NULL)) {
                do {
                        /* clean any finished work for this qh */
-                       if (!list_empty (&qh->qtd_list)
-                                       && qh->stamp != ehci->stamp) {
+                       if (!list_empty(&qh->qtd_list) && (stopped ||
+                                       qh->stamp != ehci->stamp)) {
                                int temp;
 
                                /* unlinks could happen here; completion
                                 * reporting drops the lock.  rescan using
                                 * the latest schedule, but don't rescan
-                                * qhs we already finished (no looping).
+                                * qhs we already finished (no looping)
+                                * unless the controller is stopped.
                                 */
                                qh = qh_get (qh);
                                qh->stamp = ehci->stamp;
@@ -1284,9 +1287,9 @@ rescan:
                         */
                        if (list_empty(&qh->qtd_list)
                                        && qh->qh_state == QH_STATE_LINKED) {
-                               if (!ehci->reclaim
-                                       && ((ehci->stamp - qh->stamp) & 0x1fff)
-                                               >= (EHCI_SHRINK_FRAMES * 8))
+                               if (!ehci->reclaim && (stopped ||
+                                       ((ehci->stamp - qh->stamp) & 0x1fff)
+                                               >= EHCI_SHRINK_FRAMES * 8))
                                        start_unlink_async(ehci, qh);
                                else
                                        action = TIMER_ASYNC_SHRINK;