xhci: fix matching completion events with TDs
authorMichal Pecio <michal.pecio@gmail.com>
Thu, 29 Feb 2024 14:14:35 +0000 (16:14 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 2 Mar 2024 19:25:27 +0000 (20:25 +0100)
A trb_in_td() call is used to determine if a completion event matches
any TRB of the currently executing TD. This function is told to start
searching right after the last finished TD, which is not at all where
the currently expected TD is guaranteed to begin, because some TDs in
between may have been cancelled.

Not only is a pointless work performed, but a bug resulting in the HC
executing cancelled TDs was seen to trick the driver into associating
events from a TD just cancelled with an unrelated future TD.

Since the ring is being traversed for the specific purpose of finding
a match with the current TD, always start from its first TRB. This is
the most reliable bit of information that we posses.

Tracking of HC's work progress is not affected, except for cases when
a misattributed event would have moved dequeue past a pending TD.

Signed-off-by: Michal Pecio <michal.pecio@gmail.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20240229141438.619372-7-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-ring.c

index 95ed26114ee8b8060e5ac943656740878705e8a2..b2116501048cd709ec900065bdfc79ee815dff23 100644 (file)
@@ -2813,7 +2813,7 @@ static int handle_tx_event(struct xhci_hcd *xhci,
                        td_num--;
 
                /* Is this a TRB in the currently executing TD? */
-               ep_seg = trb_in_td(xhci, ep_ring->deq_seg, ep_ring->dequeue,
+               ep_seg = trb_in_td(xhci, td->start_seg, td->first_trb,
                                td->last_trb, ep_trb_dma, false);
 
                /*
@@ -2881,9 +2881,8 @@ static int handle_tx_event(struct xhci_hcd *xhci,
                                        "part of current TD ep_index %d "
                                        "comp_code %u\n", ep_index,
                                        trb_comp_code);
-                               trb_in_td(xhci, ep_ring->deq_seg,
-                                         ep_ring->dequeue, td->last_trb,
-                                         ep_trb_dma, true);
+                               trb_in_td(xhci, td->start_seg, td->first_trb,
+                                         td->last_trb, ep_trb_dma, true);
                                return -ESHUTDOWN;
                        }
                }