diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2012-07-11 15:23:10 (GMT) |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-07-16 23:56:48 (GMT) |
commit | c4f3476436f7452b97c8accb5dd7d53219a11a3f (patch) | |
tree | 95711a24bb4357dab53d4641aae5a09292974fb8 /drivers/usb/host/ehci-hcd.c | |
parent | f42890782241a60d107f23d08089a4a12b507a11 (diff) | |
download | linux-c4f3476436f7452b97c8accb5dd7d53219a11a3f.tar.xz |
USB: EHCI: fix up locking
This patch (as1588) adjusts the locking in ehci-hcd's various halt,
shutdown, and suspend/resume pathways. We want to hold the spinlock
while writing device registers and accessing shared variables, but not
while polling in a loop.
In addition, there's no need to call ehci_work() at times when no URBs
can be active, i.e., in ehci_stop() and ehci_bus_suspend().
Finally, ehci_adjust_port_wakeup_flags() is called only in situations
where interrupts are enabled; therefore it can use spin_lock_irq
rather than spin_lock_irqsave.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/host/ehci-hcd.c')
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 48 |
1 files changed, 33 insertions, 15 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 340c9c4..ac4c8dd 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -167,21 +167,24 @@ static int tdi_in_host_mode (struct ehci_hcd *ehci) return (tmp & 3) == USBMODE_CM_HC; } -/* force HC to halt state from unknown (EHCI spec section 2.3) */ +/* + * Force HC to halt state from unknown (EHCI spec section 2.3). + * Must be called with interrupts enabled and the lock not held. + */ static int ehci_halt (struct ehci_hcd *ehci) { - u32 temp = ehci_readl(ehci, &ehci->regs->status); + u32 temp; + + spin_lock_irq(&ehci->lock); /* disable any irqs left enabled by previous code */ ehci_writel(ehci, 0, &ehci->regs->intr_enable); - if (ehci_is_TDI(ehci) && tdi_in_host_mode(ehci) == 0) { + if (ehci_is_TDI(ehci) && !tdi_in_host_mode(ehci)) { + spin_unlock_irq(&ehci->lock); return 0; } - if ((temp & STS_HALT) != 0) - return 0; - /* * This routine gets called during probe before ehci->command * has been initialized, so we can't rely on its value. @@ -190,7 +193,11 @@ static int ehci_halt (struct ehci_hcd *ehci) temp = ehci_readl(ehci, &ehci->regs->command); temp &= ~(CMD_RUN | CMD_IAAD); ehci_writel(ehci, temp, &ehci->regs->command); - return handshake (ehci, &ehci->regs->status, + + spin_unlock_irq(&ehci->lock); + synchronize_irq(ehci_to_hcd(ehci)->irq); + + return handshake(ehci, &ehci->regs->status, STS_HALT, STS_HALT, 16 * 125); } @@ -210,7 +217,10 @@ static void tdi_reset (struct ehci_hcd *ehci) ehci_writel(ehci, tmp, &ehci->regs->usbmode); } -/* reset a non-running (STS_HALT == 1) controller */ +/* + * Reset a non-running (STS_HALT == 1) controller. + * Must be called with interrupts enabled and the lock not held. + */ static int ehci_reset (struct ehci_hcd *ehci) { int retval; @@ -248,7 +258,10 @@ static int ehci_reset (struct ehci_hcd *ehci) return retval; } -/* idle the controller (from running) */ +/* + * Idle the controller (turn off the schedules). + * Must be called with interrupts enabled and the lock not held. + */ static void ehci_quiesce (struct ehci_hcd *ehci) { u32 temp; @@ -261,8 +274,10 @@ static void ehci_quiesce (struct ehci_hcd *ehci) handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); /* then disable anything that's still active */ + spin_lock_irq(&ehci->lock); ehci->command &= ~(CMD_ASE | CMD_PSE); ehci_writel(ehci, ehci->command, &ehci->regs->command); + spin_unlock_irq(&ehci->lock); /* hardware can take 16 microframes to turn off ... */ handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, 16 * 125); @@ -301,11 +316,14 @@ static void ehci_turn_off_all_ports(struct ehci_hcd *ehci) /* * Halt HC, turn off all ports, and let the BIOS use the companion controllers. - * Should be called with ehci->lock held. + * Must be called with interrupts enabled and the lock not held. */ static void ehci_silence_controller(struct ehci_hcd *ehci) { ehci_halt(ehci); + + spin_lock_irq(&ehci->lock); + ehci->rh_state = EHCI_RH_HALTED; ehci_turn_off_all_ports(ehci); /* make BIOS/etc use companion controller during reboot */ @@ -313,6 +331,7 @@ static void ehci_silence_controller(struct ehci_hcd *ehci) /* unblock posted writes */ ehci_readl(ehci, &ehci->regs->configured_flag); + spin_unlock_irq(&ehci->lock); } /* ehci_shutdown kick in for silicon on any bus (not just pci, etc). @@ -325,10 +344,11 @@ static void ehci_shutdown(struct usb_hcd *hcd) spin_lock_irq(&ehci->lock); ehci->rh_state = EHCI_RH_STOPPING; - ehci_silence_controller(ehci); ehci->enabled_hrtimer_events = 0; spin_unlock_irq(&ehci->lock); + ehci_silence_controller(ehci); + hrtimer_cancel(&ehci->hrtimer); } @@ -400,11 +420,11 @@ static void ehci_stop (struct usb_hcd *hcd) spin_lock_irq(&ehci->lock); ehci->enabled_hrtimer_events = 0; - ehci_quiesce(ehci); + spin_unlock_irq(&ehci->lock); + ehci_quiesce(ehci); ehci_silence_controller(ehci); ehci_reset (ehci); - spin_unlock_irq(&ehci->lock); hrtimer_cancel(&ehci->hrtimer); remove_sysfs_files(ehci); @@ -412,8 +432,6 @@ static void ehci_stop (struct usb_hcd *hcd) /* root hub is shut down separately (first, when possible) */ spin_lock_irq (&ehci->lock); - if (ehci->async) - ehci_work (ehci); end_free_itds(ehci); spin_unlock_irq (&ehci->lock); ehci_mem_cleanup (ehci); |