diff mbox series

[4/9] usb: xhci: Expedite processing missed isoch TDs on modern HCs

Message ID 20240827195053.185f5a91@foxbook (mailing list archive)
State New
Headers show
Series Various xhci fixes and improvements | expand

Commit Message

MichaƂ Pecio Aug. 27, 2024, 5:50 p.m. UTC
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@gmx.net/T/
Signed-off-by: Michal Pecio <michal.pecio@gmail.com>
---
 drivers/usb/host/xhci-ring.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)
diff mbox series

Patch

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,