Commit c4f34764 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f4289078
...@@ -167,21 +167,24 @@ static int tdi_in_host_mode (struct ehci_hcd *ehci) ...@@ -167,21 +167,24 @@ static int tdi_in_host_mode (struct ehci_hcd *ehci)
return (tmp & 3) == USBMODE_CM_HC; 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) 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 */ /* disable any irqs left enabled by previous code */
ehci_writel(ehci, 0, &ehci->regs->intr_enable); 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; return 0;
} }
if ((temp & STS_HALT) != 0)
return 0;
/* /*
* This routine gets called during probe before ehci->command * This routine gets called during probe before ehci->command
* has been initialized, so we can't rely on its value. * has been initialized, so we can't rely on its value.
...@@ -190,7 +193,11 @@ static int ehci_halt (struct ehci_hcd *ehci) ...@@ -190,7 +193,11 @@ static int ehci_halt (struct ehci_hcd *ehci)
temp = ehci_readl(ehci, &ehci->regs->command); temp = ehci_readl(ehci, &ehci->regs->command);
temp &= ~(CMD_RUN | CMD_IAAD); temp &= ~(CMD_RUN | CMD_IAAD);
ehci_writel(ehci, temp, &ehci->regs->command); 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); STS_HALT, STS_HALT, 16 * 125);
} }
...@@ -210,7 +217,10 @@ static void tdi_reset (struct ehci_hcd *ehci) ...@@ -210,7 +217,10 @@ static void tdi_reset (struct ehci_hcd *ehci)
ehci_writel(ehci, tmp, &ehci->regs->usbmode); 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) static int ehci_reset (struct ehci_hcd *ehci)
{ {
int retval; int retval;
...@@ -248,7 +258,10 @@ static int ehci_reset (struct ehci_hcd *ehci) ...@@ -248,7 +258,10 @@ static int ehci_reset (struct ehci_hcd *ehci)
return retval; 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) static void ehci_quiesce (struct ehci_hcd *ehci)
{ {
u32 temp; u32 temp;
...@@ -261,8 +274,10 @@ static void ehci_quiesce (struct ehci_hcd *ehci) ...@@ -261,8 +274,10 @@ static void ehci_quiesce (struct ehci_hcd *ehci)
handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, 16 * 125);
/* then disable anything that's still active */ /* then disable anything that's still active */
spin_lock_irq(&ehci->lock);
ehci->command &= ~(CMD_ASE | CMD_PSE); ehci->command &= ~(CMD_ASE | CMD_PSE);
ehci_writel(ehci, ehci->command, &ehci->regs->command); ehci_writel(ehci, ehci->command, &ehci->regs->command);
spin_unlock_irq(&ehci->lock);
/* hardware can take 16 microframes to turn off ... */ /* hardware can take 16 microframes to turn off ... */
handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, 16 * 125); 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) ...@@ -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. * 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) static void ehci_silence_controller(struct ehci_hcd *ehci)
{ {
ehci_halt(ehci); ehci_halt(ehci);
spin_lock_irq(&ehci->lock);
ehci->rh_state = EHCI_RH_HALTED;
ehci_turn_off_all_ports(ehci); ehci_turn_off_all_ports(ehci);
/* make BIOS/etc use companion controller during reboot */ /* make BIOS/etc use companion controller during reboot */
...@@ -313,6 +331,7 @@ static void ehci_silence_controller(struct ehci_hcd *ehci) ...@@ -313,6 +331,7 @@ static void ehci_silence_controller(struct ehci_hcd *ehci)
/* unblock posted writes */ /* unblock posted writes */
ehci_readl(ehci, &ehci->regs->configured_flag); 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). /* 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) ...@@ -325,10 +344,11 @@ static void ehci_shutdown(struct usb_hcd *hcd)
spin_lock_irq(&ehci->lock); spin_lock_irq(&ehci->lock);
ehci->rh_state = EHCI_RH_STOPPING; ehci->rh_state = EHCI_RH_STOPPING;
ehci_silence_controller(ehci);
ehci->enabled_hrtimer_events = 0; ehci->enabled_hrtimer_events = 0;
spin_unlock_irq(&ehci->lock); spin_unlock_irq(&ehci->lock);
ehci_silence_controller(ehci);
hrtimer_cancel(&ehci->hrtimer); hrtimer_cancel(&ehci->hrtimer);
} }
...@@ -400,11 +420,11 @@ static void ehci_stop (struct usb_hcd *hcd) ...@@ -400,11 +420,11 @@ static void ehci_stop (struct usb_hcd *hcd)
spin_lock_irq(&ehci->lock); spin_lock_irq(&ehci->lock);
ehci->enabled_hrtimer_events = 0; ehci->enabled_hrtimer_events = 0;
ehci_quiesce(ehci); spin_unlock_irq(&ehci->lock);
ehci_quiesce(ehci);
ehci_silence_controller(ehci); ehci_silence_controller(ehci);
ehci_reset (ehci); ehci_reset (ehci);
spin_unlock_irq(&ehci->lock);
hrtimer_cancel(&ehci->hrtimer); hrtimer_cancel(&ehci->hrtimer);
remove_sysfs_files(ehci); remove_sysfs_files(ehci);
...@@ -412,8 +432,6 @@ static void ehci_stop (struct usb_hcd *hcd) ...@@ -412,8 +432,6 @@ static void ehci_stop (struct usb_hcd *hcd)
/* root hub is shut down separately (first, when possible) */ /* root hub is shut down separately (first, when possible) */
spin_lock_irq (&ehci->lock); spin_lock_irq (&ehci->lock);
if (ehci->async)
ehci_work (ehci);
end_free_itds(ehci); end_free_itds(ehci);
spin_unlock_irq (&ehci->lock); spin_unlock_irq (&ehci->lock);
ehci_mem_cleanup (ehci); ehci_mem_cleanup (ehci);
......
...@@ -59,6 +59,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) ...@@ -59,6 +59,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
/* Give the connections some time to appear */ /* Give the connections some time to appear */
msleep(20); msleep(20);
spin_lock_irq(&ehci->lock);
port = HCS_N_PORTS(ehci->hcs_params); port = HCS_N_PORTS(ehci->hcs_params);
while (port--) { while (port--) {
if (test_bit(port, &ehci->owned_ports)) { if (test_bit(port, &ehci->owned_ports)) {
...@@ -70,23 +71,30 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) ...@@ -70,23 +71,30 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
clear_bit(port, &ehci->owned_ports); clear_bit(port, &ehci->owned_ports);
else if (test_bit(port, &ehci->companion_ports)) else if (test_bit(port, &ehci->companion_ports))
ehci_writel(ehci, status & ~PORT_PE, reg); ehci_writel(ehci, status & ~PORT_PE, reg);
else else {
spin_unlock_irq(&ehci->lock);
ehci_hub_control(hcd, SetPortFeature, ehci_hub_control(hcd, SetPortFeature,
USB_PORT_FEAT_RESET, port + 1, USB_PORT_FEAT_RESET, port + 1,
NULL, 0); NULL, 0);
spin_lock_irq(&ehci->lock);
}
} }
} }
spin_unlock_irq(&ehci->lock);
if (!ehci->owned_ports) if (!ehci->owned_ports)
return; return;
msleep(90); /* Wait for resets to complete */ msleep(90); /* Wait for resets to complete */
spin_lock_irq(&ehci->lock);
port = HCS_N_PORTS(ehci->hcs_params); port = HCS_N_PORTS(ehci->hcs_params);
while (port--) { while (port--) {
if (test_bit(port, &ehci->owned_ports)) { if (test_bit(port, &ehci->owned_ports)) {
spin_unlock_irq(&ehci->lock);
ehci_hub_control(hcd, GetPortStatus, ehci_hub_control(hcd, GetPortStatus,
0, port + 1, 0, port + 1,
(char *) &buf, sizeof(buf)); (char *) &buf, sizeof(buf));
spin_lock_irq(&ehci->lock);
/* The companion should now own the port, /* The companion should now own the port,
* but if something went wrong the port must not * but if something went wrong the port must not
...@@ -105,6 +113,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) ...@@ -105,6 +113,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
} }
ehci->owned_ports = 0; ehci->owned_ports = 0;
spin_unlock_irq(&ehci->lock);
} }
static int ehci_port_change(struct ehci_hcd *ehci) static int ehci_port_change(struct ehci_hcd *ehci)
...@@ -133,7 +142,6 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, ...@@ -133,7 +142,6 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
{ {
int port; int port;
u32 temp; u32 temp;
unsigned long flags;
/* If remote wakeup is enabled for the root hub but disabled /* If remote wakeup is enabled for the root hub but disabled
* for the controller, we must adjust all the port wakeup flags * for the controller, we must adjust all the port wakeup flags
...@@ -143,7 +151,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, ...@@ -143,7 +151,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
if (!ehci_to_hcd(ehci)->self.root_hub->do_remote_wakeup || do_wakeup) if (!ehci_to_hcd(ehci)->self.root_hub->do_remote_wakeup || do_wakeup)
return; return;
spin_lock_irqsave(&ehci->lock, flags); spin_lock_irq(&ehci->lock);
/* clear phy low-power mode before changing wakeup flags */ /* clear phy low-power mode before changing wakeup flags */
if (ehci->has_hostpc) { if (ehci->has_hostpc) {
...@@ -154,9 +162,9 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, ...@@ -154,9 +162,9 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
temp = ehci_readl(ehci, hostpc_reg); temp = ehci_readl(ehci, hostpc_reg);
ehci_writel(ehci, temp & ~HOSTPC_PHCD, hostpc_reg); ehci_writel(ehci, temp & ~HOSTPC_PHCD, hostpc_reg);
} }
spin_unlock_irqrestore(&ehci->lock, flags); spin_unlock_irq(&ehci->lock);
msleep(5); msleep(5);
spin_lock_irqsave(&ehci->lock, flags); spin_lock_irq(&ehci->lock);
} }
port = HCS_N_PORTS(ehci->hcs_params); port = HCS_N_PORTS(ehci->hcs_params);
...@@ -194,7 +202,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, ...@@ -194,7 +202,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
if (!suspending && ehci_port_change(ehci)) if (!suspending && ehci_port_change(ehci))
usb_hcd_resume_root_hub(ehci_to_hcd(ehci)); usb_hcd_resume_root_hub(ehci_to_hcd(ehci));
spin_unlock_irqrestore(&ehci->lock, flags); spin_unlock_irq(&ehci->lock);
} }
static int ehci_bus_suspend (struct usb_hcd *hcd) static int ehci_bus_suspend (struct usb_hcd *hcd)
...@@ -209,6 +217,9 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -209,6 +217,9 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
if (time_before (jiffies, ehci->next_statechange)) if (time_before (jiffies, ehci->next_statechange))
msleep(5); msleep(5);
/* stop the schedules */
ehci_quiesce(ehci);
spin_lock_irq (&ehci->lock); spin_lock_irq (&ehci->lock);
/* Once the controller is stopped, port resumes that are already /* Once the controller is stopped, port resumes that are already
...@@ -224,10 +235,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -224,10 +235,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
} }
} }
/* stop schedules, clean any completed work */
ehci_quiesce(ehci);
ehci_work(ehci);
/* Unlike other USB host controller types, EHCI doesn't have /* Unlike other USB host controller types, EHCI doesn't have
* any notion of "global" or bus-wide suspend. The driver has * any notion of "global" or bus-wide suspend. The driver has
* to manually suspend all the active unsuspended ports, and * to manually suspend all the active unsuspended ports, and
...@@ -289,6 +296,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -289,6 +296,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
"succeeded" : "failed"); "succeeded" : "failed");
} }
} }
spin_unlock_irq(&ehci->lock);
/* Apparently some devices need a >= 1-uframe delay here */ /* Apparently some devices need a >= 1-uframe delay here */
if (ehci->bus_suspended) if (ehci->bus_suspended)
...@@ -296,6 +304,8 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -296,6 +304,8 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
/* turn off now-idle HC */ /* turn off now-idle HC */
ehci_halt (ehci); ehci_halt (ehci);
spin_lock_irq(&ehci->lock);
ehci->rh_state = EHCI_RH_SUSPENDED; ehci->rh_state = EHCI_RH_SUSPENDED;
end_unlink_async(ehci); end_unlink_async(ehci);
...@@ -424,13 +434,14 @@ static int ehci_bus_resume (struct usb_hcd *hcd) ...@@ -424,13 +434,14 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
} }
ehci->next_statechange = jiffies + msecs_to_jiffies(5); ehci->next_statechange = jiffies + msecs_to_jiffies(5);
spin_unlock_irq(&ehci->lock);
ehci_handover_companion_ports(ehci);
/* Now we can safely re-enable irqs */ /* Now we can safely re-enable irqs */
ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
(void) ehci_readl(ehci, &ehci->regs->intr_enable); (void) ehci_readl(ehci, &ehci->regs->intr_enable);
spin_unlock_irq (&ehci->lock);
ehci_handover_companion_ports(ehci);
return 0; return 0;
} }
...@@ -1018,7 +1029,9 @@ static int ehci_hub_control ( ...@@ -1018,7 +1029,9 @@ static int ehci_hub_control (
case USB_PORT_FEAT_TEST: case USB_PORT_FEAT_TEST:
if (!selector || selector > 5) if (!selector || selector > 5)
goto error; goto error;
spin_unlock_irqrestore(&ehci->lock, flags);
ehci_quiesce(ehci); ehci_quiesce(ehci);
spin_lock_irqsave(&ehci->lock, flags);
/* Put all enabled ports into suspend */ /* Put all enabled ports into suspend */
while (ports--) { while (ports--) {
...@@ -1030,7 +1043,11 @@ static int ehci_hub_control ( ...@@ -1030,7 +1043,11 @@ static int ehci_hub_control (
ehci_writel(ehci, temp | PORT_SUSPEND, ehci_writel(ehci, temp | PORT_SUSPEND,
sreg); sreg);
} }
spin_unlock_irqrestore(&ehci->lock, flags);
ehci_halt(ehci); ehci_halt(ehci);
spin_lock_irqsave(&ehci->lock, flags);
temp = ehci_readl(ehci, status_reg); temp = ehci_readl(ehci, status_reg);
temp |= selector << 16; temp |= selector << 16;
ehci_writel(ehci, temp, status_reg); ehci_writel(ehci, temp, status_reg);
......
...@@ -445,12 +445,11 @@ static int controller_suspend(struct device *dev) ...@@ -445,12 +445,11 @@ static int controller_suspend(struct device *dev)
if (time_before(jiffies, ehci->next_statechange)) if (time_before(jiffies, ehci->next_statechange))
msleep(10); msleep(10);
spin_lock_irqsave(&ehci->lock, flags); ehci_halt(ehci);
spin_lock_irqsave(&ehci->lock, flags);
tegra->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3; tegra->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3;
ehci_halt(ehci);
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
spin_unlock_irqrestore(&ehci->lock, flags); spin_unlock_irqrestore(&ehci->lock, flags);
tegra_ehci_power_down(hcd); tegra_ehci_power_down(hcd);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment