* [PATCH v2 0/2] xhci: Sort out the TD skipping business
@ 2026-10-07 7:49 Michal Pecio
2026-10-07 7:51 ` [PATCH v2 1/2] usb: xhci: Shorten the TD skipping loop Michal Pecio
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Michal Pecio @ 2026-10-07 7:49 UTC (permalink / raw)
To: Mathias Nyman, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
Hi Mathias,
I see that you have picked this series for 7.4:
https://lore.kernel.org/linux-usb/20260804120110.01bda0e2.michal.pecio@gmail.com/
There is unfortunately a small bug in "Shorten the TD skipping loop":
old_trb_comp_code is assigned before being used by the !trb_in_td()
block, which means that we use the *current* trb_comp_code instead.
ep_ring->old_trb_comp_code = trb_comp_code;
if (ring_xrun_event)
return 0;
+ /* Handle events not referencing the current TD */
+ if (!trb_in_td(td, ep_trb_dma)) {
[...]
+ if (xhci_spurious_success_tx_event(xhci, ep_ring))
// ^-- uses old_trb_comp_code
This causes false warnings when running UVC with non-power-of-2 alt
setting on HCs like VIA, which generate Success after a mid-TD Short
Packet. So this will surely bother users with such workloads.
It happened because I tried to resuse existing ring_xrun_event check
and remove an identical check from the !trb_in_td() block, so I placed
the block after this check.
Revised patch places it before the old_trb_comp_code assignment and
retains a duplicate ring_xrun_event check in the moved code, I'm not
trying to be clever anymore. Patch 2/2 is a necessary rebase.
It is also a pre-existing bug that in some cases the function bails out
without updating old_trb_comp_code, even though we know that the prior
TD cannot generate more events (e.g. we are handling ring underrun).
But that's a separate issue that causes missing warnings in rare cases
rather than false warnings in common cases.
Regards,
Michal
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/2] usb: xhci: Shorten the TD skipping loop
2026-10-07 7:49 [PATCH v2 0/2] xhci: Sort out the TD skipping business Michal Pecio
@ 2026-10-07 7:51 ` Michal Pecio
2026-10-07 7:52 ` [PATCH 2/2] usb: xhci: Rework and improve the TD matching and skipping logic Michal Pecio
2026-10-07 9:38 ` [PATCH v2 0/2] xhci: Sort out the TD skipping business Mathias Nyman
2 siblings, 0 replies; 4+ messages in thread
From: Michal Pecio @ 2026-10-07 7:51 UTC (permalink / raw)
To: Mathias Nyman, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
Half of this loop is code which only executes once to deal with cases
where no TD matches the event and then it returns. This code needs not
to be in any kind of loop, so get it out.
Optimize conditionals remaining in the loop body.
Signed-off-by: Michal Pecio <michal.pecio@gmail.com>
---
drivers/usb/host/xhci-ring.c | 68 +++++++++++++++++-------------------
1 file changed, 33 insertions(+), 35 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 3ae823830fcf..875a0770a3b7 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -2844,10 +2844,9 @@ static int handle_tx_event(struct xhci_hcd *xhci,
td = list_first_entry(&ep_ring->td_list, struct xhci_td,
td_list);
- /* Is this TRB not part of the currently executing TD? */
- if (!trb_in_td(td, ep_trb_dma)) {
+ if (ep->skip) {
- if (ep->skip) {
+ if (!trb_in_td(td, ep_trb_dma)) {
/* this event is unlikely to match any TD, don't skip them all */
if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID)
return 0;
@@ -2885,38 +2884,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
goto check_endpoint_halted;
}
- /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */
- if (ring_xrun_event)
- return 0;
-
- /*
- * Skip the Force Stopped Event. The 'ep_trb' of FSE is not in the current
- * TD pointed by 'ep_ring->dequeue' because that the hardware dequeue
- * pointer still at the previous TRB of the current TD. The previous TRB
- * maybe a Link TD or the last TRB of the previous TD. The command
- * completion handle will take care the rest.
- */
- if (trb_comp_code == COMP_STOPPED ||
- trb_comp_code == COMP_STOPPED_LENGTH_INVALID) {
- return 0;
- }
-
- /*
- * Some hosts give a spurious success event after a short
- * transfer or error on last TRB. Ignore it.
- */
- if (xhci_spurious_success_tx_event(xhci, ep_ring)) {
- xhci_dbg(xhci, "Spurious event dma %pad, comp_code %u after %u\n",
- &ep_trb_dma, trb_comp_code, ep_ring->old_trb_comp_code);
- ep_ring->old_trb_comp_code = 0;
- return 0;
- }
-
- /* HC is busted, give up! */
- goto debug_finding_td;
- }
-
- if (ep->skip) {
xhci_dbg(xhci,
"Found td. Clear skip flag for slot %u ep %u.\n",
slot_id, ep_index);
@@ -2931,6 +2898,37 @@ static int handle_tx_event(struct xhci_hcd *xhci,
*/
} while (ep->skip);
+ /* Handle events not referencing the current TD */
+ if (!trb_in_td(td, ep_trb_dma)) {
+ /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */
+ if (ring_xrun_event)
+ return 0;
+
+ /*
+ * Skip the Force Stopped Event. The 'ep_trb' of FSE is not in the current
+ * TD pointed by 'ep_ring->dequeue' because that the hardware dequeue
+ * pointer still at the previous TRB of the current TD. The previous TRB
+ * maybe a Link TD or the last TRB of the previous TD. The command
+ * completion handle will take care the rest.
+ */
+ if (trb_comp_code == COMP_STOPPED || trb_comp_code == COMP_STOPPED_LENGTH_INVALID)
+ return 0;
+
+ /*
+ * Some hosts give a spurious success event after a short
+ * transfer or error on last TRB. Ignore it.
+ */
+ if (xhci_spurious_success_tx_event(xhci, ep_ring)) {
+ xhci_dbg(xhci, "Spurious event dma %pad, comp_code %u after %u\n",
+ &ep_trb_dma, trb_comp_code, ep_ring->old_trb_comp_code);
+ ep_ring->old_trb_comp_code = 0;
+ return 0;
+ }
+
+ /* HC is busted, give up! */
+ goto debug_finding_td;
+ }
+
ep_ring->old_trb_comp_code = trb_comp_code;
/* Get out if a TD was queued at enqueue after the xrun occurred */
--
2.48.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/2] usb: xhci: Rework and improve the TD matching and skipping logic
2026-10-07 7:49 [PATCH v2 0/2] xhci: Sort out the TD skipping business Michal Pecio
2026-10-07 7:51 ` [PATCH v2 1/2] usb: xhci: Shorten the TD skipping loop Michal Pecio
@ 2026-10-07 7:52 ` Michal Pecio
2026-10-07 9:38 ` [PATCH v2 0/2] xhci: Sort out the TD skipping business Mathias Nyman
2 siblings, 0 replies; 4+ messages in thread
From: Michal Pecio @ 2026-10-07 7:52 UTC (permalink / raw)
To: Mathias Nyman, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
Matching events with TDs and giving back missed TDs is carried out
by a complicated loop. Replace it with a simpler linear logic:
0. Having verified that 'td_list' isn't empty,
1. Scan it to find the matching TD and count missed TDs,
2. Perform necessary adjustments for corner cases,
3. Give back missed TDs, if applicable, using a short and tidy loop,
4. Check if the event refers to the expected TD and proceed as usual.
Besides cleaning up the code, this provides a few improvements:
- when the skip flag is set, no TD is given back unless we found a match
or otherwise know how many TDs should be given back
- when the skip flag is clear, we know if the event refers to a "future"
TD so we can log this in the Scary Error Message to aid debugging.
While altering the error message, drop a pointless goto.
Signed-off-by: Michal Pecio <michal.pecio@gmail.com>
---
drivers/usb/host/xhci-ring.c | 137 ++++++++++++++++-------------------
1 file changed, 61 insertions(+), 76 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 875a0770a3b7..2ea1c7dc1574 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -127,11 +127,16 @@ static bool link_trb_toggles_cycle(union xhci_trb *trb)
return le32_to_cpu(trb->link.control) & LINK_TOGGLE;
}
-static bool last_td_in_urb(struct xhci_td *td)
+static int num_tds_not_done(struct urb *urb)
{
- struct urb_priv *urb_priv = td->urb->hcpriv;
+ struct urb_priv *urb_priv = urb->hcpriv;
- return urb_priv->num_tds_done == urb_priv->num_tds;
+ return urb_priv->num_tds - urb_priv->num_tds_done;
+}
+
+static bool last_td_in_urb(struct xhci_td *td)
+{
+ return !num_tds_not_done(td->urb);
}
static bool unhandled_event_trb(struct xhci_ring *ring)
@@ -2606,14 +2611,19 @@ static bool xhci_spurious_success_tx_event(struct xhci_hcd *xhci,
}
}
-static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, dma_addr_t dma)
+static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, int *missed_tds, dma_addr_t dma)
{
struct xhci_td *td;
- if (dma)
+ if (dma) {
list_for_each_entry(td, &ep_ring->td_list, td_list)
if (trb_in_td(td, dma))
return td;
+ else
+ (*missed_tds)++;
+ }
+
+ *missed_tds = 0;
return NULL;
}
@@ -2630,8 +2640,8 @@ static int handle_tx_event(struct xhci_hcd *xhci,
struct xhci_ring *ep_ring;
unsigned int slot_id;
int ep_index;
- struct xhci_td *td = NULL;
- struct urb *missed_urb = NULL;
+ struct xhci_td *td;
+ int missed_tds = 0;
dma_addr_t ep_trb_dma;
union xhci_trb *ep_trb;
int status = -EINPROGRESS;
@@ -2814,13 +2824,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
xhci_dequeue_td(xhci, td, ep_ring, td->status);
}
- /*
- * We don't know how many TDs were missed when ep_trb_dma is zero (as permitted by
- * xHCI 1.0) or bogus. Bail out leaving ep->skip set, next event will sort it out.
- */
- if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !find_td_by_dma(ep_ring, ep_trb_dma))
- return 0;
-
if (list_empty(&ep_ring->td_list)) {
/*
* Don't print wanings if ring is empty due to a stopped endpoint generating an
@@ -2840,66 +2843,50 @@ static int handle_tx_event(struct xhci_hcd *xhci,
goto check_endpoint_halted;
}
- do {
- td = list_first_entry(&ep_ring->td_list, struct xhci_td,
- td_list);
-
- if (ep->skip) {
-
- if (!trb_in_td(td, ep_trb_dma)) {
- /* this event is unlikely to match any TD, don't skip them all */
- if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID)
- return 0;
-
- /*
- * If skip flag is still set at xrun, we are on xHCI 1.0 and our TRB
- * pointer is zero again. All missed TDs can be given back, but we
- * don't know which were missed and which were queued after the xrun
- * occurred. We can safely give back the first pending URB.
- */
- if (ring_xrun_event) {
- if (!missed_urb)
- missed_urb = td->urb;
-
- if (td->urb != missed_urb) {
- xhci_dbg(xhci, "Skipped one URB for slot %u ep %u",
- slot_id, ep_index);
- return 0;
- }
- }
-
- /*
- * TD was missed, skip it. Core already initialized frame->status
- * to -EXDEV and frame->actual_length to 0, nothing more to do.
- */
- xhci_dequeue_td(xhci, td, ep_ring, 0);
+ td = find_td_by_dma(ep_ring, &missed_tds, ep_trb_dma);
- if (!list_empty(&ep_ring->td_list))
- continue;
+ if (ep->skip) {
+ if (!td) {
+ /*
+ * xHCI 1.0 allowed MSE events to have zero TRB pointers. Some old chips
+ * also generate bogus non-zero pointers. We know, don't bother warning.
+ * Missed TDs will be given back by the next event with a valid pointer.
+ */
+ if (trb_comp_code == COMP_MISSED_SERVICE_ERROR &&
+ xhci->hci_version <= 0x100)
+ return 0;
+ /*
+ * If skip flag is still set at xrun, we are on xHCI 1.0 and our TRB pointer
+ * is zero again. All missed TDs can be given back, but we don't know which
+ * were missed and which were queued after the xrun occurred. We can safely
+ * give back the first pending URB to let the class driver know.
+ */
+ if (ring_xrun_event)
+ missed_tds = num_tds_not_done(list_first_entry(&ep_ring->td_list,
+ struct xhci_td, td_list)->urb);
+ /* In other cases missed_tds is zero */
+ }
- xhci_dbg(xhci, "All TDs skipped for slot %u ep %u. Clear skip flag.\n",
- slot_id, ep_index);
- ep->skip = false;
- td = NULL;
- goto check_endpoint_halted;
- }
+ /*
+ * Give back missed TDs. Core already initialized their frame->status to -EXDEV
+ * and frame->actual_length to 0, nothing more to do.
+ */
+ for (int i = 0; i < missed_tds; i++)
+ xhci_dequeue_td(xhci,
+ list_first_entry(&ep_ring->td_list, struct xhci_td, td_list),
+ ep_ring, 0);
- xhci_dbg(xhci,
- "Found td. Clear skip flag for slot %u ep %u.\n",
- slot_id, ep_index);
+ /* the list may become empty on ring_xrun_event */
+ if (td || list_empty(&ep_ring->td_list))
ep->skip = false;
- }
- /*
- * If ep->skip is set, it means there are missed tds on the
- * endpoint ring need to take care of.
- * Process them as short transfer until reach the td pointed by
- * the event.
- */
- } while (ep->skip);
+ xhci_dbg(xhci, "Skipped %d TDs on slot %u ep %u comp_code %u, TD found %d, skip flag %d\n",
+ missed_tds, slot_id, ep_index, trb_comp_code, !!td, ep->skip);
+ missed_tds = 0;
+ }
/* Handle events not referencing the current TD */
- if (!trb_in_td(td, ep_trb_dma)) {
+ if (!td || missed_tds) {
/* TD was queued after xrun, maybe xrun was on a link, don't panic yet */
if (ring_xrun_event)
return 0;
@@ -2926,7 +2913,13 @@ static int handle_tx_event(struct xhci_hcd *xhci,
}
/* HC is busted, give up! */
- goto debug_finding_td;
+ td = list_first_entry(&ep_ring->td_list, struct xhci_td, td_list);
+ xhci_err(xhci, "Event dma %pad for ep %d comp_code %u not part of TD at %016llx - %016llx, missed %d\n",
+ &ep_trb_dma, ep_index, trb_comp_code,
+ (u64)xhci_trb_virt_to_dma(td->start_seg, td->start_trb),
+ (u64)xhci_trb_virt_to_dma(td->end_seg, td->end_trb),
+ missed_tds);
+ return -ESHUTDOWN;
}
ep_ring->old_trb_comp_code = trb_comp_code;
@@ -2964,14 +2957,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
return 0;
-debug_finding_td:
- xhci_err(xhci, "Event dma %pad for ep %d status %d not part of TD at %016llx - %016llx\n",
- &ep_trb_dma, ep_index, trb_comp_code,
- (unsigned long long)xhci_trb_virt_to_dma(td->start_seg, td->start_trb),
- (unsigned long long)xhci_trb_virt_to_dma(td->end_seg, td->end_trb));
-
- return -ESHUTDOWN;
-
err_out:
xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n",
(unsigned long long) xhci_trb_virt_to_dma(
--
2.48.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 0/2] xhci: Sort out the TD skipping business
2026-10-07 7:49 [PATCH v2 0/2] xhci: Sort out the TD skipping business Michal Pecio
2026-10-07 7:51 ` [PATCH v2 1/2] usb: xhci: Shorten the TD skipping loop Michal Pecio
2026-10-07 7:52 ` [PATCH 2/2] usb: xhci: Rework and improve the TD matching and skipping logic Michal Pecio
@ 2026-10-07 9:38 ` Mathias Nyman
2 siblings, 0 replies; 4+ messages in thread
From: Mathias Nyman @ 2026-10-07 9:38 UTC (permalink / raw)
To: Michal Pecio, Mathias Nyman, Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel
On 10/7/26 10:49, Michal Pecio wrote:
> Hi Mathias,
>
> I see that you have picked this series for 7.4:
> https://lore.kernel.org/linux-usb/20260804120110.01bda0e2.michal.pecio@gmail.com/
>
> There is unfortunately a small bug in "Shorten the TD skipping loop":
> old_trb_comp_code is assigned before being used by the !trb_in_td()
> block, which means that we use the *current* trb_comp_code instead.
>
Thanks, replaced last two patches of your series with these two
-Mathias
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-07 9:38 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-07 7:49 [PATCH v2 0/2] xhci: Sort out the TD skipping business Michal Pecio
2026-10-07 7:51 ` [PATCH v2 1/2] usb: xhci: Shorten the TD skipping loop Michal Pecio
2026-10-07 7:52 ` [PATCH 2/2] usb: xhci: Rework and improve the TD matching and skipping logic Michal Pecio
2026-10-07 9:38 ` [PATCH v2 0/2] xhci: Sort out the TD skipping business Mathias Nyman
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®