summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci.c
diff options
context:
space:
mode:
authorTony Camuso <tcamuso@redhat.com>2013-02-21 16:11:27 -0500
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2013-05-24 15:23:39 -0700
commit77df9e0b799b03e1d5d9c68062709af5f637e834 (patch)
tree910dee10b383bd9b65db076a7cc815438fc1f639 /drivers/usb/host/xhci.c
parent88696ae432ce7321540ac53d9caab3de9118b094 (diff)
downloadlwn-77df9e0b799b03e1d5d9c68062709af5f637e834.tar.gz
lwn-77df9e0b799b03e1d5d9c68062709af5f637e834.zip
xhci - correct comp_mode_recovery_timer on return from hibernate
Commit 71c731a2 (usb: host: xhci: Fix Compliance Mode on SN65LVPE502CP Hardware) was a workaround for systems using the SN65LVPE502CP, controller, but it introduced a bug in resume from hibernate. The fix created a timer, comp_mode_recovery_timer, which is deleted from a timer list when xhci_suspend() is called. However, the hibernate image, including the timer list containing the comp_mode_recovery_timer, had already been saved before the timer was deleted. Upon resume from hibernate, the list containing the comp_mode_recovery_timer is restored from the image saved to disk, and xhci_resume(), assuming that the timer had been deleted by xhci_suspend(), makes a call to compliance_mode_recoery_timer_init(), which creates a new instance of the comp_mode_recovery_timer and attempts to place it into the same list in which it is already active, thus corrupting the list during the list_add() call. At this point, a call trace is emitted indicating the list corruption. Soon afterward, the system locks up, the watchdog times out, and the ensuing NMI crashes the system. The problem did not occur when resuming from suspend. In suspend, the image in RAM remains exactly as it was when xhci_suspend() deleted the comp_mode_recovery_timer, so there is no problem when xhci_resume() creates a new instance of this timer and places it in the still empty list. This patch avoids the problem by deleting the timer in xhci_resume() when resuming from hibernate. Now xhci_resume() can safely make the call to create a new instance of this timer, whether returning from suspend or hibernate. Thanks to Alan Stern for his help with understanding the problem. [Sarah reworked this patch to cover the case where the xHCI restore register operation fails, and (temp & STS_SRE) is true (and we re-init the host, including re-init for the compliance mode), but hibernate is false. The original patch would have caused list corruption in this case.] This patch should be backported to kernels as old as 3.2, that contain the commit 71c731a296f1b08a3724bd1b514b64f1bda87a23 "usb: host: xhci: Fix Compliance Mode on SN65LVPE502CP Hardware" Signed-off-by: Tony Camuso <tcamuso@redhat.com> Tested-by: Tony Camuso <tcamuso@redhat.com> Acked-by: Don Zickus <dzickus@redhat.com> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Cc: stable@vger.kernel.org
Diffstat (limited to 'drivers/usb/host/xhci.c')
-rw-r--r--drivers/usb/host/xhci.c12
1 files changed, 11 insertions, 1 deletions
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index b4aa79d154b2..ae59119ed087 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -956,6 +956,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
struct usb_hcd *hcd = xhci_to_hcd(xhci);
struct usb_hcd *secondary_hcd;
int retval = 0;
+ bool comp_timer_running = false;
/* Wait a bit if either of the roothubs need to settle from the
* transition into bus suspend.
@@ -993,6 +994,13 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
/* If restore operation fails, re-initialize the HC during resume */
if ((temp & STS_SRE) || hibernated) {
+
+ if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
+ !(xhci_all_ports_seen_u0(xhci))) {
+ del_timer_sync(&xhci->comp_mode_recovery_timer);
+ xhci_dbg(xhci, "Compliance Mode Recovery Timer deleted!\n");
+ }
+
/* Let the USB core know _both_ roothubs lost power. */
usb_root_hub_lost_power(xhci->main_hcd->self.root_hub);
usb_root_hub_lost_power(xhci->shared_hcd->self.root_hub);
@@ -1035,6 +1043,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
retval = xhci_init(hcd->primary_hcd);
if (retval)
return retval;
+ comp_timer_running = true;
+
xhci_dbg(xhci, "Start the primary HCD\n");
retval = xhci_run(hcd->primary_hcd);
if (!retval) {
@@ -1076,7 +1086,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
* to suffer the Compliance Mode issue again. It doesn't matter if
* ports have entered previously to U0 before system's suspension.
*/
- if (xhci->quirks & XHCI_COMP_MODE_QUIRK)
+ if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && !comp_timer_running)
compliance_mode_recovery_timer_init(xhci);
/* Re-enable port polling. */