[PATCH 24/25] USB: EHCI: fix up locking

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxxxxxxxxxx>

---

 drivers/usb/host/ehci-hcd.c   |   48 ++++++++++++++++++++++++++++--------------
 drivers/usb/host/ehci-hub.c   |   41 +++++++++++++++++++++++++----------
 drivers/usb/host/ehci-tegra.c |    5 +---
 3 files changed, 64 insertions(+), 30 deletions(-)

Index: usb-3.4/drivers/usb/host/ehci-hcd.c
===================================================================
--- usb-3.4.orig/drivers/usb/host/ehci-hcd.c
+++ usb-3.4/drivers/usb/host/ehci-hcd.c
@@ -167,21 +167,24 @@ static int tdi_in_host_mode (struct 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 *e
 	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_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 *
 	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_hc
 	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(stru
 
 /*
  * 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(stru
 
 	/* 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
 
 	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 *h
 
 	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 *h
 
 	/* 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);
Index: usb-3.4/drivers/usb/host/ehci-hub.c
===================================================================
--- usb-3.4.orig/drivers/usb/host/ehci-hub.c
+++ usb-3.4/drivers/usb/host/ehci-hub.c
@@ -59,6 +59,7 @@ static void ehci_handover_companion_port
 	/* Give the connections some time to appear */
 	msleep(20);
 
+	spin_lock_irq(&ehci->lock);
 	port = HCS_N_PORTS(ehci->hcs_params);
 	while (port--) {
 		if (test_bit(port, &ehci->owned_ports)) {
@@ -70,23 +71,30 @@ static void ehci_handover_companion_port
 				clear_bit(port, &ehci->owned_ports);
 			else if (test_bit(port, &ehci->companion_ports))
 				ehci_writel(ehci, status & ~PORT_PE, reg);
-			else
+			else {
+				spin_unlock_irq(&ehci->lock);
 				ehci_hub_control(hcd, SetPortFeature,
 						USB_PORT_FEAT_RESET, port + 1,
 						NULL, 0);
+				spin_lock_irq(&ehci->lock);
+			}
 		}
 	}
+	spin_unlock_irq(&ehci->lock);
 
 	if (!ehci->owned_ports)
 		return;
 	msleep(90);		/* Wait for resets to complete */
 
+	spin_lock_irq(&ehci->lock);
 	port = HCS_N_PORTS(ehci->hcs_params);
 	while (port--) {
 		if (test_bit(port, &ehci->owned_ports)) {
+			spin_unlock_irq(&ehci->lock);
 			ehci_hub_control(hcd, GetPortStatus,
 					0, port + 1,
 					(char *) &buf, sizeof(buf));
+			spin_lock_irq(&ehci->lock);
 
 			/* The companion should now own the port,
 			 * but if something went wrong the port must not
@@ -105,6 +113,7 @@ static void ehci_handover_companion_port
 	}
 
 	ehci->owned_ports = 0;
+	spin_unlock_irq(&ehci->lock);
 }
 
 static int ehci_port_change(struct ehci_hcd *ehci)
@@ -133,7 +142,6 @@ static void ehci_adjust_port_wakeup_flag
 {
 	int		port;
 	u32		temp;
-	unsigned long	flags;
 
 	/* If remote wakeup is enabled for the root hub but disabled
 	 * for the controller, we must adjust all the port wakeup flags
@@ -143,7 +151,7 @@ static void ehci_adjust_port_wakeup_flag
 	if (!ehci_to_hcd(ehci)->self.root_hub->do_remote_wakeup || do_wakeup)
 		return;
 
-	spin_lock_irqsave(&ehci->lock, flags);
+	spin_lock_irq(&ehci->lock);
 
 	/* clear phy low-power mode before changing wakeup flags */
 	if (ehci->has_hostpc) {
@@ -154,9 +162,9 @@ static void ehci_adjust_port_wakeup_flag
 			temp = ehci_readl(ehci, hostpc_reg);
 			ehci_writel(ehci, temp & ~HOSTPC_PHCD, hostpc_reg);
 		}
-		spin_unlock_irqrestore(&ehci->lock, flags);
+		spin_unlock_irq(&ehci->lock);
 		msleep(5);
-		spin_lock_irqsave(&ehci->lock, flags);
+		spin_lock_irq(&ehci->lock);
 	}
 
 	port = HCS_N_PORTS(ehci->hcs_params);
@@ -194,7 +202,7 @@ static void ehci_adjust_port_wakeup_flag
 	if (!suspending && ehci_port_change(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)
@@ -209,6 +217,9 @@ static int ehci_bus_suspend (struct usb_
 	if (time_before (jiffies, ehci->next_statechange))
 		msleep(5);
 
+	/* stop the schedules */
+	ehci_quiesce(ehci);
+
 	spin_lock_irq (&ehci->lock);
 
 	/* Once the controller is stopped, port resumes that are already
@@ -224,10 +235,6 @@ static int ehci_bus_suspend (struct usb_
 		}
 	}
 
-	/* stop schedules, clean any completed work */
-	ehci_quiesce(ehci);
-	ehci_work(ehci);
-
 	/* Unlike other USB host controller types, EHCI doesn't have
 	 * any notion of "global" or bus-wide suspend.  The driver has
 	 * to manually suspend all the active unsuspended ports, and
@@ -289,6 +296,7 @@ static int ehci_bus_suspend (struct usb_
 					"succeeded" : "failed");
 		}
 	}
+	spin_unlock_irq(&ehci->lock);
 
 	/* Apparently some devices need a >= 1-uframe delay here */
 	if (ehci->bus_suspended)
@@ -296,6 +304,8 @@ static int ehci_bus_suspend (struct usb_
 
 	/* turn off now-idle HC */
 	ehci_halt (ehci);
+
+	spin_lock_irq(&ehci->lock);
 	ehci->rh_state = EHCI_RH_SUSPENDED;
 
 	end_unlink_async(ehci);
@@ -424,13 +434,14 @@ static int ehci_bus_resume (struct usb_h
 	}
 
 	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 */
 	ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
 	(void) ehci_readl(ehci, &ehci->regs->intr_enable);
 
-	spin_unlock_irq (&ehci->lock);
-	ehci_handover_companion_ports(ehci);
 	return 0;
 }
 
@@ -1018,7 +1029,9 @@ static int ehci_hub_control (
 		case USB_PORT_FEAT_TEST:
 			if (!selector || selector > 5)
 				goto error;
+			spin_unlock_irqrestore(&ehci->lock, flags);
 			ehci_quiesce(ehci);
+			spin_lock_irqsave(&ehci->lock, flags);
 
 			/* Put all enabled ports into suspend */
 			while (ports--) {
@@ -1030,7 +1043,11 @@ static int ehci_hub_control (
 					ehci_writel(ehci, temp | PORT_SUSPEND,
 							sreg);
 			}
+
+			spin_unlock_irqrestore(&ehci->lock, flags);
 			ehci_halt(ehci);
+			spin_lock_irqsave(&ehci->lock, flags);
+
 			temp = ehci_readl(ehci, status_reg);
 			temp |= selector << 16;
 			ehci_writel(ehci, temp, status_reg);
Index: usb-3.4/drivers/usb/host/ehci-tegra.c
===================================================================
--- usb-3.4.orig/drivers/usb/host/ehci-tegra.c
+++ usb-3.4/drivers/usb/host/ehci-tegra.c
@@ -444,12 +444,11 @@ static int controller_suspend(struct dev
 	if (time_before(jiffies, ehci->next_statechange))
 		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;
-	ehci_halt(ehci);
 	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-
 	spin_unlock_irqrestore(&ehci->lock, flags);
 
 	tegra_ehci_power_down(hcd);


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux