xHCI spec rev. 1.0 allowed the TRB pointer of Missed Service events to be NULL. In such case we have no idea which queued TD were missed and which are still waiting for execution (and the spec forbids us guessing based on frame numbers), so all we can do is set a flag on the endpoint and wait for a new event with a valid dequeue pointer. But hosts are also allowed to give us pointer to the last missed TRB and this became mandatory in spec rev. 1.1 and later. Use this pointer, if available, to immediately skip all missed TDs. This may be particularly helpful for applications which queue very few URBs/TDs for the sake of lowest possible latency. It also saves the day if the next event is an underrun/overrun and we will not get a valid dequeue pointer with it. If we then saw the skip flag, we would have no way of knowing how many TDs were missed and how many were queued after the host encountered ring underrun. Handle Missed Service Error events as "error mid TD", if applicable, because rev. 1.0 spec excplicitly says so in notes to 4.10.3.2. The notes are no longer present in later revs, but rules of 4.9.1 should apply universally. Note that handle_tx_event() can cope with a host reporting MSE on an early TRB of a TD and then failing to signal the final TRB. However, in such (hopefully rare) case latency is not improved by this patch. Tested on ASMedia ASM3142. This USB 3.1 host gives valid pointers in Missed Service events and the skipping loop works until it finds the last missed TRB indicated by the host. Then the skip flag is cleared and the TRB passed to process_isoc_td() like any other. Tested on NEC and VIA VL805. These USB 3.0 hosts give NULL pointers so the event handler sets the skip flag and returns, as expected. Change inspired by a recent discussion about realtime USB audio. Link: https://lore.kernel.org/linux-usb/76e1a191-020d-4a76-97f6-237f9bd0ede0@xxxxxxx/T/ Signed-off-by: Michal Pecio <michal.pecio@xxxxxxxxx> --- drivers/usb/host/xhci-ring.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index e65cc80cb285..cc0420021683 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2413,8 +2413,14 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, frame->status = -EOVERFLOW; if (ep_trb != td->last_trb) td->error_mid_td = true; break; + case COMP_MISSED_SERVICE_ERROR: + frame->status = -EXDEV; + sum_trbs_for_length = true; + if (ep_trb != td->last_trb) + td->error_mid_td = true; + break; case COMP_INCOMPATIBLE_DEVICE_ERROR: case COMP_STALL_ERROR: frame->status = -EPROTO; break; @@ -2730,13 +2736,17 @@ static int handle_tx_event(struct xhci_hcd *xhci, * When encounter missed service error, one or more isoc tds * may be missed by xHC. * Set skip flag of the ep_ring; Complete the missed tds as * short transfer when process the ep_ring next time. + * If the xHC tells us the last missed TRB (ep_trb_dma != NULL) + * perform the skipping right away. */ ep->skip = true; xhci_dbg(xhci, - "Miss service interval error for slot %u ep %u, set skip flag\n", - slot_id, ep_index); + "Miss service interval error for slot %u ep %u, set skip flag, go ahead %d\n", + slot_id, ep_index, !!ep_trb_dma); + if (ep_trb_dma) + break; return 0; case COMP_NO_PING_RESPONSE_ERROR: ep->skip = true; xhci_dbg(xhci, -- 2.43.0