Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

xhci: Fix 5.12 regression of missing xHC cache clearing command after a Stall

If endpoints halts due to a stall then the dequeue pointer read from
hardware may already be set ahead of the stalled TRB.
After commit 674f8438c121 ("xhci: split handling halted endpoints into two
steps") in 5.12 xhci driver won't issue a Set TR Dequeue if hardware
dequeue pointer is already in the right place.

Turns out the "Set TR Dequeue pointer" command is anyway needed as it in
addition to moving the dequeue pointer also clears endpoint state and
cache.

Fixes: 674f8438c121 ("xhci: split handling halted endpoints into two steps")
Cc: <stable@vger.kernel.org> # 5.12
Reported-by: Peter Ganzhorn <peter.ganzhorn@googlemail.com>
Tested-by: Peter Ganzhorn <peter.ganzhorn@googlemail.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20210525074100.1154090-3-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Mathias Nyman and committed by
Greg Kroah-Hartman
a7f2e927 a80c203c

+6 -2
+6 -2
drivers/usb/host/xhci-ring.c
··· 933 933 continue; 934 934 } 935 935 /* 936 - * If ring stopped on the TD we need to cancel, then we have to 936 + * If a ring stopped on the TD we need to cancel then we have to 937 937 * move the xHC endpoint ring dequeue pointer past this TD. 938 + * Rings halted due to STALL may show hw_deq is past the stalled 939 + * TD, but still require a set TR Deq command to flush xHC cache. 938 940 */ 939 941 hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 940 942 td->urb->stream_id); 941 943 hw_deq &= ~0xf; 942 944 943 - if (trb_in_td(xhci, td->start_seg, td->first_trb, 945 + if (td->cancel_status == TD_HALTED) { 946 + cached_td = td; 947 + } else if (trb_in_td(xhci, td->start_seg, td->first_trb, 944 948 td->last_trb, hw_deq, false)) { 945 949 switch (td->cancel_status) { 946 950 case TD_CLEARED: /* TD is already no-op */