* [PATCH 0/2] usb: dwc3: Keep the host bus across suspend-to-idle
@ 2026-11-10 7:16 Arun Iyengar via B4 Relay
2026-11-10 7:16 ` [PATCH 1/2] usb: dwc3: Tell xhci-plat when the core lost its state on resume Arun Iyengar via B4 Relay
2026-11-10 7:16 ` [PATCH 2/2] usb: dwc3: Keep the host bus suspended, not torn down, across suspend-to-idle Arun Iyengar via B4 Relay
0 siblings, 2 replies; 3+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:16 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
In host mode, dwc3_suspend_common() tears the core down for every
system suspend unless the controller is a wakeup source
(dwc3_core_exit(): PHYs off, core reset). That has two consequences
for xhci-plat on top of it:
1. xhci_resume() is not told that the controller was reset. It writes
the saved operational registers back and asks for a state restore
(USBCMD.CRS), which fails on a freshly reset core, and only then
falls back to a reinit:
xhci-hcd xhci-hcd.1.auto: xHC error in resume, USBSTS 0x401, Reinit
xhci-plat already has xhci_plat_priv.power_lost for this (cdns3 sets
it). Patch 1 sets it from dwc3 whenever the core was re-initialised
and clears it when the core was kept for wakeup.
2. Every device behind the root ports was put into U3 by the bus
suspend and expects to keep its power session; resetting the core
ends all of them. On resume the USB core has to reset-resume every
device, which only works for devices that come back within the 2 s
usb_port_resume() waits. A SanDisk Extreme SSD and a card reader
behind a hub do not (2.4-3 s, measured): they are disconnected and
enumerated again after each resume, which kills a mounted filesystem.
Patch 2 keeps the core, its PHYs and the suspended bus across
suspend-to-idle, as the wakeup path already does and as PCI xHCI
controllers do, and marks the device as being on the awake path so a
power domain with GENPD_FLAG_ACTIVE_WAKEUP stays on. Suspend-to-RAM,
hibernation and needs_full_reinit controllers are unchanged.
Patch 2 depends on patch 1 (it clears power_lost on the kept-core path).
Both are changes to the dwc3 core; no glue driver is involved (the
RK3588 nodes bind to the "snps,dwc3" core driver directly).
Tested on an Orange Pi 5 (RK3588S), suspend-to-idle through rtcwake:
- Patch 1: 55 cycles on a kernel with patch 1 (and an ohci-platform
change sent separately); the "xHC error in resume, USBSTS 0x401"
message is gone, the controller re-initialises without the failed
restore, no other change in behaviour.
- Patch 2: 47 cycles on a kernel with both patches: every USB device
kept its device number, all 7 SuperSpeed links were back after each
resume, no "root hub lost power or was reset" and no re-enumeration
in dmesg, USB drives readable afterwards. Without patch 2 the SSD and
the card reader were disconnected and re-enumerated after every
resume and their mounted filesystems went offline.
- Not tested: suspend-to-RAM and hibernation (unchanged by this series;
the test platform has no working deep suspend), other dwc3 platforms.
This series was developed with Claude (Anthropic) as a coding assistant:
it wrote the code and the commit messages, and I reviewed and tested
every change as described above.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Arun Iyengar (2):
usb: dwc3: Tell xhci-plat when the core lost its state on resume
usb: dwc3: Keep the host bus suspended, not torn down, across suspend-to-idle
drivers/usb/dwc3/core.c | 43 +++++++++++++++++++++++++++++++++++++++----
drivers/usb/dwc3/core.h | 5 +++++
drivers/usb/dwc3/host.c | 29 ++++++++++++++++++++++++++---
3 files changed, 70 insertions(+), 7 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261110-dwc3-s2idle-6e332174c57b
Best regards,
--
Arun Iyengar <aiyengar1990@gmail.com>
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 1/2] usb: dwc3: Tell xhci-plat when the core lost its state on resume
2026-11-10 7:16 [PATCH 0/2] usb: dwc3: Keep the host bus across suspend-to-idle Arun Iyengar via B4 Relay
@ 2026-11-10 7:16 ` Arun Iyengar via B4 Relay
2026-11-10 7:16 ` [PATCH 2/2] usb: dwc3: Keep the host bus suspended, not torn down, across suspend-to-idle Arun Iyengar via B4 Relay
1 sibling, 0 replies; 3+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:16 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
In host mode without wakeup, dwc3_suspend_common() tears the core down
with dwc3_core_exit() and dwc3_resume_common() initialises it again, so
the xHCI registers are back at their reset values when xhci-plat resumes.
xhci_resume() is not told about this: it writes the saved operational
registers back and asks the controller to restore its internal state
(USBCMD.CRS), which cannot work on a freshly reset core. It fails with
USBSTS.SRE, and xhci_resume() only then falls back to a full reset:
xhci-hcd xhci-hcd.1.auto: xHC error in resume, USBSTS 0x401, Reinit
xhci-plat already has a way for the glue to report this:
xhci_plat_priv.power_lost, which cdns3 sets the same way. Set it from
dwc3_resume_common() whenever the core was re-initialised (and clear it
when it was kept running for wakeup), so xhci_resume() goes straight to
the reset and re-init path.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/usb/dwc3/core.c | 3 +++
drivers/usb/dwc3/core.h | 3 +++
drivers/usb/dwc3/host.c | 29 ++++++++++++++++++++++++++---
3 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index fd5c2cd36c59..2b31f146e43f 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -2639,8 +2639,10 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
if (ret)
return ret;
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST, true);
+ dwc3_host_resume(dwc, true);
break;
}
+ dwc3_host_resume(dwc, false);
/* Restore GUSB2PHYCFG bits that were modified in suspend */
for (i = 0; i < dwc->num_usb2_ports; i++) {
reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(i));
@@ -2672,6 +2674,7 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
dwc3_otg_init(dwc);
if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST) {
dwc3_otg_host_init(dwc);
+ dwc3_host_resume(dwc, true);
} else if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) {
dwc3_gadget_resume(dwc);
}
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index f3dabb37f806..862818e081af 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1677,11 +1677,14 @@ static inline void dwc3_pre_run_stop(struct dwc3 *dwc, bool is_on)
#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_host_init(struct dwc3 *dwc);
void dwc3_host_exit(struct dwc3 *dwc);
+void dwc3_host_resume(struct dwc3 *dwc, bool power_lost);
#else
static inline int dwc3_host_init(struct dwc3 *dwc)
{ return 0; }
static inline void dwc3_host_exit(struct dwc3 *dwc)
{ }
+static inline void dwc3_host_resume(struct dwc3 *dwc, bool power_lost)
+{ }
#endif
#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index c5674161b2b0..fb09b4f58414 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -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"
@@ -223,6 +221,31 @@ int dwc3_host_init(struct dwc3 *dwc)
}
EXPORT_SYMBOL_GPL(dwc3_host_init);
+/**
+ * dwc3_host_resume - tell xhci-plat whether the controller lost its state
+ * @dwc: pointer to our controller context structure
+ * @power_lost: true if the core was re-initialised during system resume
+ *
+ * When the core is torn down in system suspend (dwc3_core_exit()) and
+ * initialised again on resume, the xHCI registers are back at their reset
+ * values. xhci_resume() must then reset and re-initialise the host instead
+ * of trying to restore its saved register state with CRS, which fails
+ * (USBSTS.SRE) on a freshly reset controller.
+ */
+void dwc3_host_resume(struct dwc3 *dwc, bool power_lost)
+{
+ struct usb_hcd *hcd;
+
+ if (!dwc->xhci)
+ return;
+
+ hcd = platform_get_drvdata(dwc->xhci);
+ if (!hcd)
+ return;
+
+ hcd_to_xhci_priv(hcd)->power_lost = power_lost;
+}
+
void dwc3_host_exit(struct dwc3 *dwc)
{
if (dwc->sys_wakeup)
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 2/2] usb: dwc3: Keep the host bus suspended, not torn down, across suspend-to-idle
2026-11-10 7:16 [PATCH 0/2] usb: dwc3: Keep the host bus across suspend-to-idle Arun Iyengar via B4 Relay
2026-11-10 7:16 ` [PATCH 1/2] usb: dwc3: Tell xhci-plat when the core lost its state on resume Arun Iyengar via B4 Relay
@ 2026-11-10 7:16 ` Arun Iyengar via B4 Relay
1 sibling, 0 replies; 3+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:16 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
In host mode, dwc3_suspend_common() tears the core down for system
suspend (dwc3_core_exit(): PHYs off, core reset) unless the controller
is a wakeup source. By then xhci-plat has already suspended the bus:
the root ports and every device behind them are in U3/suspend and
expect to keep their power session, as the USB specification requires
of a suspended bus (Documentation/driver-api/usb/persist.rst).
Resetting the core ends all of them. On resume xhci-plat finds a reset
controller ("root hub lost power or was reset") and the USB core has
to reset-resume every device, which only works for devices that come
back within the 2 s that usb_port_resume() waits for a connection.
Devices that are cut off in U3 do not necessarily come back that fast.
On an Orange Pi 5 (RK3588S) the root port of the Type-A USB3 host is
back in Polling 110 ms after resume, but a SanDisk Extreme drive that
was suspended on it does not answer: the link goes to SS.Inactive
after ~1.1 s, the drive drops its terminations at ~2.4 s and only
connects again after ~3 s. A card reader behind a USB3 hub on the
other controller loses VBUS when the hub is reset as part of its
reset-resume and needs ~2.35 s. Both are disconnected and enumerated
again on every resume, which kills a filesystem mounted on them. With
the bus kept suspended they resume from U3 in milliseconds.
Suspend-to-idle does not power anything down behind the drivers'
back, so there is no reason to end the power sessions there. Keep the
core, its PHYs and the suspended ports, as is already done for wakeup,
and mark the device as being on the awake path so that a power domain
with GENPD_FLAG_ACTIVE_WAKEUP stays on as well. This matches what PCI
xHCI controllers do in any system sleep state that keeps auxiliary
power. Suspend-to-RAM, hibernation and controllers that need a full
reinit (needs_full_reinit) still tear the core down.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/usb/dwc3/core.c | 40 ++++++++++++++++++++++++++++++++++++----
drivers/usb/dwc3/core.h | 2 ++
2 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 2b31f146e43f..8b335ba7cde4 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -16,6 +16,7 @@
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/io.h>
@@ -2539,6 +2540,29 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
return ret;
}
+/*
+ * In host mode, system suspend suspends the bus: xhci-plat puts the root ports
+ * and with them every attached device into U3/suspend, and the USB core
+ * expects them to keep that power session (see
+ * Documentation/driver-api/usb/persist.rst). Tearing the core down here ends
+ * all power sessions: the devices then have to go through a reset-resume, and
+ * a device that takes longer than the USB core allows to come back (it has to
+ * recover its link from scratch, a hub behind the port is reset and switches
+ * its own ports off) is disconnected, e.g. with a mounted filesystem.
+ *
+ * Only do that when the core will lose its state anyway or the system goes
+ * into a state that powers it down: keep it for wakeup, and for
+ * suspend-to-idle, where nothing is powered off behind the drivers' back.
+ */
+static bool dwc3_host_core_exit_needed(struct dwc3 *dwc)
+{
+ if (dwc->needs_full_reinit)
+ return true;
+ if (device_may_wakeup(dwc->dev))
+ return false;
+ return pm_suspend_target_state != PM_SUSPEND_TO_IDLE;
+}
+
static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
{
u32 reg;
@@ -2569,12 +2593,20 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
dwc3_core_exit(dwc);
break;
case DWC3_GCTL_PRTCAP_HOST:
- if (!PMSG_IS_AUTO(msg) &&
- (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) {
+ dwc->host_core_exited = false;
+ if (!PMSG_IS_AUTO(msg) && dwc3_host_core_exit_needed(dwc)) {
dwc3_core_exit(dwc);
+ dwc->host_core_exited = true;
break;
}
+ /*
+ * The core and its PHYs stay powered to keep the suspended bus:
+ * keep the power domain on as well.
+ */
+ if (!PMSG_IS_AUTO(msg))
+ device_set_awake_path(dwc->dev);
+
/* Let controller to suspend HSPHY before PHY driver suspends */
if (dwc->dis_u2_susphy_quirk ||
dwc->dis_enblslpm_quirk) {
@@ -2633,8 +2665,8 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
dwc3_gadget_resume(dwc);
break;
case DWC3_GCTL_PRTCAP_HOST:
- if (!PMSG_IS_AUTO(msg) &&
- (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) {
+ if (!PMSG_IS_AUTO(msg) && dwc->host_core_exited) {
+ dwc->host_core_exited = false;
ret = dwc3_core_init_for_resume(dwc);
if (ret)
return ret;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 862818e081af..c5de3c7ee7ec 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1133,6 +1133,7 @@ struct dwc3_glue_ops {
* @usb3_lpm_capable: set if hadrware supports Link Power Management
* @usb2_lpm_disable: set to disable usb2 lpm for host
* @usb2_gadget_lpm_disable: set to disable usb2 lpm for gadget
+ * @host_core_exited: the host-mode core was torn down in system suspend
* @needs_full_reinit: set to indicate the core may lose power and need full
* initialization during system pm
* @disable_scramble_quirk: set if we enable the disable scramble quirk
@@ -1403,6 +1404,7 @@ struct dwc3 {
unsigned usb2_lpm_disable:1;
unsigned usb2_gadget_lpm_disable:1;
unsigned needs_full_reinit:1;
+ unsigned host_core_exited:1;
unsigned disable_scramble_quirk:1;
unsigned u2exit_lfps_quirk:1;
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-10 7:16 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 7:16 [PATCH 0/2] usb: dwc3: Keep the host bus across suspend-to-idle Arun Iyengar via B4 Relay
2026-11-10 7:16 ` [PATCH 1/2] usb: dwc3: Tell xhci-plat when the core lost its state on resume Arun Iyengar via B4 Relay
2026-11-10 7:16 ` [PATCH 2/2] usb: dwc3: Keep the host bus suspended, not torn down, across suspend-to-idle Arun Iyengar via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®