Commit fe82f16a authored by Mathias Nyman's avatar Mathias Nyman Committed by Greg Kroah-Hartman

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: 55f6153d ("xhci: remove extra loop in interrupt context")
Cc: stable@vger.kernel.org
Reported-by: default avatarMiller Hunter <MillerH@hearthnhome.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217242Tested-by: default avatarMiller Hunter <MillerH@hearthnhome.com>
Signed-off-by: default avatarMathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20230515134059.161110-3-mathias.nyman@linux.intel.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 2a821fc3
...@@ -276,6 +276,26 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, ...@@ -276,6 +276,26 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring,
trace_xhci_inc_enq(ring); trace_xhci_inc_enq(ring);
} }
static int xhci_num_trbs_to(struct xhci_segment *start_seg, union xhci_trb *start,
struct xhci_segment *end_seg, union xhci_trb *end,
unsigned int num_segs)
{
union xhci_trb *last_on_seg;
int num = 0;
int i = 0;
do {
if (start_seg == end_seg && end >= start)
return num + (end - start);
last_on_seg = &start_seg->trbs[TRBS_PER_SEGMENT - 1];
num += last_on_seg - start;
start_seg = start_seg->next;
start = start_seg->trbs;
} while (i++ <= num_segs);
return -EINVAL;
}
/* /*
* Check to see if there's room to enqueue num_trbs on the ring and make sure * Check to see if there's room to enqueue num_trbs on the ring and make sure
* enqueue pointer will not advance into dequeue segment. See rules above. * 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, ...@@ -2140,6 +2160,7 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
u32 trb_comp_code) u32 trb_comp_code)
{ {
struct xhci_ep_ctx *ep_ctx; struct xhci_ep_ctx *ep_ctx;
int trbs_freed;
ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index);
...@@ -2209,9 +2230,15 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, ...@@ -2209,9 +2230,15 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
} }
/* Update ring dequeue pointer */ /* Update ring dequeue pointer */
trbs_freed = xhci_num_trbs_to(ep_ring->deq_seg, ep_ring->dequeue,
td->last_trb_seg, td->last_trb,
ep_ring->num_segs);
if (trbs_freed < 0)
xhci_dbg(xhci, "Failed to count freed trbs at TD finish\n");
else
ep_ring->num_trbs_free += trbs_freed;
ep_ring->dequeue = td->last_trb; ep_ring->dequeue = td->last_trb;
ep_ring->deq_seg = td->last_trb_seg; ep_ring->deq_seg = td->last_trb_seg;
ep_ring->num_trbs_free += td->num_trbs - 1;
inc_deq(xhci, ep_ring); inc_deq(xhci, ep_ring);
return xhci_td_cleanup(xhci, td, ep_ring, td->status); return xhci_td_cleanup(xhci, td, ep_ring, td->status);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment