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

Configure Feed

Select the types of activity you want to include in your feed.

usb: xhci: Fix handling errors mid TD followed by other errors

Some host controllers fail to produce the final completion event on an
isochronous TD which experienced an error mid TD. We deal with it by
flagging such TDs and checking if the next event points at the flagged
TD or at the next one, and giving back the flagged TD if the latter.

This is not enough, because the next TD may be missed by the xHC. Or
there may be no next TD but a ring underrun. We also need to get such
TD quickly out of the way, or errors on later TDs may be handled wrong.

If the next TD experiences a Missed Service Error, we will set the skip
flag on the endpoint and then attempt skipping TDs when yet another
event arrives. In such scenario, we ought to report the 'error mid TD'
transfer as such rather than skip it.

Another problem case are Stopped events. If we see one after an error
mid TD, we naively assume that it's a Force Stopped Event because it
doesn't match the pending TD, but in reality it might be an ordinary
Stopped event for the next TD, which we fail to recognize and handle.

Fix this by moving error mid TD handling before the whole TD skipping
loop. Remove unnecessary conditions, always give back the TD if the new
event points to any TRB outside it or if the pointer is NULL, as may be
the case in Ring Underrun and Overrun events on 1st gen hardware. Only
if the pending TD isn't flagged, consider other actions like skipping.

As a side effect of reordering with skip and FSE cases, error mid TD is
reordered with last_td_was_short check. This is harmless, because the
two cases are mutually exclusive - only one can happen in any given run
of handle_tx_event().

Tested on the NEC host and a USB camera with flaky cable. Dynamic debug
confirmed that Transaction Errors are sometimes seen, sometimes mid-TD,
sometimes followed by Missed Service. In such cases, they were finished
properly before skipping began.

[Rebase on 6.12-rc1 -Mathias]

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/20241016140000.783905-4-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Michal Pecio and committed by
Greg Kroah-Hartman
f42a36ba fe49df60

+29 -37
+29 -37
drivers/usb/host/xhci-ring.c
··· 2775 2775 return 0; 2776 2776 } 2777 2777 2778 + /* 2779 + * xhci 4.10.2 states isoc endpoints should continue 2780 + * processing the next TD if there was an error mid TD. 2781 + * So host like NEC don't generate an event for the last 2782 + * isoc TRB even if the IOC flag is set. 2783 + * xhci 4.9.1 states that if there are errors in mult-TRB 2784 + * TDs xHC should generate an error for that TRB, and if xHC 2785 + * proceeds to the next TD it should genete an event for 2786 + * any TRB with IOC flag on the way. Other host follow this. 2787 + * 2788 + * We wait for the final IOC event, but if we get an event 2789 + * anywhere outside this TD, just give it back already. 2790 + */ 2791 + td = list_first_entry_or_null(&ep_ring->td_list, struct xhci_td, td_list); 2792 + 2793 + if (td && td->error_mid_td && !trb_in_td(xhci, td, ep_trb_dma, false)) { 2794 + xhci_dbg(xhci, "Missing TD completion event after mid TD error\n"); 2795 + ep_ring->dequeue = td->last_trb; 2796 + ep_ring->deq_seg = td->last_trb_seg; 2797 + inc_deq(xhci, ep_ring); 2798 + xhci_td_cleanup(xhci, td, ep_ring, td->status); 2799 + } 2800 + 2778 2801 if (list_empty(&ep_ring->td_list)) { 2779 2802 /* 2780 2803 * Don't print wanings if ring is empty due to a stopped endpoint generating an ··· 2859 2836 return 0; 2860 2837 } 2861 2838 2862 - /* 2863 - * xhci 4.10.2 states isoc endpoints should continue 2864 - * processing the next TD if there was an error mid TD. 2865 - * So host like NEC don't generate an event for the last 2866 - * isoc TRB even if the IOC flag is set. 2867 - * xhci 4.9.1 states that if there are errors in mult-TRB 2868 - * TDs xHC should generate an error for that TRB, and if xHC 2869 - * proceeds to the next TD it should genete an event for 2870 - * any TRB with IOC flag on the way. Other host follow this. 2871 - * So this event might be for the next TD. 2872 - */ 2873 - if (td->error_mid_td && 2874 - !list_is_last(&td->td_list, &ep_ring->td_list)) { 2875 - struct xhci_td *td_next = list_next_entry(td, td_list); 2839 + /* HC is busted, give up! */ 2840 + xhci_err(xhci, 2841 + "ERROR Transfer event TRB DMA ptr not part of current TD ep_index %d comp_code %u\n", 2842 + ep_index, trb_comp_code); 2843 + trb_in_td(xhci, td, ep_trb_dma, true); 2876 2844 2877 - ep_seg = trb_in_td(xhci, td_next, ep_trb_dma, false); 2878 - if (ep_seg) { 2879 - /* give back previous TD, start handling new */ 2880 - xhci_dbg(xhci, "Missing TD completion event after mid TD error\n"); 2881 - ep_ring->dequeue = td->last_trb; 2882 - ep_ring->deq_seg = td->last_trb_seg; 2883 - inc_deq(xhci, ep_ring); 2884 - xhci_td_cleanup(xhci, td, ep_ring, td->status); 2885 - td = td_next; 2886 - } 2887 - } 2888 - 2889 - if (!ep_seg) { 2890 - /* HC is busted, give up! */ 2891 - xhci_err(xhci, 2892 - "ERROR Transfer event TRB DMA ptr not " 2893 - "part of current TD ep_index %d " 2894 - "comp_code %u\n", ep_index, 2895 - trb_comp_code); 2896 - trb_in_td(xhci, td, ep_trb_dma, true); 2897 - 2898 - return -ESHUTDOWN; 2899 - } 2845 + return -ESHUTDOWN; 2900 2846 } 2901 2847 2902 2848 if (ep->skip) {