From: Mathias Nyman <mathias.nyman@linux.intel.com>
To: <linux-usb@vger.kernel.org>
Cc: <stern@rowland.harvard.edu>,
Thinh.Nguyen@synopsys.com, michal.pecio@gmail.com,
oneukum@suse.com, niklas.neronin@linux.intel.com,
Mathias Nyman <mathias.nyman@linux.intel.com>
Subject: [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error
Date: Mon, 23 Mar 2026 14:25:11 +0200 [thread overview]
Message-ID: <20260323122512.2019893-2-mathias.nyman@linux.intel.com> (raw)
In-Reply-To: <20260323122512.2019893-1-mathias.nyman@linux.intel.com>
Avoid automatically restarting bulk or interrupt transfers after a
URB is given back due to stall or error.
Introduce a 'TD_TAINTED' state for pending TDs queued on a endpoint when
it halted. The actual TD the endpoint halted on is marked TD_HALTED,
and its URB is given back with proper EPROTO or EPIPE error code.
Don't automatically restart an endpoint if the next queued TD after
the TD_HALTED one is marked tainted.
class driver is expected to cancel all those pending URBs before
submitting more URBs, or before clearing halt, but is not forced to.
Always start the endpoint when a new URB is queued, but print a debug
message if there are still tainted URBs left uncancelled.
The same code is used for clearing up cached TDs from xHC in both
the URB stall/error case as when a class driver cancels URBs.
In the cancel case endpoint is restarted after removing the cancelled
URB.
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
---
drivers/usb/host/xhci-ring.c | 48 ++++++++++++++++++++++++++++++------
drivers/usb/host/xhci.c | 7 ++++++
drivers/usb/host/xhci.h | 4 ++-
3 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 1cbefee3c4ca..f01f343a7e37 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -981,6 +981,31 @@ static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id,
return ret;
}
+/*
+ * Mark the TD the endpoint halted on as TD_HALTED and add it to the cancelled
+ * td list, ensuring invalidate_cancelled_tds() clears the TD from xHC cache.
+ * Mark the other TDs on that bulk or interrupt endpoint as TD_TAINTED to
+ * prevent the ring from being restarted too early.
+ */
+
+static void xhci_cancel_halted_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
+ struct xhci_td *td)
+{
+ struct xhci_ring *ring;
+ struct xhci_td *tdi;
+
+ ring = xhci_urb_to_transfer_ring(xhci, td->urb);
+
+ td->cancel_status = TD_HALTED;
+ if (list_empty(&td->cancelled_td_list))
+ list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list);
+
+ list_for_each_entry(tdi, &ring->td_list, td_list) {
+ if (!tdi->cancel_status && ring->type != TYPE_CTRL)
+ tdi->cancel_status = TD_TAINTED;
+ }
+}
+
static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
struct xhci_virt_ep *ep,
struct xhci_td *td,
@@ -999,10 +1024,8 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
/* 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;
- 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 (td)
+ xhci_cancel_halted_tds(xhci, ep, td);
}
if (ep->ep_state & EP_HALTED) {
@@ -1079,6 +1102,7 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
case TD_CLEARED: /* TD is already no-op */
case TD_CLEARING_CACHE: /* set TR deq command already queued */
break;
+ case TD_TAINTED:
case TD_DIRTY: /* TD is cached, clear it */
case TD_HALTED:
case TD_CLEARING_CACHE_DEFERRED:
@@ -1406,6 +1430,14 @@ void xhci_hc_died(struct xhci_hcd *xhci)
usb_hc_died(xhci_to_hcd(xhci));
}
+bool next_td_is_tainted(struct xhci_ring *ring)
+{
+ struct xhci_td *td;
+
+ td = list_first_entry_or_null(&ring->td_list, struct xhci_td, td_list);
+ return td && td->cancel_status == TD_TAINTED;
+}
+
/*
* When we get a completion for a Set Transfer Ring Dequeue Pointer command,
* we need to clear the set deq pending flag in the endpoint ring state, so that
@@ -1543,13 +1575,15 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
__func__);
xhci_invalidate_cancelled_tds(ep);
/* Try to restart the endpoint if all is done */
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
+ if (!next_td_is_tainted(ep_ring))
+ ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
/* Start giving back any TDs invalidated above */
xhci_giveback_invalidated_tds(ep);
} else {
/* Restart any rings with pending URBs */
- xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__);
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
+ xhci_dbg(ep->xhci, "%s: All cancelled TDs cleared\n", __func__);
+ if (!next_td_is_tainted(ep_ring))
+ ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
}
}
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index ef6d8662adec..8f417836ae1f 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1625,6 +1625,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
unsigned long flags;
int ret = 0;
unsigned int slot_id, ep_index;
+ struct xhci_ring *ring;
unsigned int *ep_state;
struct urb_priv *urb_priv;
int num_tds;
@@ -1661,6 +1662,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
}
slot_id = urb->dev->slot_id;
+ ring = xhci_urb_to_transfer_ring(xhci, urb);
if (!HCD_HW_ACCESSIBLE(hcd)) {
ret = -ESHUTDOWN;
@@ -1694,6 +1696,10 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
goto free_priv;
}
+ ring = xhci_urb_to_transfer_ring(xhci, urb);
+ if (ring && next_td_is_tainted(ring))
+ xhci_dbg(xhci, "WARN, enqueue URB without canceling old tainted URBs\n");
+
switch (usb_endpoint_type(&urb->ep->desc)) {
case USB_ENDPOINT_XFER_CONTROL:
@@ -3354,6 +3360,7 @@ static void xhci_endpoint_reset(struct usb_hcd *hcd,
/* Bail out if toggle is already being cleared by a endpoint reset */
spin_lock_irqsave(&xhci->lock, flags);
+
if (ep->ep_state & EP_HARD_CLEAR_TOGGLE) {
ep->ep_state &= ~EP_HARD_CLEAR_TOGGLE;
spin_unlock_irqrestore(&xhci->lock, flags);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 2b0796f6d00e..0be040f87abf 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1292,7 +1292,8 @@ struct xhci_segment {
};
enum xhci_cancelled_td_status {
- TD_DIRTY = 0,
+ TD_TAINTED = 1,
+ TD_DIRTY,
TD_HALTED,
TD_CLEARING_CACHE,
TD_CLEARING_CACHE_DEFERRED,
@@ -1951,6 +1952,7 @@ void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
void xhci_cleanup_command_queue(struct xhci_hcd *xhci);
void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring);
unsigned int count_trbs(u64 addr, u64 len);
+bool next_td_is_tainted(struct xhci_ring *ring);
int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
int suspend, gfp_t gfp_flags);
void xhci_process_cancelled_tds(struct xhci_virt_ep *ep);
--
2.43.0
next prev parent reply other threads:[~2026-03-23 12:28 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-23 12:25 [RFC PATCH 0/2] fix xhci endpoint restart at EPROTO Mathias Nyman
2026-03-23 12:25 ` Mathias Nyman [this message]
2026-03-25 1:52 ` [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error Thinh Nguyen
2026-03-25 9:38 ` Mathias Nyman
2026-03-26 1:19 ` Thinh Nguyen
2026-03-26 11:25 ` Mathias Nyman
2026-03-26 23:24 ` Thinh Nguyen
2026-03-23 12:25 ` [RFC PATCH 2/2] xhci: Ensure URB is given back when endpoint halts on a multi-TD URB Mathias Nyman
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=20260323122512.2019893-2-mathias.nyman@linux.intel.com \
--to=mathias.nyman@linux.intel.com \
--cc=Thinh.Nguyen@synopsys.com \
--cc=linux-usb@vger.kernel.org \
--cc=michal.pecio@gmail.com \
--cc=niklas.neronin@linux.intel.com \
--cc=oneukum@suse.com \
--cc=stern@rowland.harvard.edu \
/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