[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251119120430.5e41bf74.michal.pecio@gmail.com>
Date: Wed, 19 Nov 2025 12:04:30 +0100
From: Michal Pecio <michal.pecio@...il.com>
To: Mathias Nyman <mathias.nyman@...el.com>, Greg Kroah-Hartman
<gregkh@...uxfoundation.org>
Cc: linux-usb@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: [PATCH 3/5] usb: xhci: Refactor and generalize trb_in_td()
Replace the internal logic of trb_in_td() with a new one which works
on (virtual) TRB pointers instead of DMA addresses. Since driver data
structures use virtual pointers, the function and some of its callers
will be simplified. Optimize for common case of trb == end_trb.
Extract the implementation into a lower level trb_in_range() function
which works with arbitrary TRB ranges.
Create a new trb_in_td() as the obvious wrapper around trb_in_range().
Create a new dma_in_td() using xhci_dma_to_trb() and trb_in_td().
Update former trb_in_td() callers to use new functions appropriately.
Signed-off-by: Michal Pecio <michal.pecio@...il.com>
---
drivers/usb/host/xhci-ring.c | 101 +++++++++++++++++++----------------
1 file changed, 55 insertions(+), 46 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 531e2f207b17..a2257e1dc396 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -113,6 +113,11 @@ static bool trb_is_link(union xhci_trb *trb)
return TRB_TYPE_LINK_LE32(trb->link.control);
}
+static bool trb_on_seg(struct xhci_segment *seg, union xhci_trb *trb)
+{
+ return in_range((uintptr_t) trb, (uintptr_t) seg->trbs, TRB_SEGMENT_SIZE);
+}
+
static bool last_trb_on_seg(struct xhci_segment *seg, union xhci_trb *trb)
{
return trb == &seg->trbs[TRBS_PER_SEGMENT - 1];
@@ -304,54 +309,58 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring,
}
/*
- * If the suspect DMA address is a TRB in this TD, this function returns that
- * TRB's segment. Otherwise it returns 0.
+ * Check if seg:trb is a TRB in the range between start_trb and end_trb, inclusive.
+ * If start_trb == end_trb, the range is one TRB, not the full ring. The range may
+ * cycle back and end in an earlier segment or on earlier TRB in start_trb segment.
*/
-static struct xhci_segment *trb_in_td(struct xhci_td *td, dma_addr_t suspect_dma)
+static bool trb_in_range(struct xhci_hcd *xhci, union xhci_trb *start_trb, union xhci_trb *end_trb,
+ struct xhci_segment *seg, union xhci_trb *trb)
{
- dma_addr_t start_dma;
- dma_addr_t end_seg_dma;
- dma_addr_t end_trb_dma;
- struct xhci_segment *cur_seg;
+ struct xhci_segment *seg_other;
- start_dma = xhci_trb_virt_to_dma(td->start_seg, td->start_trb);
- cur_seg = td->start_seg;
+ if (!trb || !seg)
+ return false;
- do {
- if (start_dma == 0)
- return NULL;
- /* We may get an event for a Link TRB in the middle of a TD */
- end_seg_dma = xhci_trb_virt_to_dma(cur_seg,
- &cur_seg->trbs[TRBS_PER_SEGMENT - 1]);
- /* If the end TRB isn't in this segment, this is set to 0 */
- end_trb_dma = xhci_trb_virt_to_dma(cur_seg, td->end_trb);
-
- if (end_trb_dma > 0) {
- /* The end TRB is in this segment, so suspect should be here */
- if (start_dma <= end_trb_dma) {
- if (suspect_dma >= start_dma && suspect_dma <= end_trb_dma)
- return cur_seg;
- } else {
- /* Case for one segment with
- * a TD wrapped around to the top
- */
- if ((suspect_dma >= start_dma &&
- suspect_dma <= end_seg_dma) ||
- (suspect_dma >= cur_seg->dma &&
- suspect_dma <= end_trb_dma))
- return cur_seg;
- }
- return NULL;
- }
- /* Might still be somewhere in this segment */
- if (suspect_dma >= start_dma && suspect_dma <= end_seg_dma)
- return cur_seg;
+ /* Typically end_trb is near trb in the same segment */
+ if (trb_on_seg(seg, end_trb))
+ return trb <= end_trb
+ /* must not start between trb and end_trb */
+ ? !(trb < start_trb && start_trb <= end_trb)
+ /* must start between end_trb and trb */
+ : end_trb < start_trb && start_trb <= trb;
- cur_seg = cur_seg->next;
- start_dma = xhci_trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]);
- } while (cur_seg != td->start_seg);
+ /* The range ends in other segment, easy if it starts here */
+ if (trb_on_seg(seg, start_trb))
+ return start_trb <= trb;
- return NULL;
+ /* Walk segments and see if end_trb is reached before start_trb */
+ for (seg_other = seg->next; seg_other != seg; seg_other = seg_other->next) {
+ bool found_start = trb_on_seg(seg_other, start_trb);
+ bool found_end = trb_on_seg(seg_other, end_trb);
+
+ if (found_end)
+ return found_start ? end_trb < start_trb : true;
+
+ if (found_start)
+ return false;
+ }
+
+ xhci_err(xhci, "TRB range boundaries not on ring\n");
+ return false;
+}
+
+static bool trb_in_td(struct xhci_hcd *xhci, struct xhci_td *td,
+ struct xhci_segment *seg, union xhci_trb *trb)
+{
+ return trb_in_range(xhci, td->start_trb, td->end_trb, seg, trb);
+}
+
+static bool dma_in_td(struct xhci_hcd *xhci, struct xhci_td *td, dma_addr_t dma)
+{
+ struct xhci_segment *seg;
+ union xhci_trb *trb = xhci_dma_to_trb(td->start_seg, dma, &seg);
+
+ return trb_in_td(xhci, td, seg, trb);
}
/*
@@ -1094,7 +1103,7 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
td->urb->stream_id);
hw_deq &= TR_DEQ_PTR_MASK;
- if (td->cancel_status == TD_HALTED || trb_in_td(td, hw_deq)) {
+ if (td->cancel_status == TD_HALTED || dma_in_td(xhci, td, hw_deq)) {
switch (td->cancel_status) {
case TD_CLEARED: /* TD is already no-op */
case TD_CLEARING_CACHE: /* set TR deq command already queued */
@@ -1184,7 +1193,7 @@ static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep)
hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0);
hw_deq &= TR_DEQ_PTR_MASK;
td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list);
- if (trb_in_td(td, hw_deq))
+ if (dma_in_td(ep->xhci, td, hw_deq))
return td;
}
return NULL;
@@ -2843,7 +2852,7 @@ static int handle_tx_event(struct xhci_hcd *xhci,
*/
td = list_first_entry_or_null(&ep_ring->td_list, struct xhci_td, td_list);
- if (td && td->error_mid_td && !trb_in_td(td, ep_trb_dma)) {
+ if (td && td->error_mid_td && !trb_in_td(xhci, td, ep_seg, ep_trb)) {
xhci_dbg(xhci, "Missing TD completion event after mid TD error\n");
xhci_dequeue_td(xhci, td, ep_ring, td->status);
}
@@ -2876,7 +2885,7 @@ static int handle_tx_event(struct xhci_hcd *xhci,
td_list);
/* Is this a TRB in the currently executing TD? */
- if (!trb_in_td(td, ep_trb_dma)) {
+ if (!trb_in_td(xhci, td, ep_seg, ep_trb)) {
if (ep->skip && usb_endpoint_xfer_isoc(&td->urb->ep->desc)) {
/* this event is unlikely to match any TD, don't skip them all */
--
2.48.1
Powered by blists - more mailing lists