From: Mathias Nyman <mathias.nyman@linux.intel.com>
To: yiyu@yiyuzhou.io
Cc: <linux-usb@vger.kernel.org>,
regressions@lists.linux.dev, diogochaves@me.com,
michal.pecio@gmail.com, raoxu@uniontech.com, dennis@dsine.de,
gregkh@linuxfoundation.org,
Mathias Nyman <mathias.nyman@linux.intel.com>
Subject: [RFT PATCH 2/2] xhci: clean up ep0 ring after disconnect instead of leaving stale TRBs
Date: Tue, 27 Oct 2026 21:21:27 +0200 [thread overview]
Message-ID: <20261027192128.386082-3-mathias.nyman@linux.intel.com> (raw)
In-Reply-To: <20261027192128.386082-1-mathias.nyman@linux.intel.com>
Commit 042aad8d0db6 ("xhci: prevent endpoint recovery after roothub
disconnect") and commit 7e0ef4332ed9 ("xhci: avoid xHC endpoint changes
after disconnect or link error.") skip endpoint recovery if the root port
is disconnected or the link is inactive, and set EP_DROP_PENDING to keep
the endpoint stopped. TDs are given back without moving the endpoint
dequeue pointer, or turning the TRBs into no-ops, assuming the endpoint
will soon be dropped and its ring freed.
This assumption doesn't hold for the default control endpoint. If a
USB 2 device briefly disconnects during a port reset in enumeration, the
hub driver retries the reset and keeps the same usb_device, and the
xhci ep0 ring stays in use. The given back TRBs remain on the ring
between the xHC dequeue pointer and the enqueue pointer.
Reset Device fails with a context state error if the slot is in default
state, and Address Device with BSR=1 is skipped for a slot already in
default state. In this case nothing moves the ep0 dequeue pointer before
the next enumeration attempt. xHC may then process TRBs of already given
back URBs, writing data into buffers that may already be freed, or ep0
remains halted and the next control transfer times out.
Handle a halted ep0 on a disconnected port by cancelling all its TDs and
resetting it normally. The Reset Endpoint handler turns the cancelled TDs
into no-ops and issues a Set TR Dequeue Pointer command to move past
them, flushing any cached TRBs, before the TDs are given back. ep0 is
left stopped. It is not restarted while EP_DROP_PENDING is set, so the
reset, restart and stop sequence that caused the xHC to fail Stop
Endpoint commands is still avoided.
Other endpoints are still given back directly without resetting them,
but their TRBs are now turned into no-ops first. The halted TD is given
back with -EPROTO if no error status was set yet, and other TDs with
-ESHUTDOWN.
Commit message written with LLM help, code is still organic
Fixes: 042aad8d0db6 ("xhci: prevent endpoint recovery after roothub disconnect")
Fixes: 7e0ef4332ed9 ("xhci: avoid xHC endpoint changes after disconnect or link error.")
Assisted-by: LLM
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
---
drivers/usb/host/xhci-ring.c | 77 +++++++++++++++++++++++++++---------
1 file changed, 59 insertions(+), 18 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 8262ed516d37..fdc02e7d937a 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -956,6 +956,24 @@ static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_vir
}
}
+static void xhci_cancel_ring_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
+ struct xhci_ring *ring, int td_status)
+{
+ struct xhci_td *tmp_td = NULL;
+ struct xhci_td *td = NULL;
+
+ /* Add all TDs on ring to cancelled td list */
+ list_for_each_entry_safe(td, tmp_td, &ring->td_list, td_list) {
+ if (list_empty(&td->cancelled_td_list)) {
+ list_add_tail(&td->cancelled_td_list,
+ &ep->cancelled_td_list);
+ td->cancel_status = TD_DIRTY;
+ if (!td->status)
+ td->status = td_status;
+ }
+ }
+}
+
static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id,
unsigned int ep_index, enum xhci_ep_reset_type reset_type)
{
@@ -987,40 +1005,62 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
{
struct xhci_port *rhub_port = ep->vdev->rhub_port;
unsigned int slot_id = ep->vdev->slot_id;
+ bool disconnected = false;
+ struct xhci_ring *ring;
int err;
- /*
- * Avoid resetting endpoint if link is inactive or device disconnected.
- * Can cause host hang.
- * The endpoint won't restart before the device is reset or the endpoint
- * dropped, so give back all its TDs now instead of letting them time out.
- */
+ /* link is inactive or device disconnected */
if (rhub_port->link_inactive || !rhub_port->connected) {
- xhci_dbg(xhci, "Not resetting ep %u slot %u after disconnect or link error\n",
- ep->ep_index, slot_id);
ep->ep_state |= EP_DROP_PENDING;
- if (td)
- xhci_td_cleanup(xhci, td, xhci_urb_to_transfer_ring(xhci, td->urb),
- td->status ?: -EPROTO);
- xhci_kill_endpoint_urbs(xhci, slot_id, ep->ep_index);
- return -ENODEV;
+ disconnected = true;
+ if (!td) {
+ xhci_kill_endpoint_urbs(xhci, slot_id, ep->ep_index);
+ return -ENODEV;
+ }
}
- /* add td to cancelled list and let reset ep handler take care of it */
- if (reset_type == EP_HARD_RESET) {
- ep->ep_state |= EP_HARD_CLEAR_TOGGLE;
+ /* Add halted td to cancelled list and let reset ep handler take care of it */
+ if (reset_type == EP_HARD_RESET || disconnected) {
if (td && list_empty(&td->cancelled_td_list)) {
list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list);
td->cancel_status = TD_HALTED;
+ if (disconnected && !td->status)
+ td->status = -EPROTO;
}
}
+ /*
+ * Avoid resetting non-ep0 endpoints if link is inactive or device
+ * disconnected. Can cause host hang.
+ * Endpoints other than ep0 won't restart before the device is reset or
+ * the endpoint is dropped, so cancel all TDs, and give back non-ep0 TDs
+ * now instead of letting them time out. Ep0 can survive a short device
+ * disconnect during enumeration so reset ep0 normally and clean it up.
+ */
+ if (disconnected) {
+ ring = xhci_urb_to_transfer_ring(xhci, td->urb);
+ xhci_cancel_ring_tds(xhci, ep, ring, -ESHUTDOWN);
+ if (ep->ep_index != 0) {
+ xhci_dbg(xhci, "Not resetting ep %u slot %u after disconnect\n",
+ ep->ep_index, slot_id);
+
+ xhci_process_cancelled_tds(xhci, ep);
+ xhci_kill_endpoint_urbs(xhci, slot_id, ep->ep_index);
+ return -ENODEV;
+ }
+ /* hard-reset a halted disconnected ep0 */
+ reset_type = EP_HARD_RESET;
+ }
+
if (ep->ep_state & EP_HALTED) {
xhci_dbg(xhci, "Reset ep command for ep_index %d already pending\n",
ep->ep_index);
return 0;
}
+ if (reset_type == EP_HARD_RESET)
+ ep->ep_state |= EP_HARD_CLEAR_TOGGLE;
+
err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type);
if (err)
return err;
@@ -1071,8 +1111,9 @@ static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt
continue;
}
- /* device disconnected or link error, ep will be dropped */
- if (ep->ep_state & EP_DROP_PENDING) {
+ /* device disconnected or link error, non-ep0 endpoints will be dropped */
+ if (ep->ep_state & EP_DROP_PENDING && ep->ep_index != 0) {
+ td_to_noop(xhci, ep, td, false);
td->cancel_status = TD_CLEARED;
continue;
}
--
2.43.0
next prev parent reply other threads:[~2026-10-27 19:22 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 17:47 [REGRESSION] usbhid -110 timeouts and reset loop for Keychron M5 (3434:d068) at boot since v7.3 Dennis Wissel
2026-10-24 20:34 ` Yiyu Zhou
2026-10-24 20:35 ` Yiyu Zhou
2026-10-24 23:10 ` Dennis Wissel
2026-10-25 8:59 ` Thorsten Leemhuis
2026-10-26 9:28 ` Mathias Nyman
2026-10-26 9:49 ` Thorsten Leemhuis
2026-10-26 10:47 ` Greg KH
2026-10-25 10:14 ` Michal Pecio
2026-10-25 11:11 ` Dennis Wissel
2026-10-26 11:15 ` Michal Pecio
2026-10-26 20:39 ` Yiyu Zhou
2026-10-27 7:29 ` Dennis Wissel
2026-10-27 8:48 ` [PATCH] xhci: keep ep0 blocked after disconnect until Address Device reloads it Yiyu Zhou
2026-10-27 9:10 ` sashiko-bot
2026-10-27 10:36 ` Mathias Nyman
2026-10-27 11:27 ` Michal Pecio
2026-10-27 18:40 ` Yiyu Zhou
2026-10-27 19:21 ` [RFT PATCH 0/2] xhci: clean up endpoint rings after disconnect detect Mathias Nyman
2026-10-27 19:21 ` [RFT PATCH 1/2] xhci: give back all TDs when skipping endpoint reset after disconnect Mathias Nyman
2026-10-27 19:21 ` Mathias Nyman [this message]
2026-10-27 23:05 ` [RFT PATCH 0/2] xhci: clean up endpoint rings after disconnect detect Dennis Wissel
2026-10-28 7:07 ` Michal Pecio
2026-10-29 6:54 ` [PATCH 0/4] Fix problems on disconnected devices in 7.3 Michal Pecio
2026-10-29 6:54 ` [PATCH 1/4] usb: xhci: Fix TD invalidation on disconnected endpoints Michal Pecio
2026-10-29 7:12 ` sashiko-bot
2026-10-29 6:55 ` [PATCH 2/4] usb: xhci: Give back stranded URBs " Michal Pecio
2026-10-29 7:23 ` sashiko-bot
2026-10-29 6:56 ` [PATCH 3/4] usb: xhci: Give back URBs on halted " Michal Pecio
2026-10-29 7:19 ` sashiko-bot
2026-10-29 6:57 ` [PATCH 4/4] usb: xhci: Reject URBs to disconnected devices Michal Pecio
2026-10-29 7:24 ` sashiko-bot
2026-10-29 4:18 ` [RFT PATCH 0/2] xhci: clean up endpoint rings after disconnect detect Yiyu Zhou
2026-10-27 10:58 ` [REGRESSION] usbhid -110 timeouts and reset loop for Keychron M5 (3434:d068) at boot since v7.3 Michal Pecio
2026-10-26 9:18 ` Mathias Nyman
2026-10-26 9:51 ` Michal Pecio
2026-10-26 11:38 ` Michal Pecio
2026-10-26 11:54 ` Mathias Nyman
2026-10-26 18:56 ` Alan Stern
2026-10-26 18:58 ` Alan Stern
2026-10-25 6:28 ` Greg KH
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261027192128.386082-3-mathias.nyman@linux.intel.com \
--to=mathias.nyman@linux.intel.com \
--cc=dennis@dsine.de \
--cc=diogochaves@me.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-usb@vger.kernel.org \
--cc=michal.pecio@gmail.com \
--cc=raoxu@uniontech.com \
--cc=regressions@lists.linux.dev \
--cc=yiyu@yiyuzhou.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox