USB: EHCI: resolve some unlikely races
authorAlan Stern <stern@rowland.harvard.edu>
Wed, 11 Jul 2012 15:23:16 +0000 (11:23 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 16 Jul 2012 23:56:48 +0000 (16:56 -0700)
This patch (as1589) resolves some unlikely races involving system
shutdown or controller death in ehci-hcd:

Shutdown races with both root-hub resume and controller
resume.

Controller death races with root-hub suspend.

A new bitflag is added to indicate that the controller has been shut
down (whether for system shutdown or because it died).  Tests are
added in the suspend and resume pathways to avoid reactivating the
controller after any sort of shutdown.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-hub.c
drivers/usb/host/ehci.h

index ac4c8ddde20a624dadb7f3b0ced3f31aa4d601a4..e44ca5453aa244ad58702a25b29d5d233d2e84ed 100644 (file)
@@ -343,6 +343,7 @@ static void ehci_shutdown(struct usb_hcd *hcd)
        struct ehci_hcd *ehci = hcd_to_ehci(hcd);
 
        spin_lock_irq(&ehci->lock);
+       ehci->shutdown = true;
        ehci->rh_state = EHCI_RH_STOPPING;
        ehci->enabled_hrtimer_events = 0;
        spin_unlock_irq(&ehci->lock);
@@ -823,6 +824,7 @@ dead:
                usb_hc_died(hcd);
 
                /* Don't let the controller do anything more */
+               ehci->shutdown = true;
                ehci->rh_state = EHCI_RH_STOPPING;
                ehci->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE);
                ehci_writel(ehci, ehci->command, &ehci->regs->command);
@@ -1129,6 +1131,9 @@ static int __maybe_unused ehci_resume(struct usb_hcd *hcd, bool hibernated)
        /* Mark hardware accessible again as we are back to full power by now */
        set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 
+       if (ehci->shutdown)
+               return 0;               /* Controller is dead */
+
        /*
         * If CF is still set and we aren't resuming from hibernation
         * then we maintained suspend power.
@@ -1139,10 +1144,17 @@ static int __maybe_unused ehci_resume(struct usb_hcd *hcd, bool hibernated)
                int     mask = INTR_MASK;
 
                ehci_prepare_ports_for_controller_resume(ehci);
+
+               spin_lock_irq(&ehci->lock);
+               if (ehci->shutdown)
+                       goto skip;
+
                if (!hcd->self.root_hub->do_remote_wakeup)
                        mask &= ~STS_PCD;
                ehci_writel(ehci, mask, &ehci->regs->intr_enable);
                ehci_readl(ehci, &ehci->regs->intr_enable);
+ skip:
+               spin_unlock_irq(&ehci->lock);
                return 0;
        }
 
@@ -1154,14 +1166,20 @@ static int __maybe_unused ehci_resume(struct usb_hcd *hcd, bool hibernated)
        (void) ehci_halt(ehci);
        (void) ehci_reset(ehci);
 
+       spin_lock_irq(&ehci->lock);
+       if (ehci->shutdown)
+               goto skip;
+
        ehci_writel(ehci, ehci->command, &ehci->regs->command);
        ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
        ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
 
+       ehci->rh_state = EHCI_RH_SUSPENDED;
+       spin_unlock_irq(&ehci->lock);
+
        /* here we "know" root ports should always stay powered */
        ehci_port_power(ehci, 1);
 
-       ehci->rh_state = EHCI_RH_SUSPENDED;
        return 1;
 }
 
index ffc5f27df725bff5edc12df99bd05098dadff9b0..c7880223738a694fe4e04277f8e27f2daf0e924a 100644 (file)
@@ -221,6 +221,8 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
        ehci_quiesce(ehci);
 
        spin_lock_irq (&ehci->lock);
+       if (ehci->rh_state < EHCI_RH_RUNNING)
+               goto done;
 
        /* Once the controller is stopped, port resumes that are already
         * in progress won't complete.  Hence if remote wakeup is enabled
@@ -306,6 +308,10 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
        ehci_halt (ehci);
 
        spin_lock_irq(&ehci->lock);
+       if (ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_POLL_DEAD))
+               ehci_handle_controller_death(ehci);
+       if (ehci->rh_state != EHCI_RH_RUNNING)
+               goto done;
        ehci->rh_state = EHCI_RH_SUSPENDED;
 
        end_unlink_async(ehci);
@@ -320,6 +326,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
        ehci_writel(ehci, mask, &ehci->regs->intr_enable);
        ehci_readl(ehci, &ehci->regs->intr_enable);
 
+ done:
        ehci->next_statechange = jiffies + msecs_to_jiffies(10);
        ehci->enabled_hrtimer_events = 0;
        ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT;
@@ -342,10 +349,8 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
        if (time_before (jiffies, ehci->next_statechange))
                msleep(5);
        spin_lock_irq (&ehci->lock);
-       if (!HCD_HW_ACCESSIBLE(hcd)) {
-               spin_unlock_irq(&ehci->lock);
-               return -ESHUTDOWN;
-       }
+       if (!HCD_HW_ACCESSIBLE(hcd) || ehci->shutdown)
+               goto shutdown;
 
        if (unlikely(ehci->debug)) {
                if (!dbgp_reset_prep())
@@ -384,6 +389,8 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
        spin_unlock_irq(&ehci->lock);
        msleep(8);
        spin_lock_irq(&ehci->lock);
+       if (ehci->shutdown)
+               goto shutdown;
 
        /* clear phy low-power mode before resume */
        if (ehci->bus_suspended && ehci->has_hostpc) {
@@ -401,6 +408,8 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
                spin_unlock_irq(&ehci->lock);
                msleep(5);
                spin_lock_irq(&ehci->lock);
+               if (ehci->shutdown)
+                       goto shutdown;
        }
 
        /* manually resume the ports we suspended during bus_suspend() */
@@ -421,6 +430,8 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
                spin_unlock_irq(&ehci->lock);
                msleep(20);
                spin_lock_irq(&ehci->lock);
+               if (ehci->shutdown)
+                       goto shutdown;
        }
 
        i = HCS_N_PORTS (ehci->hcs_params);
@@ -439,10 +450,18 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
        ehci_handover_companion_ports(ehci);
 
        /* Now we can safely re-enable irqs */
+       spin_lock_irq(&ehci->lock);
+       if (ehci->shutdown)
+               goto shutdown;
        ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
        (void) ehci_readl(ehci, &ehci->regs->intr_enable);
+       spin_unlock_irq(&ehci->lock);
 
        return 0;
+
+ shutdown:
+       spin_unlock_irq(&ehci->lock);
+       return -ESHUTDOWN;
 }
 
 #else
index 7de58fe52d513177b10479fbeb1b32ad20ac32c1..da07d98f7d1de7780c578853e92b705c1231d316 100644 (file)
@@ -118,6 +118,7 @@ struct ehci_hcd {                   /* one per controller */
        bool                    need_rescan:1;
        bool                    intr_unlinking:1;
        bool                    async_unlinking:1;
+       bool                    shutdown:1;
        struct ehci_qh          *qh_scan_next;
 
        /* async schedule support */