[RFC PATCH 2/2] xhci: Ensure URB is given back when endpoint halts on a multi-TD URB

Mathias Nyman
2026-03-23T12:28:12Z

Make sure URB is given back by xhci driver after a stall (EPIPE)
or transaction error (EPROTO) on one of the TDs in the URB.

Most bulk, interrupt and control transfer URBs consist of one TD,
so adding that TD to the cancel list was enough to give back the
entire URB when invalidating and giving back cancelled TDs

Some bulk URBs that need an additional zero length packet consist
of two TDs. Make sure the second TD is added to the cancel list so
that URB is given back when clearing the internal endpoint halt.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
---
drivers/usb/host/xhci-ring.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index f01f343a7e37..5305ad7be025 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -982,8 +982,9 @@ static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id,
}

/*
- * 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 TD the endpoint halted on as TD_HALTED, add all TDs of that URB
+ * to the cancelled td list, ensuring invalidate_cancelled_tds() clears the
+ * TD from xHC cache and URB is given back.
* Mark the other TDs on that bulk or interrupt endpoint as TD_TAINTED to
* prevent the ring from being restarted too early.
*/
@@ -997,10 +998,11 @@ static void xhci_cancel_halted_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *e
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->urb == td->urb && list_empty(&tdi->cancelled_td_list))
+ list_add_tail(&tdi->cancelled_td_list, &ep->cancelled_td_list);
+
if (!tdi->cancel_status && ring->type != TYPE_CTRL)
tdi->cancel_status = TD_TAINTED;
}
--
2.43.0