* [PATCH 1/9] usb: xhci: simplify CMRT initialization logic
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-27 12:34 ` [PATCH 2/9] usb: xhci: relocate Restore/Controller error check Niklas Neronin
` (7 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman
Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin, Andy Shevchenko
The function compliance_mode_recovery_timer_init() is called from
xhci_init() because the Compliance Mode Recovery Timer (CMRT) must be set
up before xhci_run() when the xhci driver is re-initialized.
To handle this case, the boolean flag 'comp_timer_running' was introduced
to track whether xhci_run() had already been called, ensuring that
xhci_resume() would not invoke compliance_mode_recovery_timer_init()
a second time.
This can be simplified by moving the 'done' label in xhci_resume() to
after the compliance_mode_recovery_timer_init() call. With this change,
the timer initialization runs only when the xhci driver has not been
re-initialized, making the 'comp_timer_running' flag unnecessary and
allowing it to be removed.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index ef6d8662adec..810905b824d3 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1084,7 +1084,6 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
u32 command, temp = 0;
struct usb_hcd *hcd = xhci_to_hcd(xhci);
int retval = 0;
- bool comp_timer_running = false;
bool pending_portevent = false;
bool suspended_usb3_devs = false;
@@ -1196,7 +1195,6 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
retval = xhci_init(hcd);
if (retval)
return retval;
- comp_timer_running = true;
xhci_dbg(xhci, "Start the primary HCD\n");
retval = xhci_run(hcd);
@@ -1265,16 +1263,16 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
usb_hcd_resume_root_hub(hcd);
}
}
-done:
+
/*
* If system is subject to the Quirk, Compliance Mode Timer needs to
* be re-initialized Always after a system resume. Ports are subject
* 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) && !comp_timer_running)
+ if (xhci->quirks & XHCI_COMP_MODE_QUIRK)
compliance_mode_recovery_timer_init(xhci);
-
+done:
if (xhci->quirks & XHCI_ASMEDIA_MODIFY_FLOWCONTROL)
usb_asmedia_modifyflowcontrol(to_pci_dev(hcd->self.controller));
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 2/9] usb: xhci: relocate Restore/Controller error check
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
2026-03-27 12:34 ` [PATCH 1/9] usb: xhci: simplify CMRT initialization logic Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-27 12:34 ` [PATCH 3/9] usb: xhci: factor out roothub bandwidth cleanup Niklas Neronin
` (6 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman
Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin, Andy Shevchenko
A Restore Error or Host Controller Error indicates that the host controller
failed to resume after suspend. In such cases, the xhci driver is fully
re-initialized, similar to a post-hibernation scenario.
The existing error check is only relevant when 'power_lost' is false.
If 'power_lost' is true, a Restore or Controller error has no effect:
no warning is printed and the 'power_lost' state remains unchanged.
Move the entire error check into the if '!power_lost' condition
to make this dependency explicit and simplify the resume logic.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 810905b824d3..a04b1365bb6a 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1140,16 +1140,13 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
spin_unlock_irq(&xhci->lock);
return -ETIMEDOUT;
}
- }
- temp = readl(&xhci->op_regs->status);
-
- /* re-initialize the HC on Restore Error, or Host Controller Error */
- if ((temp & (STS_SRE | STS_HCE)) &&
- !(xhci->xhc_state & XHCI_STATE_REMOVING)) {
- if (!power_lost)
+ /* re-initialize the HC on Restore Error, or Host Controller Error */
+ temp = readl(&xhci->op_regs->status);
+ if ((temp & (STS_SRE | STS_HCE)) && !(xhci->xhc_state & XHCI_STATE_REMOVING)) {
xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp);
- power_lost = true;
+ power_lost = true;
+ }
}
if (power_lost) {
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 3/9] usb: xhci: factor out roothub bandwidth cleanup
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
2026-03-27 12:34 ` [PATCH 1/9] usb: xhci: simplify CMRT initialization logic Niklas Neronin
2026-03-27 12:34 ` [PATCH 2/9] usb: xhci: relocate Restore/Controller error check Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-30 8:29 ` Michal Pecio
2026-03-27 12:34 ` [PATCH 4/9] usb: xhci: move reserving command ring trb Niklas Neronin
` (5 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
Introduce xhci_rh_bw_cleanup() to release all bandwidth tracking
structures associated with xHCI roothub ports.
The new helper clears:
* TT bandwidth entries
* Per-interval endpoint lists
This refactors and consolidates the existing per-port cleanup logic
previously embedded in xhci_mem_cleanup(), reducing duplication and
making the teardown sequence easier to follow.
The helper will also be reused for upcoming S4 resume handling.
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci-mem.c | 50 +++++++++++++++++++++----------------
1 file changed, 29 insertions(+), 21 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 75bc1eb98b76..d4a9dbed8f16 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -1895,10 +1895,36 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup
}
EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter);
+/* Cleanup roothub bandwidth data */
+static void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
+{
+ struct xhci_root_port_bw_info *rh_bw;
+ struct xhci_tt_bw_info *tt_info, *tt_next;
+ struct list_head *eps, *ep, *ep_next;
+
+ for (int i = 0; i < xhci->max_ports; i++) {
+ rh_bw = &xhci->rh_bw[i];
+
+ /* Clear and free all TT bandwidth entries */
+ list_for_each_entry_safe(tt_info, tt_next, &rh_bw->tts, tt_list) {
+ list_del(&tt_info->tt_list);
+ kfree(tt_info);
+ }
+
+ /* Clear per-interval endpoint lists */
+ for (int j = 0; j < XHCI_MAX_INTERVAL; j++) {
+ eps = &rh_bw->bw_table.interval_bw[j].endpoints;
+
+ list_for_each_safe(ep, ep_next, eps)
+ list_del_init(ep);
+ }
+ }
+}
+
void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
- int i, j;
+ int i;
cancel_delayed_work_sync(&xhci->cmd_timer);
@@ -1917,15 +1943,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed command ring");
xhci_cleanup_command_queue(xhci);
- for (i = 0; i < xhci->max_ports && xhci->rh_bw; i++) {
- struct xhci_interval_bw_table *bwt = &xhci->rh_bw[i].bw_table;
- for (j = 0; j < XHCI_MAX_INTERVAL; j++) {
- struct list_head *ep = &bwt->interval_bw[j].endpoints;
- while (!list_empty(ep))
- list_del_init(ep->next);
- }
- }
-
for (i = xhci->max_slots; i > 0; i--)
xhci_free_virt_devices_depth_first(xhci, i);
@@ -1959,18 +1976,9 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
scratchpad_free(xhci);
- if (!xhci->rh_bw)
- goto no_bw;
+ if (xhci->rh_bw)
+ xhci_rh_bw_cleanup(xhci);
- for (i = 0; i < xhci->max_ports; i++) {
- struct xhci_tt_bw_info *tt, *n;
- list_for_each_entry_safe(tt, n, &xhci->rh_bw[i].tts, tt_list) {
- list_del(&tt->tt_list);
- kfree(tt);
- }
- }
-
-no_bw:
xhci->cmd_ring_reserved_trbs = 0;
xhci->usb2_rhub.num_ports = 0;
xhci->usb3_rhub.num_ports = 0;
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 3/9] usb: xhci: factor out roothub bandwidth cleanup
2026-03-27 12:34 ` [PATCH 3/9] usb: xhci: factor out roothub bandwidth cleanup Niklas Neronin
@ 2026-03-30 8:29 ` Michal Pecio
0 siblings, 0 replies; 18+ messages in thread
From: Michal Pecio @ 2026-03-30 8:29 UTC (permalink / raw)
To: Niklas Neronin; +Cc: mathias.nyman, linux-usb, raoxu
On Fri, 27 Mar 2026 13:34:34 +0100, Niklas Neronin wrote:
> Introduce xhci_rh_bw_cleanup() to release all bandwidth tracking
> structures associated with xHCI roothub ports.
>
> The new helper clears:
> * TT bandwidth entries
> * Per-interval endpoint lists
>
> This refactors and consolidates the existing per-port cleanup logic
> previously embedded in xhci_mem_cleanup(), reducing duplication and
> making the teardown sequence easier to follow.
>
> The helper will also be reused for upcoming S4 resume handling.
>
> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
> ---
> drivers/usb/host/xhci-mem.c | 50 +++++++++++++++++++++----------------
> 1 file changed, 29 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
> index 75bc1eb98b76..d4a9dbed8f16 100644
> --- a/drivers/usb/host/xhci-mem.c
> +++ b/drivers/usb/host/xhci-mem.c
> @@ -1895,10 +1895,36 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup
> }
> EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter);
>
> +/* Cleanup roothub bandwidth data */
> +static void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
> +{
> + struct xhci_root_port_bw_info *rh_bw;
> + struct xhci_tt_bw_info *tt_info, *tt_next;
> + struct list_head *eps, *ep, *ep_next;
> +
> + for (int i = 0; i < xhci->max_ports; i++) {
> + rh_bw = &xhci->rh_bw[i];
> +
> + /* Clear and free all TT bandwidth entries */
> + list_for_each_entry_safe(tt_info, tt_next, &rh_bw->tts, tt_list) {
> + list_del(&tt_info->tt_list);
> + kfree(tt_info);
> + }
This loop is theoretically pointless, because each tt_info corresponds
to a hub virtual device, and all this stuff should have been destroyed
by xhci_free_virt_devices_depth_first() earlier.
If anything, it seems to paper over potential memory leaks in there.
> +
> + /* Clear per-interval endpoint lists */
> + for (int j = 0; j < XHCI_MAX_INTERVAL; j++) {
> + eps = &rh_bw->bw_table.interval_bw[j].endpoints;
> +
> + list_for_each_safe(ep, ep_next, eps)
> + list_del_init(ep);
> + }
This loop used to run before xhci_free_virt_devices_depth_first(), but
now it will run after. It seems that the endpoints here are virt_ep
which also should be gone already, so this loop likely does nothing
(empty list) or writes to virtual devices after free (somebody forgot
to unlink some endpoints from the list).
Do we trust xhci_free_virt_devices_depth_first() to work correctly?
If yes then it seems this whole function is unnecessary.
If not, perhaps delete this monstrosity and write a simpler cleanup:
1. for each slot_id
- disable debugfs
- free virt_device and child allocations but don't worry about tt_info
2. for each root hub port
- free all tt_info allocations but don't worry about eps or vdevs
And since this will be used by reset on resume:
3. zero out DCBAA and xhci->devs
4. reinitialize xhci->rb_hw
> + }
> +}
> +
> void xhci_mem_cleanup(struct xhci_hcd *xhci)
> {
> struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
> - int i, j;
> + int i;
>
> cancel_delayed_work_sync(&xhci->cmd_timer);
>
> @@ -1917,15 +1943,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
> xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed command ring");
> xhci_cleanup_command_queue(xhci);
>
> - for (i = 0; i < xhci->max_ports && xhci->rh_bw; i++) {
> - struct xhci_interval_bw_table *bwt = &xhci->rh_bw[i].bw_table;
> - for (j = 0; j < XHCI_MAX_INTERVAL; j++) {
> - struct list_head *ep = &bwt->interval_bw[j].endpoints;
> - while (!list_empty(ep))
> - list_del_init(ep->next);
> - }
> - }
> -
> for (i = xhci->max_slots; i > 0; i--)
> xhci_free_virt_devices_depth_first(xhci, i);
>
> @@ -1959,18 +1976,9 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
>
> scratchpad_free(xhci);
>
> - if (!xhci->rh_bw)
> - goto no_bw;
> + if (xhci->rh_bw)
> + xhci_rh_bw_cleanup(xhci);
>
> - for (i = 0; i < xhci->max_ports; i++) {
> - struct xhci_tt_bw_info *tt, *n;
> - list_for_each_entry_safe(tt, n, &xhci->rh_bw[i].tts, tt_list) {
> - list_del(&tt->tt_list);
> - kfree(tt);
> - }
> - }
> -
> -no_bw:
> xhci->cmd_ring_reserved_trbs = 0;
> xhci->usb2_rhub.num_ports = 0;
> xhci->usb3_rhub.num_ports = 0;
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 4/9] usb: xhci: move reserving command ring trb
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
` (2 preceding siblings ...)
2026-03-27 12:34 ` [PATCH 3/9] usb: xhci: factor out roothub bandwidth cleanup Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-27 12:34 ` [PATCH 5/9] usb: xhci: move ring initialization Niklas Neronin
` (4 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
Move the command ring TRB reservation from xhci_mem_init() to xhci_init().
Function xhci_mem_init() is intended for memory allocation,
while xhci_init() is for initialization.
This split allows xhci_init() to be reused when resuming from S4
suspend-to-disk.
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci-mem.c | 7 -------
drivers/usb/host/xhci.c | 6 ++++++
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index d4a9dbed8f16..45638ab13635 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -2485,13 +2485,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "First segment DMA is 0x%pad",
&xhci->cmd_ring->first_seg->dma);
- /*
- * Reserve one command ring TRB for disabling LPM.
- * Since the USB core grabs the shared usb_bus bandwidth mutex before
- * disabling LPM, we only need to reserve one TRB for all devices.
- */
- xhci->cmd_ring_reserved_trbs++;
-
/* Allocate and set up primary interrupter 0 with an event ring. */
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring");
xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters),
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index a04b1365bb6a..facadf0f0d1e 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -564,6 +564,12 @@ static int xhci_init(struct usb_hcd *hcd)
/* Set the Number of Device Slots Enabled to the maximum supported value */
xhci_enable_max_dev_slots(xhci);
+ /*
+ * Reserve one command ring TRB for disabling LPM.
+ * Since the USB core grabs the shared usb_bus bandwidth mutex before
+ * disabling LPM, we only need to reserve one TRB for all devices.
+ */
+ xhci->cmd_ring_reserved_trbs = 1;
/* Set the address in the Command Ring Control register */
xhci_set_cmd_ring_deq(xhci);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 5/9] usb: xhci: move ring initialization
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
` (3 preceding siblings ...)
2026-03-27 12:34 ` [PATCH 4/9] usb: xhci: move reserving command ring trb Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-30 8:42 ` Michal Pecio
2026-03-27 12:34 ` [PATCH 6/9] usb: xhci: move initialization for lifetime objects Niklas Neronin
` (3 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
Move ring initialization from xhci_ring_alloc() to xhci_ring_init().
Call xhci_ring_init() after xhci_ring_alloc(); in the future,
it can also be used to re-initialize the ring during resume.
Additionally, remove xhci_dbg_trace() from xhci_mem_init(). The command
ring's first segment DMA address is now printed during the trace call in
xhci_ring_init().
This refactoring lays also the groundwork for eventually replacing:
* xhci_dbc_ring_init()
* xhci_clear_command_ring()
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci-mem.c | 19 ++++++++++++++-----
drivers/usb/host/xhci.c | 3 +++
drivers/usb/host/xhci.h | 1 +
3 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 45638ab13635..ca4463eebc49 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -129,6 +129,13 @@ static void xhci_initialize_ring_segments(struct xhci_hcd *xhci, struct xhci_rin
ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= cpu_to_le32(LINK_TOGGLE);
}
+void xhci_ring_init(struct xhci_hcd *xhci, struct xhci_ring *ring)
+{
+ xhci_initialize_ring_segments(xhci, ring);
+ xhci_initialize_ring_info(ring);
+ trace_xhci_ring_alloc(ring);
+}
+
/*
* Link the src ring segments to the dst ring.
* Set Toggle Cycle for the new ring if needed.
@@ -389,9 +396,6 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs,
if (ret)
goto fail;
- xhci_initialize_ring_segments(xhci, ring);
- xhci_initialize_ring_info(ring);
- trace_xhci_ring_alloc(ring);
return ring;
fail:
@@ -668,6 +672,8 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
cur_ring = stream_info->stream_rings[cur_stream];
if (!cur_ring)
goto cleanup_rings;
+
+ xhci_ring_init(xhci, cur_ring);
cur_ring->stream_id = cur_stream;
cur_ring->trb_address_map = &stream_info->trb_address_map;
/* Set deq ptr, cycle bit, and stream context type */
@@ -1011,6 +1017,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
if (!dev->eps[0].ring)
goto fail;
+ xhci_ring_init(xhci, dev->eps[0].ring);
+
dev->udev = udev;
/* Point to output device context in dcbaa. */
@@ -1492,6 +1500,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
virt_dev->eps[ep_index].skip = false;
ep_ring = virt_dev->eps[ep_index].new_ring;
+ xhci_ring_init(xhci, ep_ring);
/* Fill the endpoint context */
ep_ctx->ep_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_HI(max_esit_payload) |
@@ -2370,6 +2379,8 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs,
if (!ir)
return NULL;
+ xhci_ring_init(xhci, ir->event_ring);
+
spin_lock_irq(&xhci->lock);
if (!intr_num) {
/* Find available secondary interrupter, interrupter 0 is reserved for primary */
@@ -2482,8 +2493,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
goto fail;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocated command ring at %p", xhci->cmd_ring);
- xhci_dbg_trace(xhci, trace_xhci_dbg_init, "First segment DMA is 0x%pad",
- &xhci->cmd_ring->first_seg->dma);
/* Allocate and set up primary interrupter 0 with an event ring. */
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring");
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index facadf0f0d1e..170615dd1e93 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -564,6 +564,8 @@ static int xhci_init(struct usb_hcd *hcd)
/* Set the Number of Device Slots Enabled to the maximum supported value */
xhci_enable_max_dev_slots(xhci);
+ /* Initialize the Command ring */
+ xhci_ring_init(xhci, xhci->cmd_ring);
/*
* Reserve one command ring TRB for disabling LPM.
* Since the USB core grabs the shared usb_bus bandwidth mutex before
@@ -583,6 +585,7 @@ static int xhci_init(struct usb_hcd *hcd)
xhci_set_dev_notifications(xhci);
/* Initialize the Primary interrupter */
+ xhci_ring_init(xhci, xhci->interrupters[0]->event_ring);
xhci_add_interrupter(xhci, 0);
xhci->interrupters[0]->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 2b0796f6d00e..ade0198bf9ea 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1823,6 +1823,7 @@ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
unsigned int num_trbs, gfp_t flags);
void xhci_initialize_ring_info(struct xhci_ring *ring);
+void xhci_ring_init(struct xhci_hcd *xhci, struct xhci_ring *ring);
void xhci_free_endpoint_ring(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev,
unsigned int ep_index);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 5/9] usb: xhci: move ring initialization
2026-03-27 12:34 ` [PATCH 5/9] usb: xhci: move ring initialization Niklas Neronin
@ 2026-03-30 8:42 ` Michal Pecio
2026-03-30 8:53 ` Neronin, Niklas
0 siblings, 1 reply; 18+ messages in thread
From: Michal Pecio @ 2026-03-30 8:42 UTC (permalink / raw)
To: Niklas Neronin; +Cc: mathias.nyman, linux-usb, raoxu
On Fri, 27 Mar 2026 13:34:36 +0100, Niklas Neronin wrote:
> Move ring initialization from xhci_ring_alloc() to xhci_ring_init().
> Call xhci_ring_init() after xhci_ring_alloc();
This adds more code and more opportunities for bugs.
Can't ring_alloc() just call ring_init() itself?
> in the future it can also be used to re-initialize the ring during
> resume.
Yes, but it seems we don't have the opposite problem: there is no
need to allocate rings but never initialize them.
>
> Additionally, remove xhci_dbg_trace() from xhci_mem_init(). The
> command ring's first segment DMA address is now printed during the
> trace call in xhci_ring_init().
>
> This refactoring lays also the groundwork for eventually replacing:
> * xhci_dbc_ring_init()
> * xhci_clear_command_ring()
Or xhci_clear_command_ring() could just call memset() and ring_init(),
instead of duplicating this sequence in every place which needs it.
>
> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 5/9] usb: xhci: move ring initialization
2026-03-30 8:42 ` Michal Pecio
@ 2026-03-30 8:53 ` Neronin, Niklas
0 siblings, 0 replies; 18+ messages in thread
From: Neronin, Niklas @ 2026-03-30 8:53 UTC (permalink / raw)
To: Michal Pecio; +Cc: mathias.nyman, linux-usb, raoxu
On 30/03/2026 11.42, Michal Pecio wrote:
> On Fri, 27 Mar 2026 13:34:36 +0100, Niklas Neronin wrote:
>> Move ring initialization from xhci_ring_alloc() to xhci_ring_init().
>> Call xhci_ring_init() after xhci_ring_alloc();
>
> This adds more code and more opportunities for bugs.
> Can't ring_alloc() just call ring_init() itself?
Sure, but the naming would not be accurate.
ring_create() or ring_alloc_and_init() would be accurate.
>
>> in the future it can also be used to re-initialize the ring during
>> resume.
>
> Yes, but it seems we don't have the opposite problem: there is no
> need to allocate rings but never initialize them.
Technically there is, during ring linking. The "new" ring does not
need to be initialized, as it will be initialized according to the
"dst" rings values.
This is not implemented in this patch series, but I planned to do it
later.
>
>>
>> Additionally, remove xhci_dbg_trace() from xhci_mem_init(). The
>> command ring's first segment DMA address is now printed during the
>> trace call in xhci_ring_init().
>>
>> This refactoring lays also the groundwork for eventually replacing:
>> * xhci_dbc_ring_init()
>> * xhci_clear_command_ring()
>
> Or xhci_clear_command_ring() could just call memset() and ring_init(),
> instead of duplicating this sequence in every place which needs it.
xhci_clear_command_ring() clears the command ring specifically.
The idea is to have (naming not set in stone):
ring_alloc() <- allocate necessary ring memory
ring_free() <- free all ring memory
ring_init() <- initialize necessary ring memory
ring_reset() <- resets the ring
The reader can then easily understand what each function does.
>
>>
>> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 6/9] usb: xhci: move initialization for lifetime objects
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
` (4 preceding siblings ...)
2026-03-27 12:34 ` [PATCH 5/9] usb: xhci: move ring initialization Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-30 8:49 ` Michal Pecio
2026-03-27 12:34 ` [PATCH 7/9] usb: xhci: split core allocation and initialization Niklas Neronin
` (2 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
Initialize objects that exist for the lifetime of the driver only once,
rather than repeatedly. These objects do not require re-initialization
after events such as S4 (suspend-to-disk).
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci-mem.c | 1 -
drivers/usb/host/xhci.c | 15 ++++++++-------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index ca4463eebc49..2cd6111c9707 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -2009,7 +2009,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci->port_caps = NULL;
xhci->interrupters = NULL;
- xhci->page_size = 0;
xhci->usb2_rhub.bus_state.bus_suspended = 0;
xhci->usb3_rhub.bus_state.bus_suspended = 0;
}
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 170615dd1e93..4e811a2668e6 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -549,13 +549,6 @@ static int xhci_init(struct usb_hcd *hcd)
int retval;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
- spin_lock_init(&xhci->lock);
-
- INIT_LIST_HEAD(&xhci->cmd_list);
- INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout);
- init_completion(&xhci->cmd_ring_stop_completion);
- xhci_hcd_page_size(xhci);
- memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
retval = xhci_mem_init(xhci, GFP_KERNEL);
if (retval)
@@ -5525,6 +5518,14 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
}
+ spin_lock_init(&xhci->lock);
+ INIT_LIST_HEAD(&xhci->cmd_list);
+ INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout);
+ init_completion(&xhci->cmd_ring_stop_completion);
+ xhci_hcd_page_size(xhci);
+
+ memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
+
xhci_dbg(xhci, "Calling HCD init\n");
/* Initialize HCD and host controller data structures. */
retval = xhci_init(hcd);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 6/9] usb: xhci: move initialization for lifetime objects
2026-03-27 12:34 ` [PATCH 6/9] usb: xhci: move initialization for lifetime objects Niklas Neronin
@ 2026-03-30 8:49 ` Michal Pecio
0 siblings, 0 replies; 18+ messages in thread
From: Michal Pecio @ 2026-03-30 8:49 UTC (permalink / raw)
To: Niklas Neronin; +Cc: mathias.nyman, linux-usb, raoxu
On Fri, 27 Mar 2026 13:34:37 +0100, Niklas Neronin wrote:
> Initialize objects that exist for the lifetime of the driver only once,
> rather than repeatedly. These objects do not require re-initialization
> after events such as S4 (suspend-to-disk).
>
> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
> ---
> drivers/usb/host/xhci-mem.c | 1 -
> drivers/usb/host/xhci.c | 15 ++++++++-------
> 2 files changed, 8 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
> index ca4463eebc49..2cd6111c9707 100644
> --- a/drivers/usb/host/xhci-mem.c
> +++ b/drivers/usb/host/xhci-mem.c
> @@ -2009,7 +2009,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
> xhci->port_caps = NULL;
> xhci->interrupters = NULL;
>
> - xhci->page_size = 0;
> xhci->usb2_rhub.bus_state.bus_suspended = 0;
> xhci->usb3_rhub.bus_state.bus_suspended = 0;
> }
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index 170615dd1e93..4e811a2668e6 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -549,13 +549,6 @@ static int xhci_init(struct usb_hcd *hcd)
> int retval;
>
> xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
> - spin_lock_init(&xhci->lock);
> -
> - INIT_LIST_HEAD(&xhci->cmd_list);
> - INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout);
> - init_completion(&xhci->cmd_ring_stop_completion);
> - xhci_hcd_page_size(xhci);
> - memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
>
> retval = xhci_mem_init(xhci, GFP_KERNEL);
> if (retval)
> @@ -5525,6 +5518,14 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
> dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
> }
>
> + spin_lock_init(&xhci->lock);
> + INIT_LIST_HEAD(&xhci->cmd_list);
> + INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout);
> + init_completion(&xhci->cmd_ring_stop_completion);
> + xhci_hcd_page_size(xhci);
> +
> + memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
This seems to do nothing because we just got a freshly kzalloc'd
xhci_hcd from __usb_create_hcd(), as far as I understand.
On the other hand, doing it in xhci_init() used to cover for the
possibility of xhci_mem_cleanup() failing to fully clear xhci->dev.
So same questions:
Do we trust xhci_free_virt_devices_depth_first() to do its job?
Does it need to become more robust?
> xhci_dbg(xhci, "Calling HCD init\n");
> /* Initialize HCD and host controller data structures. */
> retval = xhci_init(hcd);
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 7/9] usb: xhci: split core allocation and initialization
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
` (5 preceding siblings ...)
2026-03-27 12:34 ` [PATCH 6/9] usb: xhci: move initialization for lifetime objects Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-30 8:57 ` Michal Pecio
2026-03-27 12:34 ` [PATCH 8/9] usb: xhci: improve debug messages during suspend Niklas Neronin
2026-03-27 12:34 ` [PATCH 9/9] usb: xhci: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
8 siblings, 1 reply; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
Separate allocation and initialization in the xHCI core:
* xhci_mem_init() now only handles memory allocation.
* xhci_init() now only handles initialization.
This split allows xhci_init() to be reused when resuming from S4
suspend-to-disk.
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci-mem.c | 3 +++
drivers/usb/host/xhci.c | 30 ++++++++++--------------------
2 files changed, 13 insertions(+), 20 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 2cd6111c9707..f1b4f06d4b8b 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -2421,6 +2421,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
dma_addr_t dma;
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
+
/*
* xHCI section 5.4.6 - Device Context array must be
* "physically contiguous and 64-byte (cache line) aligned".
@@ -2510,6 +2512,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
if (xhci_setup_port_arrays(xhci, flags))
goto fail;
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__);
return 0;
fail:
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 4e811a2668e6..658419eb6827 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -536,24 +536,13 @@ static void xhci_set_dev_notifications(struct xhci_hcd *xhci)
writel(dev_notf, &xhci->op_regs->dev_notification);
}
-/*
- * Initialize memory for HCD and xHC (one-time init).
- *
- * Program the PAGESIZE register, initialize the device context array, create
- * device contexts (?), set up a command ring segment (or two?), create event
- * ring (one for now).
- */
-static int xhci_init(struct usb_hcd *hcd)
+/* Setup basic xHCI registers */
+static void xhci_init(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
- int retval;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
- retval = xhci_mem_init(xhci, GFP_KERNEL);
- if (retval)
- return retval;
-
/* Set the Number of Device Slots Enabled to the maximum supported value */
xhci_enable_max_dev_slots(xhci);
@@ -589,7 +578,6 @@ static int xhci_init(struct usb_hcd *hcd)
}
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__);
- return 0;
}
/*-------------------------------------------------------------------------*/
@@ -1190,11 +1178,12 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
* first with the primary HCD, and then with the secondary HCD.
* If we don't do the same, the host will never be started.
*/
- xhci_dbg(xhci, "Initialize the xhci_hcd\n");
- retval = xhci_init(hcd);
+ retval = xhci_mem_init(xhci, GFP_KERNEL);
if (retval)
return retval;
+ xhci_init(hcd);
+
xhci_dbg(xhci, "Start the primary HCD\n");
retval = xhci_run(hcd);
if (!retval && xhci->shared_hcd) {
@@ -5526,12 +5515,13 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
- xhci_dbg(xhci, "Calling HCD init\n");
- /* Initialize HCD and host controller data structures. */
- retval = xhci_init(hcd);
+ /* Allocate xHCI data structures */
+ retval = xhci_mem_init(xhci, GFP_KERNEL);
if (retval)
return retval;
- xhci_dbg(xhci, "Called HCD init\n");
+
+ /* Initialize HCD and host controller data structures */
+ xhci_init(hcd);
if (xhci_hcd_is_usb3(hcd))
xhci_hcd_init_usb3_data(xhci, hcd);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 7/9] usb: xhci: split core allocation and initialization
2026-03-27 12:34 ` [PATCH 7/9] usb: xhci: split core allocation and initialization Niklas Neronin
@ 2026-03-30 8:57 ` Michal Pecio
0 siblings, 0 replies; 18+ messages in thread
From: Michal Pecio @ 2026-03-30 8:57 UTC (permalink / raw)
To: Niklas Neronin; +Cc: mathias.nyman, linux-usb, raoxu
On Fri, 27 Mar 2026 13:34:38 +0100, Niklas Neronin wrote:
> Separate allocation and initialization in the xHCI core:
> * xhci_mem_init() now only handles memory allocation.
> * xhci_init() now only handles initialization.
>
> This split allows xhci_init() to be reused when resuming from S4
> suspend-to-disk.
>
> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
> ---
> drivers/usb/host/xhci-mem.c | 3 +++
> drivers/usb/host/xhci.c | 30 ++++++++++--------------------
> 2 files changed, 13 insertions(+), 20 deletions(-)
>
> diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
> index 2cd6111c9707..f1b4f06d4b8b 100644
> --- a/drivers/usb/host/xhci-mem.c
> +++ b/drivers/usb/host/xhci-mem.c
> @@ -2421,6 +2421,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
> struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
> dma_addr_t dma;
>
> + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
> +
> /*
> * xHCI section 5.4.6 - Device Context array must be
> * "physically contiguous and 64-byte (cache line) aligned".
> @@ -2510,6 +2512,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
> if (xhci_setup_port_arrays(xhci, flags))
> goto fail;
>
> + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__);
Patch looks OK, but I don't think there is a need to log this. The
function is completely boring, just some memory allocations while the
controller is halted and nothing happens.
Maybe it doesn't even make sense to log the beginning either. We know
which functions call this and when, we also know that they will return
ENOMEM if this fails.
> return 0;
>
> fail:
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index 4e811a2668e6..658419eb6827 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -536,24 +536,13 @@ static void xhci_set_dev_notifications(struct xhci_hcd *xhci)
> writel(dev_notf, &xhci->op_regs->dev_notification);
> }
>
> -/*
> - * Initialize memory for HCD and xHC (one-time init).
> - *
> - * Program the PAGESIZE register, initialize the device context array, create
> - * device contexts (?), set up a command ring segment (or two?), create event
> - * ring (one for now).
> - */
> -static int xhci_init(struct usb_hcd *hcd)
> +/* Setup basic xHCI registers */
> +static void xhci_init(struct usb_hcd *hcd)
> {
> struct xhci_hcd *xhci = hcd_to_xhci(hcd);
> - int retval;
>
> xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
>
> - retval = xhci_mem_init(xhci, GFP_KERNEL);
> - if (retval)
> - return retval;
> -
> /* Set the Number of Device Slots Enabled to the maximum supported value */
> xhci_enable_max_dev_slots(xhci);
>
> @@ -589,7 +578,6 @@ static int xhci_init(struct usb_hcd *hcd)
> }
>
> xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__);
> - return 0;
> }
>
> /*-------------------------------------------------------------------------*/
> @@ -1190,11 +1178,12 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
> * first with the primary HCD, and then with the secondary HCD.
> * If we don't do the same, the host will never be started.
> */
> - xhci_dbg(xhci, "Initialize the xhci_hcd\n");
> - retval = xhci_init(hcd);
> + retval = xhci_mem_init(xhci, GFP_KERNEL);
> if (retval)
> return retval;
>
> + xhci_init(hcd);
> +
> xhci_dbg(xhci, "Start the primary HCD\n");
> retval = xhci_run(hcd);
> if (!retval && xhci->shared_hcd) {
> @@ -5526,12 +5515,13 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
>
> memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
>
> - xhci_dbg(xhci, "Calling HCD init\n");
> - /* Initialize HCD and host controller data structures. */
> - retval = xhci_init(hcd);
> + /* Allocate xHCI data structures */
> + retval = xhci_mem_init(xhci, GFP_KERNEL);
> if (retval)
> return retval;
> - xhci_dbg(xhci, "Called HCD init\n");
> +
> + /* Initialize HCD and host controller data structures */
> + xhci_init(hcd);
>
> if (xhci_hcd_is_usb3(hcd))
> xhci_hcd_init_usb3_data(xhci, hcd);
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 8/9] usb: xhci: improve debug messages during suspend
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
` (6 preceding siblings ...)
2026-03-27 12:34 ` [PATCH 7/9] usb: xhci: split core allocation and initialization Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-30 9:14 ` Michal Pecio
2026-03-27 12:34 ` [PATCH 9/9] usb: xhci: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
8 siblings, 1 reply; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
Improve debug output for suspend failures, particularly when the controller
handshake does not complete. This will become important as upcoming patches
significantly rework the resume path, making more detailed suspend-side
messages valuable for debugging.
Add an explicit check of the Save/Restore Error (SRE) flag after a
successful Save State (CSS) operation. The xHCI specification
(note in section 4.23.2) states:
"After a Save or Restore State operation completes, the
Save/Restore Error (SRE) flag in USBSTS should be checked to
ensure the operation completed successfully."
Currently, the SRE error is only observed and warning is printed.
This patch does not introduce deeper error handling, as the correct
response is unclear and changes to suspend behavior may risk regressions
once the resume path is updated.
Additionally, simplify and clean up the suspend USBSTS CSS/SSS
handling code, improving readability and quirk handling for AMD
SNPS xHC controllers that occasionally do not clear the SSS bit.
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci.c | 65 +++++++++++++++++++++++------------------
1 file changed, 37 insertions(+), 28 deletions(-)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 658419eb6827..232e6143ac4b 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -957,11 +957,11 @@ static bool xhci_pending_portevent(struct xhci_hcd *xhci)
*/
int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
{
- int rc = 0;
+ int err;
unsigned int delay = XHCI_MAX_HALT_USEC * 2;
struct usb_hcd *hcd = xhci_to_hcd(xhci);
u32 command;
- u32 res;
+ u32 usbsts;
if (!hcd->state)
return 0;
@@ -1007,11 +1007,10 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
/* Some chips from Fresco Logic need an extraordinary delay */
delay *= (xhci->quirks & XHCI_SLOW_SUSPEND) ? 10 : 1;
- if (xhci_handshake(&xhci->op_regs->status,
- STS_HALT, STS_HALT, delay)) {
- xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n");
- spin_unlock_irq(&xhci->lock);
- return -ETIMEDOUT;
+ err = xhci_handshake(&xhci->op_regs->status, STS_HALT, STS_HALT, delay);
+ if (err) {
+ xhci_warn(xhci, "Clearing Run/Stop bit failed %d\n", err);
+ goto handshake_error;
}
xhci_clear_command_ring(xhci);
@@ -1022,28 +1021,34 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
command = readl(&xhci->op_regs->command);
command |= CMD_CSS;
writel(command, &xhci->op_regs->command);
+
+ err = xhci_handshake(&xhci->op_regs->status, STS_SAVE, 0, 20 * USEC_PER_MSEC);
+ usbsts = readl(&xhci->op_regs->status);
xhci->broken_suspend = 0;
- if (xhci_handshake(&xhci->op_regs->status,
- STS_SAVE, 0, 20 * 1000)) {
- /*
- * AMD SNPS xHC 3.0 occasionally does not clear the
- * SSS bit of USBSTS and when driver tries to poll
- * to see if the xHC clears BIT(8) which never happens
- * and driver assumes that controller is not responding
- * and times out. To workaround this, its good to check
- * if SRE and HCE bits are not set (as per xhci
- * Section 5.4.2) and bypass the timeout.
- */
- res = readl(&xhci->op_regs->status);
- if ((xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND) &&
- (((res & STS_SRE) == 0) &&
- ((res & STS_HCE) == 0))) {
- xhci->broken_suspend = 1;
- } else {
- xhci_warn(xhci, "WARN: xHC save state timeout\n");
- spin_unlock_irq(&xhci->lock);
- return -ETIMEDOUT;
+ if (err) {
+ /*
+ * AMD SNPS xHC 3.0 occasionally does not clear the
+ * SSS bit of USBSTS and when driver tries to poll
+ * to see if the xHC clears BIT(8) which never happens
+ * and driver assumes that controller is not responding
+ * and times out. To workaround this, its good to check
+ * if SRE and HCE bits are not set (as per xhci
+ * Section 5.4.2) and bypass the timeout.
+ */
+ if (!(xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND)) {
+ xhci_warn(xhci, "Controller Save State failed %d\n", err);
+ goto handshake_error;
}
+
+ if (usbsts & (STS_SRE | STS_HCE)) {
+ xhci_warn(xhci, "Controller Save State failed, USBSTS 0x%08x\n", usbsts);
+ goto handshake_error;
+ }
+
+ xhci_dbg(xhci, "SNPS broken suspend, save state unreliable\n");
+ xhci->broken_suspend = 1;
+ } else if (usbsts & STS_SRE) {
+ xhci_warn(xhci, "Suspend Save Error (SRE), USBSTS 0x%08x\n", usbsts);
}
spin_unlock_irq(&xhci->lock);
@@ -1059,7 +1064,11 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
__func__);
}
- return rc;
+ return 0;
+
+handshake_error:
+ spin_unlock_irq(&xhci->lock);
+ return -ETIMEDOUT;
}
EXPORT_SYMBOL_GPL(xhci_suspend);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 8/9] usb: xhci: improve debug messages during suspend
2026-03-27 12:34 ` [PATCH 8/9] usb: xhci: improve debug messages during suspend Niklas Neronin
@ 2026-03-30 9:14 ` Michal Pecio
2026-03-30 11:44 ` Neronin, Niklas
0 siblings, 1 reply; 18+ messages in thread
From: Michal Pecio @ 2026-03-30 9:14 UTC (permalink / raw)
To: Niklas Neronin; +Cc: mathias.nyman, linux-usb, raoxu
On Fri, 27 Mar 2026 13:34:39 +0100, Niklas Neronin wrote:
> Improve debug output for suspend failures, particularly when the
> controller handshake does not complete. This will become important as
> upcoming patches significantly rework the resume path, making more
> detailed suspend-side messages valuable for debugging.
As an aside, I think that if this series will cause any problems,
it will be some stale data structures surviving after resume, not
anything going wrong here.
> Add an explicit check of the Save/Restore Error (SRE) flag after a
> successful Save State (CSS) operation. The xHCI specification
> (note in section 4.23.2) states:
>
> "After a Save or Restore State operation completes, the
> Save/Restore Error (SRE) flag in USBSTS should be checked to
> ensure the operation completed successfully."
>
> Currently, the SRE error is only observed and warning is printed.
> This patch does not introduce deeper error handling, as the correct
> response is unclear and changes to suspend behavior may risk
> regressions once the resume path is updated.
I think patch 10/9 should add setting xhci->broken_suspend if this is
detected. It's ridiculous to try State Restore after State Save error.
At best, it should fail. At worst, it might not fail...
>
> Additionally, simplify and clean up the suspend USBSTS CSS/SSS
> handling code, improving readability and quirk handling for AMD
> SNPS xHC controllers that occasionally do not clear the SSS bit.
>
> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
> ---
> drivers/usb/host/xhci.c | 65 +++++++++++++++++++++++------------------
> 1 file changed, 37 insertions(+), 28 deletions(-)
>
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index 658419eb6827..232e6143ac4b 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -957,11 +957,11 @@ static bool xhci_pending_portevent(struct xhci_hcd *xhci)
> */
> int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
> {
> - int rc = 0;
> + int err;
> unsigned int delay = XHCI_MAX_HALT_USEC * 2;
> struct usb_hcd *hcd = xhci_to_hcd(xhci);
> u32 command;
> - u32 res;
> + u32 usbsts;
>
> if (!hcd->state)
> return 0;
> @@ -1007,11 +1007,10 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
> /* Some chips from Fresco Logic need an extraordinary delay */
> delay *= (xhci->quirks & XHCI_SLOW_SUSPEND) ? 10 : 1;
>
> - if (xhci_handshake(&xhci->op_regs->status,
> - STS_HALT, STS_HALT, delay)) {
> - xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n");
> - spin_unlock_irq(&xhci->lock);
> - return -ETIMEDOUT;
> + err = xhci_handshake(&xhci->op_regs->status, STS_HALT, STS_HALT, delay);
> + if (err) {
> + xhci_warn(xhci, "Clearing Run/Stop bit failed %d\n", err);
> + goto handshake_error;
> }
> xhci_clear_command_ring(xhci);
>
> @@ -1022,28 +1021,34 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
> command = readl(&xhci->op_regs->command);
> command |= CMD_CSS;
> writel(command, &xhci->op_regs->command);
> +
> + err = xhci_handshake(&xhci->op_regs->status, STS_SAVE, 0, 20 * USEC_PER_MSEC);
> + usbsts = readl(&xhci->op_regs->status);
> xhci->broken_suspend = 0;
> - if (xhci_handshake(&xhci->op_regs->status,
> - STS_SAVE, 0, 20 * 1000)) {
> - /*
> - * AMD SNPS xHC 3.0 occasionally does not clear the
> - * SSS bit of USBSTS and when driver tries to poll
> - * to see if the xHC clears BIT(8) which never happens
> - * and driver assumes that controller is not responding
> - * and times out. To workaround this, its good to check
> - * if SRE and HCE bits are not set (as per xhci
> - * Section 5.4.2) and bypass the timeout.
> - */
> - res = readl(&xhci->op_regs->status);
> - if ((xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND) &&
> - (((res & STS_SRE) == 0) &&
> - ((res & STS_HCE) == 0))) {
> - xhci->broken_suspend = 1;
> - } else {
> - xhci_warn(xhci, "WARN: xHC save state timeout\n");
> - spin_unlock_irq(&xhci->lock);
> - return -ETIMEDOUT;
> + if (err) {
> + /*
> + * AMD SNPS xHC 3.0 occasionally does not clear the
> + * SSS bit of USBSTS and when driver tries to poll
> + * to see if the xHC clears BIT(8) which never happens
> + * and driver assumes that controller is not responding
> + * and times out. To workaround this, its good to check
> + * if SRE and HCE bits are not set (as per xhci
> + * Section 5.4.2) and bypass the timeout.
> + */
> + if (!(xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND)) {
> + xhci_warn(xhci, "Controller Save State failed %d\n", err);
> + goto handshake_error;
> }
> +
> + if (usbsts & (STS_SRE | STS_HCE)) {
> + xhci_warn(xhci, "Controller Save State failed, USBSTS 0x%08x\n", usbsts);
> + goto handshake_error;
> + }
> +
> + xhci_dbg(xhci, "SNPS broken suspend, save state unreliable\n");
> + xhci->broken_suspend = 1;
> + } else if (usbsts & STS_SRE) {
> + xhci_warn(xhci, "Suspend Save Error (SRE), USBSTS 0x%08x\n", usbsts);
> }
This is a bit complicated and those warns are almost identical.
Would it be a problem to simply:
if (SRE | HCE)
xhci_warn("Save State error USBSTS %x")
// patch 10/9: set broken_suspend here
// patch 11/9: add HSE to this list
if (err)
if (quirk)
xhci->broken_suspend = 1;
else
xhci_warn("Save State timeout")
goto handshake_error
This may sometimes print both warnings, but not a big deal.
> spin_unlock_irq(&xhci->lock);
>
> @@ -1059,7 +1064,11 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
> __func__);
> }
>
> - return rc;
> + return 0;
> +
> +handshake_error:
> + spin_unlock_irq(&xhci->lock);
> + return -ETIMEDOUT;
> }
> EXPORT_SYMBOL_GPL(xhci_suspend);
>
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 8/9] usb: xhci: improve debug messages during suspend
2026-03-30 9:14 ` Michal Pecio
@ 2026-03-30 11:44 ` Neronin, Niklas
0 siblings, 0 replies; 18+ messages in thread
From: Neronin, Niklas @ 2026-03-30 11:44 UTC (permalink / raw)
To: Michal Pecio; +Cc: mathias.nyman, linux-usb, raoxu
On 30/03/2026 12.14, Michal Pecio wrote:
>>
>> Currently, the SRE error is only observed and warning is printed.
>> This patch does not introduce deeper error handling, as the correct
>> response is unclear and changes to suspend behavior may risk
>> regressions once the resume path is updated.
>
> I think patch 10/9 should add setting xhci->broken_suspend if this is
> detected. It's ridiculous to try State Restore after State Save error.
> At best, it should fail. At worst, it might not fail...
I plan to follow‑up with a cleanup/optimization patch after this patch set.
(Forgot to mention this in the cover letter)
The primary goal of this initial patch set is narrowly scoped:
to change resume handling from a full teardown and reinitialization,
to a minimal reset‑and‑restore flow. This has a higher risk of triggering
issues on some hardware.
Because of that, I deliberately avoided refactoring or optimizing
unrelated xhci code in this first step. Keeping changes minimal to helps
isolate resume issues, if they arise :)
Once this new resume path has had broader testing and any regressions
are resolved, I intend to follow up with cleanup and optimization patches,
where the surrounding code can be refactored with less risk of obscuring
resume memory state issues.
Thanks,
Niklas
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 9/9] usb: xhci: optimize resuming from S4 (suspend-to-disk)
2026-03-27 12:34 [PATCH 0/9] xhci: usb: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
` (7 preceding siblings ...)
2026-03-27 12:34 ` [PATCH 8/9] usb: xhci: improve debug messages during suspend Niklas Neronin
@ 2026-03-27 12:34 ` Niklas Neronin
2026-03-30 9:45 ` Michal Pecio
8 siblings, 1 reply; 18+ messages in thread
From: Niklas Neronin @ 2026-03-27 12:34 UTC (permalink / raw)
To: mathias.nyman; +Cc: linux-usb, raoxu, michal.pecio, Niklas Neronin
On resume from S4 (power loss after suspend/hibernation), the xHCI
driver previously freed, reallocated, and fully reinitialized all
data structures. Most of this is unnecessary because the data is
restored from a saved image; only the xHCI registers lose their values.
This patch optimizes S4 resume by performing only a host controller
reset, which includes:
* Freeing or clearing runtime-created data.
* Rewriting xHCI registers.
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
drivers/usb/host/xhci-mem.c | 4 +--
drivers/usb/host/xhci.c | 53 ++++++++++++++++++++++---------------
drivers/usb/host/xhci.h | 2 ++
3 files changed, 35 insertions(+), 24 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index f1b4f06d4b8b..4156822eb000 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -936,7 +936,7 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev,
* that tt_info, then free the child first. Recursive.
* We can't rely on udev at this point to find child-parent relationships.
*/
-static void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id)
+void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id)
{
struct xhci_virt_device *vdev;
struct list_head *tt_list_head;
@@ -1905,7 +1905,7 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup
EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter);
/* Cleanup roothub bandwidth data */
-static void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
+void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
{
struct xhci_root_port_bw_info *rh_bw;
struct xhci_tt_bw_info *tt_info, *tt_next;
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 232e6143ac4b..8fb2b91fc0cc 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1082,9 +1082,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
{
u32 command, temp = 0;
struct usb_hcd *hcd = xhci_to_hcd(xhci);
+ struct xhci_segment *seg;
int retval = 0;
bool pending_portevent = false;
bool suspended_usb3_devs = false;
+ bool reset_registers = false;
if (!hcd->state)
return 0;
@@ -1103,10 +1105,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
spin_lock_irq(&xhci->lock);
- if (xhci->quirks & XHCI_RESET_ON_RESUME || xhci->broken_suspend)
- power_lost = true;
-
- if (!power_lost) {
+ if (power_lost || xhci->broken_suspend || xhci->quirks & XHCI_RESET_ON_RESUME) {
+ xhci_dbg(xhci, "HC state lost, performing host controller reset\n");
+ reset_registers = true;
+ } else {
+ xhci_dbg(xhci, "HC state intact, continuing without reset\n");
/*
* Some controllers might lose power during suspend, so wait
* for controller not ready bit to clear, just as in xHC init.
@@ -1144,11 +1147,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
temp = readl(&xhci->op_regs->status);
if ((temp & (STS_SRE | STS_HCE)) && !(xhci->xhc_state & XHCI_STATE_REMOVING)) {
xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp);
- power_lost = true;
+ reset_registers = true;
}
}
- if (power_lost) {
+ if (reset_registers) {
if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
!(xhci_all_ports_seen_u0(xhci))) {
timer_delete_sync(&xhci->comp_mode_recovery_timer);
@@ -1172,27 +1175,33 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
if (retval)
return retval;
- xhci_dbg(xhci, "// Disabling event ring interrupts\n");
- temp = readl(&xhci->op_regs->status);
- writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
- xhci_disable_interrupter(xhci, xhci->interrupters[0]);
+ cancel_delayed_work_sync(&xhci->cmd_timer);
+
+ /* Delete all remaining commands */
+ xhci_cleanup_command_queue(xhci);
+
+ /* Clear data which is re-initilized during runtime */
+ xhci_for_each_ring_seg(xhci->interrupters[0]->event_ring->first_seg, seg)
+ memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT);
+
+ for (int i = xhci->max_slots; i > 0; i--)
+ xhci_free_virt_devices_depth_first(xhci, i);
+
+ xhci_rh_bw_cleanup(xhci);
+
+ xhci->cmd_ring_reserved_trbs = 0;
+ xhci_for_each_ring_seg(xhci->cmd_ring->first_seg, seg)
+ memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT);
- xhci_dbg(xhci, "cleaning up memory\n");
- xhci_mem_cleanup(xhci);
xhci_debugfs_exit(xhci);
- xhci_dbg(xhci, "xhci_stop completed - status = %x\n",
- readl(&xhci->op_regs->status));
-
- /* USB core calls the PCI reinit and start functions twice:
- * first with the primary HCD, and then with the secondary HCD.
- * If we don't do the same, the host will never be started.
- */
- retval = xhci_mem_init(xhci, GFP_KERNEL);
- if (retval)
- return retval;
xhci_init(hcd);
+ /*
+ * USB core calls the PCI reinit and start functions twice:
+ * first with the primary HCD, and then with the secondary HCD.
+ * If we don't do the same, the host will never be started.
+ */
xhci_dbg(xhci, "Start the primary HCD\n");
retval = xhci_run(hcd);
if (!retval && xhci->shared_hcd) {
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index ade0198bf9ea..a76e183515b3 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1792,6 +1792,7 @@ void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *),
void xhci_mem_cleanup(struct xhci_hcd *xhci);
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags);
void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev, int slot_id);
+void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id);
int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags);
int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev);
void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci,
@@ -1803,6 +1804,7 @@ void xhci_update_tt_active_eps(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev,
int old_active_eps);
void xhci_clear_endpoint_bw_info(struct xhci_bw_info *bw_info);
+void xhci_rh_bw_cleanup(struct xhci_hcd *xhci);
void xhci_update_bw_info(struct xhci_hcd *xhci,
struct xhci_container_ctx *in_ctx,
struct xhci_input_control_ctx *ctrl_ctx,
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 9/9] usb: xhci: optimize resuming from S4 (suspend-to-disk)
2026-03-27 12:34 ` [PATCH 9/9] usb: xhci: optimize resuming from S4 (suspend-to-disk) Niklas Neronin
@ 2026-03-30 9:45 ` Michal Pecio
0 siblings, 0 replies; 18+ messages in thread
From: Michal Pecio @ 2026-03-30 9:45 UTC (permalink / raw)
To: Niklas Neronin; +Cc: mathias.nyman, linux-usb, raoxu
On Fri, 27 Mar 2026 13:34:40 +0100, Niklas Neronin wrote:
> On resume from S4 (power loss after suspend/hibernation), the xHCI
> driver previously freed, reallocated, and fully reinitialized all
> data structures. Most of this is unnecessary because the data is
> restored from a saved image; only the xHCI registers lose their
> values.
... and all runtime information must be wiped from data structures.
That's the tricky part here.
>
> This patch optimizes S4 resume by performing only a host controller
> reset, which includes:
> * Freeing or clearing runtime-created data.
> * Rewriting xHCI registers.
>
> Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
> ---
> drivers/usb/host/xhci-mem.c | 4 +--
> drivers/usb/host/xhci.c | 53 ++++++++++++++++++++++---------------
> drivers/usb/host/xhci.h | 2 ++
> 3 files changed, 35 insertions(+), 24 deletions(-)
>
> diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
> index f1b4f06d4b8b..4156822eb000 100644
> --- a/drivers/usb/host/xhci-mem.c
> +++ b/drivers/usb/host/xhci-mem.c
> @@ -936,7 +936,7 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev,
> * that tt_info, then free the child first. Recursive.
> * We can't rely on udev at this point to find child-parent relationships.
> */
> -static void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id)
> +void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id)
> {
> struct xhci_virt_device *vdev;
> struct list_head *tt_list_head;
> @@ -1905,7 +1905,7 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup
> EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter);
>
> /* Cleanup roothub bandwidth data */
> -static void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
> +void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
> {
> struct xhci_root_port_bw_info *rh_bw;
> struct xhci_tt_bw_info *tt_info, *tt_next;
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index 232e6143ac4b..8fb2b91fc0cc 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -1082,9 +1082,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
> {
> u32 command, temp = 0;
> struct usb_hcd *hcd = xhci_to_hcd(xhci);
> + struct xhci_segment *seg;
> int retval = 0;
> bool pending_portevent = false;
> bool suspended_usb3_devs = false;
> + bool reset_registers = false;
>
> if (!hcd->state)
> return 0;
> @@ -1103,10 +1105,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
>
> spin_lock_irq(&xhci->lock);
>
> - if (xhci->quirks & XHCI_RESET_ON_RESUME || xhci->broken_suspend)
> - power_lost = true;
> -
> - if (!power_lost) {
> + if (power_lost || xhci->broken_suspend || xhci->quirks & XHCI_RESET_ON_RESUME) {
> + xhci_dbg(xhci, "HC state lost, performing host controller reset\n");
> + reset_registers = true;
> + } else {
> + xhci_dbg(xhci, "HC state intact, continuing without reset\n");
Maybe not a bad idea to log this function, but it could simply print
the value of reset_registers instead of two separate xhci_dbg().
And maybe also print power_lost and broken_suspend separately.
> /*
> * Some controllers might lose power during suspend, so wait
> * for controller not ready bit to clear, just as in xHC init.
> @@ -1144,11 +1147,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
> temp = readl(&xhci->op_regs->status);
> if ((temp & (STS_SRE | STS_HCE)) && !(xhci->xhc_state & XHCI_STATE_REMOVING)) {
> xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp);
> - power_lost = true;
> + reset_registers = true;
> }
> }
>
> - if (power_lost) {
> + if (reset_registers) {
> if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
> !(xhci_all_ports_seen_u0(xhci))) {
> timer_delete_sync(&xhci->comp_mode_recovery_timer);
> @@ -1172,27 +1175,33 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
> if (retval)
> return retval;
It's suspicious that xhci_halt() is called earlier without checking
retval. If it fails then xhci_reset() returns success and this check
passes and it seems that nothing good will happen.
No sure if the HC can be running here?
>
> - xhci_dbg(xhci, "// Disabling event ring interrupts\n");
> - temp = readl(&xhci->op_regs->status);
> - writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
> - xhci_disable_interrupter(xhci, xhci->interrupters[0]);
> + cancel_delayed_work_sync(&xhci->cmd_timer);
> +
> + /* Delete all remaining commands */
> + xhci_cleanup_command_queue(xhci);
Considering that xhci_suspend() clears the command ring anyway, it
could probably do this too so we don't need to.
BTW, debugfs/port_bandwidth interface queues commands and I'm not sure
if it's synchronized in any way with power management. IOW, it might be
possible that command are pending at suspend, but I'm not sure.
> +
> + /* Clear data which is re-initilized during runtime */
> + xhci_for_each_ring_seg(xhci->interrupters[0]->event_ring->first_seg, seg)
> + memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT);
> +
> + for (int i = xhci->max_slots; i > 0; i--)
> + xhci_free_virt_devices_depth_first(xhci, i);
This loop is a bit ugly, it could be a function in xhci-mem.
> +
> + xhci_rh_bw_cleanup(xhci);
> +
> + xhci->cmd_ring_reserved_trbs = 0;
> + xhci_for_each_ring_seg(xhci->cmd_ring->first_seg, seg)
> + memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT);
This looks like a bug because it nukes link TRBs. I know that
xhci_init() will fix this up (unless somebody changes that without
updating here), but it looks confusing.
I think calling clear_command_ring() was clearer and less bug prone.
>
> - xhci_dbg(xhci, "cleaning up memory\n");
> - xhci_mem_cleanup(xhci);
> xhci_debugfs_exit(xhci);
Interesting, it looks like there used to be a window of time when all
memory was freed but debugfs files still existed? Odd...
> - xhci_dbg(xhci, "xhci_stop completed - status = %x\n",
> - readl(&xhci->op_regs->status));
> -
> - /* USB core calls the PCI reinit and start functions twice:
> - * first with the primary HCD, and then with the secondary HCD.
> - * If we don't do the same, the host will never be started.
> - */
> - retval = xhci_mem_init(xhci, GFP_KERNEL);
> - if (retval)
> - return retval;
>
> xhci_init(hcd);
>
> + /*
> + * USB core calls the PCI reinit and start functions twice:
> + * first with the primary HCD, and then with the secondary HCD.
> + * If we don't do the same, the host will never be started.
> + */
> xhci_dbg(xhci, "Start the primary HCD\n");
> retval = xhci_run(hcd);
> if (!retval && xhci->shared_hcd) {
> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> index ade0198bf9ea..a76e183515b3 100644
> --- a/drivers/usb/host/xhci.h
> +++ b/drivers/usb/host/xhci.h
> @@ -1792,6 +1792,7 @@ void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *),
> void xhci_mem_cleanup(struct xhci_hcd *xhci);
> int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags);
> void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev, int slot_id);
> +void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id);
> int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags);
> int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev);
> void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci,
> @@ -1803,6 +1804,7 @@ void xhci_update_tt_active_eps(struct xhci_hcd *xhci,
> struct xhci_virt_device *virt_dev,
> int old_active_eps);
> void xhci_clear_endpoint_bw_info(struct xhci_bw_info *bw_info);
> +void xhci_rh_bw_cleanup(struct xhci_hcd *xhci);
> void xhci_update_bw_info(struct xhci_hcd *xhci,
> struct xhci_container_ctx *in_ctx,
> struct xhci_input_control_ctx *ctrl_ctx,
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread