Skip to contentSkip to conversation

usb: dwc3: tell xHCI that it lost its state when the core is powered off

PATCH RESEND Superseded Joonhoe Kim 2 participants 3 messages Nov 12

Subsystems (2)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

PATCH RESEND 1/1 usb: dwc3: tell xHCI that it lost its state when the core is powered off

In host mode, dwc3_suspend_common() powers the core off for system suspend (dwc3_core_exit()) when the controller may not wake the system or needs a full re-init. The xHCI registers and the state saved with the Controller Save State command are gone then, but nothing tells
xhci-plat: xhci_resume() takes the "state intact" path, writes the saved registers back and asks the controller to restore its state (CRS).

xhci-plat has a flag for exactly this, power_lost (commit 668cc6bc1178 ("usb: host: xhci-plat: allow upper layers to signal power loss"), used by cdns3), but dwc3 never sets it.

On a Lenovo TB323FU (SM8850, DWC3 + eUSB2 PHY) with a USB 2.0 hub, a USB 3 flash drive and a Billboard device attached, and USB wakeup disabled, s2idle then broke the host controller on resume: either the controller wrote to an address that was not mapped

arm-smmu 15000000.iommu: Unhandled context fault: fsr=0x402,

    iova=0xd85e7c300, fsynr=0x3d0011, cbfrsynra=0x40, cb=13

xhci-hcd xhci-hcd.2.auto: WARNING: Host System Error usb 1-1: hub failed to enable device, error -108

or its command ring would not stop

xhci-hcd xhci-hcd.2.auto: Abort failed to stop command ring: -110 xhci-hcd xhci-hcd.2.auto: xHCI host controller not responding, assume dead xhci-hcd xhci-hcd.2.auto: HC died; cleaning up
usb 1-1: PM: **** DPM device timeout ****

  Kernel panic - not syncing: usb 1-1: unrecoverable failure

With wakeup enabled (the core stays powered) the same setup survived every cycle.

Remember that the core was powered off and hand it to xhci-plat as power_lost from a resume_quirk, so that xhci_resume() resets the controller and the devices are re-enumerated. Unlike a static XHCI_RESET_ON_RESUME this only applies to the resumes that follow a power-off; resumes with the core kept powered (wakeup enabled) and runtime resume keep the restore path.

Same kernel and setup, 30 s RTC s2idle, wakeup disabled: without this change 1/1 cycles lost the controller, with it 3/3 cycles kept all devices. Only tested on this device.

Fixes c4a5153e87fd ("usb: dwc3: core: Power-off core/PHYs on system_suspend in host mode")
Assisted-by LLM

---
Resend: no replies since 2026-09-29; unchanged, still applies to next-20261105.

Arun Iyengar's RFC for the Rockchip glue builds on this patch [1]. On an Orange Pi 5 (RK3588S), with the core torn down for suspend-to-idle, he saw no "xHC error in resume" in 10 of 10 cycles.

[1] https://lore.kernel.org/all/[email protected]/

host.c now includes ../host/xhci.h (for hcd_to_xhci_priv()) in place of the three xHCI headers it used, as cdns3's host.c does.

 drivers/usb/dwc3/core.c |  2 ++
 drivers/usb/dwc3/core.h |  3 +++
 drivers/usb/dwc3/host.c | 20 +++++++++++++++++---
 3 files changed, 22 insertions(+), 3 deletions(-)
drivers/usb/dwc3/core.c +2 -0
index fd5c2cd36c59..b5490b9a8474 100644
@@ -2572,8 +2572,10 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
 		if (!PMSG_IS_AUTO(msg) &&
 		    (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) {
 			dwc3_core_exit(dwc);
+			dwc->xhci_power_lost = 1;
 			break;
 		}
+		dwc->xhci_power_lost = 0;
 
 		/* Let controller to suspend HSPHY before PHY driver suspends */
 		if (dwc->dis_u2_susphy_quirk ||
drivers/usb/dwc3/core.h +3 -0
index 608daeb7ef10..1aa898b343e6 100644
@@ -1172,6 +1172,8 @@ struct dwc3_glue_ops {
  * @suspended: set to track suspend event due to U3/L2.
  * @susphy_state: state of DWC3_GUSB2PHYCFG_SUSPHY + DWC3_GUSB3PIPECTL_SUSPHY
  *		  before PM suspend.
+ * @xhci_power_lost: the core was powered off for system suspend in host
+ *		     mode; xHCI must not try to restore its state on resume
  * @imod_interval: set the interrupt moderation interval in 250ns
  *			increments or 0 to disable.
  * @max_cfg_eps: current max number of IN eps used across all USB configs.
@@ -1420,6 +1422,7 @@ struct dwc3 {
 	unsigned		wakeup_configured:1;
 	unsigned		suspended:1;
 	unsigned		susphy_state:1;
+	unsigned		xhci_power_lost:1;
 
 	u16			imod_interval;
 
drivers/usb/dwc3/host.c +17 -3
index c5674161b2b0..c7143a8e3e32 100644
@@ -14,9 +14,7 @@
 #include <linux/usb/hcd.h>
 #include <linux/bitfield.h>
 
-#include "../host/xhci-port.h"
-#include "../host/xhci-ext-caps.h"
-#include "../host/xhci-caps.h"
+#include "../host/xhci.h"
 #include "../host/xhci-plat.h"
 #include "core.h"
 
@@ -78,7 +76,23 @@ static void dwc3_xhci_plat_start(struct usb_hcd *hcd)
 	dwc3_enable_susphy(dwc, true);
 }
 
+/*
+ * dwc3_suspend_common() powers the core off for system suspend in host mode
+ * when the controller may not wake the system. The xHCI registers and its
+ * saved internal state are gone then: tell xhci-plat, so that xhci_resume()
+ * resets the controller instead of restoring registers and asking it to
+ * restore a state it no longer has.
+ */
+static int dwc3_xhci_resume_quirk(struct usb_hcd *hcd)
+{
+	struct dwc3 *dwc = dev_get_drvdata(hcd->self.controller->parent);
+
+	hcd_to_xhci_priv(hcd)->power_lost = dwc->xhci_power_lost;
+	return 0;
+}
+
 static const struct xhci_plat_priv dwc3_xhci_plat_quirk = {
+	.resume_quirk = dwc3_xhci_resume_quirk,
 	.plat_start = dwc3_xhci_plat_start,
 };
 

base-commit: 6375e61c01e93e35ee7acd336a689ac1fae4b509

Signature · 1 line
2.43.0
#1 lore raw
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider: - [Medium] usb: dwc3: stale xhci_power_lost after resume in dwc3_xhci_resume_quirk() - [Medium] usb: dwc3…

Thank you for your contribution! Sashiko AI review found 2 potential issues to consider:
- [Medium] usb: dwc3: stale xhci_power_lost after resume in dwc3_xhci_resume_quirk()
- [Medium] usb: dwc3: missing xhci_power_lost set for OTG host in dwc3_suspend_common()
It also noticed 1 related pre-existing issue this patch does not introduce:
- [High] usb: dwc3: missing xhci_power_lost for OTG mode in dwc3_suspend_common() [1]
--

commit 1536e0b07384ff8dbfd40131768e7772947697b4
Author: Joonhoe Kim <[email protected]>

usb: dwc3: tell xHCI that it lost its state when the core is powered off

This commit records when dwc3_suspend_common() powers off the DWC3 core during host-mode system suspend and passes that state to xhci-plat as power_lost via a resume_quirk callback.

drivers/usb/dwc3/core.c static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) · line 2578

Quoted 12 lines
drivers/usb/dwc3/core.c +2 -0
index fd5c2cd36c595..b5490b9a8474f 100644
@@ -2572,8 +2572,10 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
 		if (!PMSG_IS_AUTO(msg) &&
 		    (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) {
 			dwc3_core_exit(dwc);
+			dwc->xhci_power_lost = 1;
 			break;
 		}
+		dwc->xhci_power_lost = 0;

[Severity: Medium]
Does the DWC3_GCTL_PRTCAP_OTG case in dwc3_suspend_common() also need to set dwc->xhci_power_lost?

When the controller operates in OTG mode with the host role active (dwc->current_otg_role == DWC3_OTG_ROLE_HOST), dwc3_otg_update() registers the xhci-hcd device while dwc->current_dr_role stays DWC3_GCTL_PRTCAP_OTG. On system suspend, the DWC3_GCTL_PRTCAP_OTG branch unconditionally calls dwc3_core_exit(dwc) without setting dwc->xhci_power_lost:

drivers/usb/dwc3/core.c:dwc3_suspend_common() {
    ...
	case DWC3_GCTL_PRTCAP_OTG:
		/* do nothing during runtime_suspend */
		if (PMSG_IS_AUTO(msg))
			break;
    ...
		dwc3_otg_exit(dwc);
		dwc3_core_exit(dwc);
		break;
}

On resume, dwc3_resume_common() reinitializes the core from scratch, but dwc3_xhci_resume_quirk() still sees xhci_power_lost == 0 and xhci_resume() attempts to restore state onto the reset controller.

[Severity: High]
This problem wasn't introduced by this patch, but in OTG host mode
[1] does dwc3_suspend_common() power off the core via dwc3_core_exit() without telling xhci-plat that power was lost? When a dual-role controller in DWC3_GCTL_PRTCAP_OTG mode is acting as host, system suspend tears down the core in the DWC3_GCTL_PRTCAP_OTG branch while leaving xhci_power_lost unset, so xhci_resume() tries to restore saved registers to a reset controller and triggers unhandled IOMMU context faults or command ring timeouts.

Quoted 4 lines
drivers/usb/dwc3/host.c +0 -0
index c5674161b2b08..c7143a8e3e326 100644

[ ... ]

Quoted 8 lines
drivers/usb/dwc3/host.c +4 -0
@@ -78,7 +76,23 @@ static void dwc3_xhci_plat_start(struct usb_hcd *hcd)
 	dwc3_enable_susphy(dwc, true);
 }
 
+/*
+ * dwc3_suspend_common() powers the core off for system suspend in host mode
+ * when the controller may not wake the system. The xHCI registers and its
+ * saved internal state are gone then: tell xhci-plat, so that xhci_resume()

drivers/usb/dwc3/host.c static void dwc3_xhci_plat_start(struct usb_hcd *hcd) · line 92

Quoted 10 lines
drivers/usb/dwc3/host.c +10 -0
+ * resets the controller instead of restoring registers and asking it to
+ * restore a state it no longer has.
+ */
+static int dwc3_xhci_resume_quirk(struct usb_hcd *hcd)
+{
+	struct dwc3 *dwc = dev_get_drvdata(hcd->self.controller->parent);
+
+	hcd_to_xhci_priv(hcd)->power_lost = dwc->xhci_power_lost;
+	return 0;
+}

[Severity: Medium]
Can dwc->xhci_power_lost remain 1 after resume and cause an unnecessary xHCI reset on a later aborted suspend?

Neither dwc3_xhci_resume_quirk() nor dwc3_resume_common() clears dwc->xhci_power_lost after a power-off suspend/resume cycle. If a subsequent system suspend aborts after the child xhci-hcd device runs xhci_plat_suspend() but before the parent dwc3 device runs dwc3_suspend_common(), dpm_resume() calls dwc3_xhci_resume_quirk(), which reads the stale dwc->xhci_power_lost == 1 and forces xhci_resume() to reset the controller and re-enumerate all attached devices even though the core stayed powered.

[1] https://sashiko.dev/#/bug/linux-d6c3b577-381b-4dd2-a215-d44eec3c0bbf

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#2 lore raw
Quoted 3 lines · On Thu, 12 Nov 2026 12:00:08 +0000, [email protected] wrote:

[Severity: Medium]
Does the DWC3_GCTL_PRTCAP_OTG case in dwc3_suspend_common() also need to set dwc->xhci_power_lost?

[...]

Quoted 4 lines

[Severity: High]
This problem wasn't introduced by this patch, but in OTG host mode does dwc3_suspend_common() power off the core via dwc3_core_exit() without telling xhci-plat that power was lost?

Yes, both are the same gap. The OTG case always powers the core off for system suspend, so v2 sets the flag there too when the host role is active. OTG mode is not tested; this board switches roles through usb-role-switch and never runs in DWC3_GCTL_PRTCAP_OTG.

Quoted 3 lines

[Severity: Medium]
Can dwc->xhci_power_lost remain 1 after resume and cause an unnecessary xHCI reset on a later aborted suspend?

Yes. v2 clears the flag in the resume quirk once xhci-plat has read it.

v2: https://lore.kernel.org/all/[email protected]/

Thanks,
Joonhoe Kim