Skip to content

Commit fe82f16

Browse files
matnymangregkh
authored andcommitted
xhci: Fix incorrect tracking of free space on transfer rings
This incorrect tracking caused unnecessary ring expansion in some usecases which over days of use consume a lot of memory. xhci driver tries to keep track of free transfer blocks (TRBs) on the ring buffer, but failed to add back some cancelled transfers that were turned into no-op operations instead of just moving past them. This can happen if there are several queued pending transfers which then are cancelled in reverse order. Solve this by counting the numer of steps we move the dequeue pointer once we complete a transfer, and add it to the number of free trbs instead of just adding the trb number of the current transfer. This way we ensure we count the no-op trbs on the way as well. Fixes: 55f6153 ("xhci: remove extra loop in interrupt context") Cc: stable@vger.kernel.org Reported-by: Miller Hunter <MillerH@hearthnhome.com> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217242 Tested-by: Miller Hunter <MillerH@hearthnhome.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Link: https://lore.kernel.org/r/20230515134059.161110-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 2a821fc commit fe82f16

File tree

1 file changed

+28
-1
lines changed

1 file changed

+28
-1
lines changed

drivers/usb/host/xhci-ring.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,26 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring,
276276
trace_xhci_inc_enq(ring);
277277
}
278278

279+
static int xhci_num_trbs_to(struct xhci_segment *start_seg, union xhci_trb *start,
280+
struct xhci_segment *end_seg, union xhci_trb *end,
281+
unsigned int num_segs)
282+
{
283+
union xhci_trb *last_on_seg;
284+
int num = 0;
285+
int i = 0;
286+
287+
do {
288+
if (start_seg == end_seg && end >= start)
289+
return num + (end - start);
290+
last_on_seg = &start_seg->trbs[TRBS_PER_SEGMENT - 1];
291+
num += last_on_seg - start;
292+
start_seg = start_seg->next;
293+
start = start_seg->trbs;
294+
} while (i++ <= num_segs);
295+
296+
return -EINVAL;
297+
}
298+
279299
/*
280300
* Check to see if there's room to enqueue num_trbs on the ring and make sure
281301
* enqueue pointer will not advance into dequeue segment. See rules above.
@@ -2140,6 +2160,7 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
21402160
u32 trb_comp_code)
21412161
{
21422162
struct xhci_ep_ctx *ep_ctx;
2163+
int trbs_freed;
21432164

21442165
ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index);
21452166

@@ -2209,9 +2230,15 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
22092230
}
22102231

22112232
/* Update ring dequeue pointer */
2233+
trbs_freed = xhci_num_trbs_to(ep_ring->deq_seg, ep_ring->dequeue,
2234+
td->last_trb_seg, td->last_trb,
2235+
ep_ring->num_segs);
2236+
if (trbs_freed < 0)
2237+
xhci_dbg(xhci, "Failed to count freed trbs at TD finish\n");
2238+
else
2239+
ep_ring->num_trbs_free += trbs_freed;
22122240
ep_ring->dequeue = td->last_trb;
22132241
ep_ring->deq_seg = td->last_trb_seg;
2214-
ep_ring->num_trbs_free += td->num_trbs - 1;
22152242
inc_deq(xhci, ep_ring);
22162243

22172244
return xhci_td_cleanup(xhci, td, ep_ring, td->status);

0 commit comments

Comments
 (0)