* [PATCH] xhci: keep ep0 blocked after disconnect until Address Device reloads it
[not found] <qLQr_bD2TzCrwVMFdRe7Wg@dsine.de>
@ 2026-10-27 8:48 ` Yiyu Zhou
2026-10-27 10:36 ` Mathias Nyman
0 siblings, 1 reply; 3+ messages in thread
From: Yiyu Zhou @ 2026-10-27 8:48 UTC (permalink / raw)
To: mathias.nyman, gregkh
Cc: Yiyu Zhou, linux-usb, linux-kernel, michal.pecio, regressions,
Dennis Wissel
After a root port disconnect or link error, xhci_handle_halted_endpoint()
sets EP_DROP_PENDING instead of resetting the endpoint, and gives back
its TDs. Their TRBs stay on the ring, and the endpoint's dequeue
pointer still points at the halted TD.
If the device comes back before hub_wq handles the disconnect, a driver
may reset it on the same slot. usbhid does this after 1 s of
interrupt-in errors. xhci_discover_or_reset_device() then clears
EP_DROP_PENDING on every endpoint. The other endpoints are disabled by
Reset Device and get new rings, but ep0 keeps its ring and dequeue
pointer. For USB 2 devices, usbcore by default sends a 64 byte
GET_DESCRIPTOR before SET_ADDRESS, and so before Address Device reloads
the ep0 context. That request is queued behind the stale TRBs. With a
Keychron M5 the reset stalled for 11 s, and the xHC then ran the old
TDs ("Event dma ... for ep 0 ... not part of TD"), writing into buffers
of URBs that had already been given back.
Keep EP_DROP_PENDING set on ep0 through Reset Device, and clear it when
an Address Device command succeeds. That command loads the ep0 context
from the input context, whose dequeue pointer is our enqueue pointer, so
the xHC skips the stale TRBs. Until then, ep0 requests fail with
-ENODEV. If Address Device comes first, as for SuperSpeed devices, the
device recovers in place. Otherwise the reset fails quickly, and
usbcore re-enumerates the device on a new slot, as after a real
disconnect. The same applies when Reset Device fails because the slot
was never addressed: the next enumeration attempt uses a new slot
instead of the stale ring.
Fixes: 042aad8d0db6 ("xhci: prevent endpoint recovery after roothub disconnect")
Fixes: 7e0ef4332ed9 ("xhci: avoid xHC endpoint changes after disconnect or link error.")
Reported-by: Dennis Wissel <dennis@dsine.de>
Closes: https://lore.kernel.org/linux-usb/4MjB9K-2SoiA85WbI9iEig@dsine.de/
Tested-by: Dennis Wissel <dennis@dsine.de>
Signed-off-by: Yiyu Zhou <yiyu@yiyuzhou.io>
---
This is the RFC from the regression thread [1], unchanged apart from
Dennis's Tested-by. It applies on top of 1f36cc9fa949 ("xhci: give
back all TDs when skipping endpoint reset after disconnect") in
for-usb-linus and depends on it: without that patch, ep0 requests are
not rejected while EP_DROP_PENDING is set, so they would wait for their
timeouts. 7.3 needs both, so they should probably go to stable
together.
I used an LLM to trace the code and draft the patch.
[1] https://lore.kernel.org/linux-usb/20261026203944.2235991-1-yiyu@yiyuzhou.io/
drivers/usb/host/xhci.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index fa3012bf4c0f..15f4ee4aa2c3 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -4091,7 +4091,13 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd,
ret = 0;
command_cleanup:
- for (i = 0; i < EP_CTX_PER_DEV; i++)
+ /*
+ * Reset Device leaves the ep0 ring and dequeue pointer as they were.
+ * After a disconnect or link error, ep0 may still point at TRBs of TDs
+ * that were given back, so keep it blocked until Address Device
+ * reloads its context.
+ */
+ for (i = 1; i < EP_CTX_PER_DEV; i++)
virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING;
xhci_free_command(xhci, reset_device_cmd);
@@ -4463,6 +4469,16 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
}
if (ret)
goto out;
+
+ /*
+ * Address Device loaded the ep0 context from the input context, so
+ * the xHC now starts at our enqueue pointer, past any TRBs of TDs
+ * given back after a disconnect or link error. Unblock ep0.
+ */
+ spin_lock_irqsave(&xhci->lock, flags);
+ virt_dev->eps[0].ep_state &= ~EP_DROP_PENDING;
+ spin_unlock_irqrestore(&xhci->lock, flags);
+
temp_64 = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr);
xhci_dbg_trace(xhci, trace_xhci_dbg_address,
"Op regs DCBAA ptr = %#016llx", temp_64);
base-commit: 1f36cc9fa9496189e95fe5762cde8a74e0cd9d68
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] xhci: keep ep0 blocked after disconnect until Address Device reloads it
2026-10-27 8:48 ` [PATCH] xhci: keep ep0 blocked after disconnect until Address Device reloads it Yiyu Zhou
@ 2026-10-27 10:36 ` Mathias Nyman
2026-10-27 11:27 ` Michal Pecio
0 siblings, 1 reply; 3+ messages in thread
From: Mathias Nyman @ 2026-10-27 10:36 UTC (permalink / raw)
To: Yiyu Zhou, mathias.nyman, gregkh
Cc: linux-usb, linux-kernel, michal.pecio, regressions, Dennis Wissel
On 10/27/26 10:48, Yiyu Zhou wrote:
> After a root port disconnect or link error, xhci_handle_halted_endpoint()
> sets EP_DROP_PENDING instead of resetting the endpoint, and gives back
> its TDs. Their TRBs stay on the ring, and the endpoint's dequeue
> pointer still points at the halted TD.
>
> If the device comes back before hub_wq handles the disconnect, a driver
> may reset it on the same slot. usbhid does this after 1 s of
> interrupt-in errors. xhci_discover_or_reset_device() then clears
> EP_DROP_PENDING on every endpoint. The other endpoints are disabled by
> Reset Device and get new rings, but ep0 keeps its ring and dequeue
> pointer. For USB 2 devices, usbcore by default sends a 64 byte
> GET_DESCRIPTOR before SET_ADDRESS, and so before Address Device reloads
> the ep0 context. That request is queued behind the stale TRBs. With a
> Keychron M5 the reset stalled for 11 s, and the xHC then ran the old
> TDs ("Event dma ... for ep 0 ... not part of TD"), writing into buffers
> of URBs that had already been given back.
>
> Keep EP_DROP_PENDING set on ep0 through Reset Device, and clear it when
> an Address Device command succeeds. That command loads the ep0 context
> from the input context, whose dequeue pointer is our enqueue pointer, so
> the xHC skips the stale TRBs. Until then, ep0 requests fail with
> -ENODEV. If Address Device comes first, as for SuperSpeed devices, the
> device recovers in place. Otherwise the reset fails quickly, and
> usbcore re-enumerates the device on a new slot, as after a real
> disconnect. The same applies when Reset Device fails because the slot
> was never addressed: the next enumeration attempt uses a new slot
> instead of the stale ring.
Thanks for the detailed description, this looks like a very targeted fix
for this specific hid case.
It still leaves the stale TRBs on ep0 ring, and relies on a successful
address device command to clear the EP_DROP_PENDING flag, blocking ep0
in all other scenarios. It also leaves xHC internal ep0 in the
'halted' state. Not sure if xHC internal halted ep0 state is cleared during
'reset device' or 'address device' command.
There are cases where EP_DROP_PENDING won't be cleared at all in this patch.
For example if slot is already in the default state when hci_setup_device()
is called. (slot is in default state after reset device command)
I'm thinking we should take a step backwards first when fixing this regression,
and then look if we can tune fine tune ep0 after that.
1. Make sure All TDs are turned to no-op before returning the URBs in the disconnect
case.
Add all TDs in ring to cancelled list, turn them no-op, and then give them back.
2. Treat ep0 differently, cancel all TDs on ring as above, but still reset it when halted.
Allow a Set TR deq pointer command as well, and only give back ep0 URBs after this.
This partially reverts the two patches that caused this issue, and clears the ep0
halted state
3. Possibly keep EP_DROP_PENDING flag for ep0 until xhci_setup_device() as this patch
does, but always clear it in xhci_setup_device() before it returns
Let me know if you find any flaws with this approach.
Thanks
Mathias
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] xhci: keep ep0 blocked after disconnect until Address Device reloads it
2026-10-27 10:36 ` Mathias Nyman
@ 2026-10-27 11:27 ` Michal Pecio
0 siblings, 0 replies; 3+ messages in thread
From: Michal Pecio @ 2026-10-27 11:27 UTC (permalink / raw)
To: Mathias Nyman
Cc: Yiyu Zhou, mathias.nyman, gregkh, linux-usb, linux-kernel,
regressions, Dennis Wissel
On Tue, 27 Oct 2026 12:36:26 +0200, Mathias Nyman wrote:
> Thanks for the detailed description, this looks like a very targeted fix
> for this specific hid case.
>
> It still leaves the stale TRBs on ep0 ring, and relies on a successful
> address device command to clear the EP_DROP_PENDING flag, blocking ep0
> in all other scenarios. It also leaves xHC internal ep0 in the
> 'halted' state. Not sure if xHC internal halted ep0 state is cleared during
> 'reset device' or 'address device' command.
Addres Device is not suitable, unless preceded by Drop/Enable slot.
Reset Device:
For the Default Control Endpoint the xHC shall terminate any USB
activity, abort any pending events not already posted to an Event
Ring, and transition the endpoint to the Running state.
I see no mention of "halted" in all of 4.6.11. Logically, the spec
seems to assume that SW can use this command when EP0 is running and
busy, so it can transition to halted while the command is pending,
so the xHC "should" be able to deal with that.
We know how dependable xHCI hardware is in practice, but actually this
might work because IIRC Windows also doesn't bother resetting endpoints
of disconnected devices. I haven't studied this very thoroughly, but
I recall noticing that it queries PORTSC before resetting endpoints
and it violates the spec by dropping halted endpoints, just like we do.
> I'm thinking we should take a step backwards first when fixing this
> regression, and then look if we can tune fine tune ep0 after that.
>
> 1. Make sure All TDs are turned to no-op before returning the URBs in
> the disconnect case.
>
> Add all TDs in ring to cancelled list, turn them no-op, and then
> give them back.
Seems helpful and harmless, doesn't account for potential TRB caching.
> 2. Treat ep0 differently, cancel all TDs on ring as above, but still
> reset it when halted. Allow a Set TR deq pointer command as well,
> and only give back ep0 URBs after this. This partially reverts the
> two patches that caused this issue, and clears the ep0 halted state
Sensible, if Reset EP/Set TR Deq on EP0 of a disconnected USB3 device
don't trigger HW bugs. These commands only change some EP Ctx fields
and don't submit or abort USB transfers, so they may be safe. It's even
possible that they are safe on all endpoints, but who knows.
I don't think this will cause any issuess on Renesas, even generalized
to all endpoints, and I still have the HW and ability to test.
We could use some input from the author of b8c3b718087b ("usb: xhci:
Don't try to recover an endpoint if port is in error state.") about
those issues, whether they affected EP0 or not :)
> 3. Possibly keep EP_DROP_PENDING flag for ep0 until
> xhci_setup_device() as this patch does, but always clear it in
> xhci_setup_device() before it returns
Does this result in URBs failing to submit before the device is
addressed, causing USB core to drop and enumerate again? That part
does seem a little dodgy. We may agree or disagree with core's
policies, but they are what they are, and not an accidental bug.
Doesn't seem suitable for 7.3.
Regards,
Michal
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-27 11:27 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <qLQr_bD2TzCrwVMFdRe7Wg@dsine.de>
2026-10-27 8:48 ` [PATCH] xhci: keep ep0 blocked after disconnect until Address Device reloads it Yiyu Zhou
2026-10-27 10:36 ` Mathias Nyman
2026-10-27 11:27 ` Michal Pecio
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®