[PATCH] xhci_suspend optimized for hibernate

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

 



This patch improves xhci suspend time during a hibernate call by 
removing redundant saves and stores. It adds a new hc_driver callback 
called pci_poweroff which any host can define. It will be called on 
hibernate instead of pci_suspend if it exists. The patch then implements 
this call for xhci (xhci_pci_poweroff), which instructs xhci_suspend to 
skip any calls which save the current state, since the hibernate image 
has already been generate and all subsequent info will be lost. This 
improves the suspend time by a bit.

This is a patch is an implementation of a feature suggested by Alan 
Stern and Sarah Sharp: http://marc.info/?l=linux-usb&m=136147059529127&w=2

xhci_suspend is called both during system suspend, and after the memory image
is created during system hibernate. On suspend, the driver directs the xHC to
save registers (and anything in scratchpad pages), and does a bunch of other
cleanup. On resume from suspend, the registers, pages, and dequeue pointers
are restored. On hibernate, we also save state. However, xhci_suspend is
called after the hibernate memory image is created, so any memory writes
(e.g. to zero rings or save dequeue pointers) will be lost on resume. On
resume from hibernate, we basically wipe out any changes in the host and
driver, and re-initialize from scratch. So it's useless to save state when
xhci_suspend is called as part of the hibernate state. - Sarah Sharp

Signed-off-by: Todd Brandt <todd.e.brandt@xxxxxxxxxxxxxxx>

 drivers/usb/core/hcd-pci.c   | 27 ++++++++++++++++++++-------
 drivers/usb/host/xhci-pci.c  | 18 +++++++++++++++++-
 drivers/usb/host/xhci-plat.c |  2 +-
 drivers/usb/host/xhci.c      | 30 +++++++++++++++++-------------
 drivers/usb/host/xhci.h      |  2 +-
 include/linux/usb/hcd.h      |  3 +++
 6 files changed, 59 insertions(+), 23 deletions(-)

diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index dfe9d0f..1499da5 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -428,7 +428,8 @@ static int check_root_hub_suspended(struct device *dev)
 }
 
 #if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME)
-static int suspend_common(struct device *dev, bool do_wakeup)
+static int suspend_common(struct device *dev, bool do_wakeup,
+		unsigned int pm_event)
 {
 	struct pci_dev		*pci_dev = to_pci_dev(dev);
 	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);
@@ -452,9 +453,15 @@ static int suspend_common(struct device *dev, bool do_wakeup)
 		if (do_wakeup && hcd->shared_hcd &&
 				HCD_WAKEUP_PENDING(hcd->shared_hcd))
 			return -EBUSY;
-		retval = hcd->driver->pci_suspend(hcd, do_wakeup);
-		suspend_report_result(hcd->driver->pci_suspend, retval);
-
+		if ((pm_event == PM_EVENT_HIBERNATE) &&
+				hcd->driver->pci_poweroff) {
+			retval = hcd->driver->pci_poweroff(hcd);
+			suspend_report_result(hcd->driver->pci_poweroff,
+				retval);
+		} else {
+			retval = hcd->driver->pci_suspend(hcd, do_wakeup);
+			suspend_report_result(hcd->driver->pci_suspend, retval);
+		}
 		/* Check again in case wakeup raced with pci_suspend */
 		if ((retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) ||
 				(retval == 0 && do_wakeup && hcd->shared_hcd &&
@@ -532,7 +539,12 @@ static int resume_common(struct device *dev, int event)
 
 static int hcd_pci_suspend(struct device *dev)
 {
-	return suspend_common(dev, device_may_wakeup(dev));
+	return suspend_common(dev, device_may_wakeup(dev), PM_EVENT_SUSPEND);
+}
+
+static int hcd_pci_poweroff(struct device *dev)
+{
+	return suspend_common(dev, device_may_wakeup(dev), PM_EVENT_HIBERNATE);
 }
 
 static int hcd_pci_suspend_noirq(struct device *dev)
@@ -598,6 +610,7 @@ static int hcd_pci_restore(struct device *dev)
 #else
 
 #define hcd_pci_suspend		NULL
+#define hcd_pci_poweroff	NULL
 #define hcd_pci_suspend_noirq	NULL
 #define hcd_pci_resume_noirq	NULL
 #define hcd_pci_resume		NULL
@@ -611,7 +624,7 @@ static int hcd_pci_runtime_suspend(struct device *dev)
 {
 	int	retval;
 
-	retval = suspend_common(dev, true);
+	retval = suspend_common(dev, true, PM_EVENT_SUSPEND);
 	if (retval == 0)
 		powermac_set_asic(to_pci_dev(dev), 0);
 	dev_dbg(dev, "hcd_pci_runtime_suspend: %d\n", retval);
@@ -644,7 +657,7 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = {
 	.freeze_noirq	= check_root_hub_suspended,
 	.thaw_noirq	= NULL,
 	.thaw		= NULL,
-	.poweroff	= hcd_pci_suspend,
+	.poweroff	= hcd_pci_poweroff,
 	.poweroff_noirq	= hcd_pci_suspend_noirq,
 	.restore_noirq	= hcd_pci_resume_noirq,
 	.restore	= hcd_pci_restore,
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index b8dffd5..43a06a0 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -258,7 +258,22 @@ static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup)
 	if (xhci_compliance_mode_recovery_timer_quirk_check())
 		pdev->no_d3cold = true;
 
-	return xhci_suspend(xhci);
+	return xhci_suspend(xhci, PM_EVENT_SUSPEND);
+}
+
+static int xhci_pci_poweroff(struct usb_hcd *hcd)
+{
+	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
+	struct pci_dev		*pdev = to_pci_dev(hcd->self.controller);
+
+	/*
+	 * Systems with the TI redriver that loses port status change events
+	 * need to have the registers polled during D3, so avoid D3cold.
+	 */
+	if (xhci_compliance_mode_recovery_timer_quirk_check())
+		pdev->no_d3cold = true;
+
+	return xhci_suspend(xhci, PM_EVENT_HIBERNATE);
 }
 
 static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated)
@@ -311,6 +326,7 @@ static const struct hc_driver xhci_pci_hc_driver = {
 	.start =		xhci_run,
 #ifdef CONFIG_PM
 	.pci_suspend =          xhci_pci_suspend,
+	.pci_poweroff =         xhci_pci_poweroff,
 	.pci_resume =           xhci_pci_resume,
 #endif
 	.stop =			xhci_stop,
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index d9c169f..5986a70 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -203,7 +203,7 @@ static int xhci_plat_suspend(struct device *dev)
 	struct usb_hcd	*hcd = dev_get_drvdata(dev);
 	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
 
-	return xhci_suspend(xhci);
+	return xhci_suspend(xhci, PM_EVENT_SUSPEND);
 }
 
 static int xhci_plat_resume(struct device *dev)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 4265b48..a31a485 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -843,7 +843,7 @@ static void xhci_clear_command_ring(struct xhci_hcd *xhci)
  * This is called when the machine transition into S3/S4 mode.
  *
  */
-int xhci_suspend(struct xhci_hcd *xhci)
+int xhci_suspend(struct xhci_hcd *xhci, unsigned int pm_event)
 {
 	int			rc = 0;
 	unsigned int		delay = XHCI_MAX_HALT_USEC;
@@ -879,20 +879,24 @@ int xhci_suspend(struct xhci_hcd *xhci)
 		spin_unlock_irq(&xhci->lock);
 		return -ETIMEDOUT;
 	}
-	xhci_clear_command_ring(xhci);
 
-	/* step 3: save registers */
-	xhci_save_registers(xhci);
+	/* only save if we're not hibernating */
+	if (pm_event != PM_EVENT_HIBERNATE) {
+		xhci_clear_command_ring(xhci);
 
-	/* step 4: set CSS flag */
-	command = xhci_readl(xhci, &xhci->op_regs->command);
-	command |= CMD_CSS;
-	xhci_writel(xhci, command, &xhci->op_regs->command);
-	if (xhci_handshake(xhci, &xhci->op_regs->status,
-				STS_SAVE, 0, 10 * 1000)) {
-		xhci_warn(xhci, "WARN: xHC save state timeout\n");
-		spin_unlock_irq(&xhci->lock);
-		return -ETIMEDOUT;
+		/* step 3: save registers */
+		xhci_save_registers(xhci);
+
+		/* step 4: set CSS flag */
+		command = xhci_readl(xhci, &xhci->op_regs->command);
+		command |= CMD_CSS;
+		xhci_writel(xhci, command, &xhci->op_regs->command);
+		if (xhci_handshake(xhci, &xhci->op_regs->status,
+					STS_SAVE, 0, 10 * 1000)) {
+			xhci_warn(xhci, "WARN: xHC save state timeout\n");
+			spin_unlock_irq(&xhci->lock);
+			return -ETIMEDOUT;
+		}
 	}
 	spin_unlock_irq(&xhci->lock);
 
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 03c74b7..e63f5ff 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1767,7 +1767,7 @@ void xhci_shutdown(struct usb_hcd *hcd);
 int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
 
 #ifdef	CONFIG_PM
-int xhci_suspend(struct xhci_hcd *xhci);
+int xhci_suspend(struct xhci_hcd *xhci, unsigned int pm_event);
 int xhci_resume(struct xhci_hcd *xhci, bool hibernated);
 #else
 #define	xhci_suspend	NULL
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index b8aba19..b7b6d36 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -246,6 +246,9 @@ struct hc_driver {
 	/* called after suspending the hub, before entering D3 etc */
 	int	(*pci_suspend)(struct usb_hcd *hcd, bool do_wakeup);
 
+	/* called after suspending the hub, before hibernating */
+	int	(*pci_poweroff)(struct usb_hcd *hcd);
+
 	/* called after entering D0 (etc), before resuming the hub */
 	int	(*pci_resume)(struct usb_hcd *hcd, bool hibernated);
 
--
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