diff mbox series

[5/5] usb: xhci: Fix Ring Underrun/Overrun handling

Message ID 20240910131651.6c4c0195@foxbook (mailing list archive)
State New
Headers show
Series Quick and effective handle_tx_event() cleanup | expand

Commit Message

MichaƂ Pecio Sept. 10, 2024, 11:16 a.m. UTC
Depending on implemented spec revision, the TRB pointer of these events
may either be NULL or point at enqueue at the time of error occurrence.
By the time we handle the event, a new TD may be queued at this address.

Ensure that the new TD is not completed prematurely if the event handler
is entered due to the skip flag being set on the endpoint. Or, when the
TRB pointer is NULL, prevent the empty ring warning being printed.

Now that it is safe to enter the event handler, also enter it when the
skip flag is not set. This enables completion of any TD stuck in 'error
mid TD' state on buggy hosts. Such problem could, for example, happen
when a USBFS application knows in advance how many frames it needs and
submits the exact number of URBs, then an error occurs on the last TD.

One bug remains: when enqueue points at a Link TRB and a new TD appears
after that, skipping will remove the new TD prematurely. This should be
255 times less common that the 'matching TD' bug being fixed here, and
it will take a major improvement to the skipping loop to fix.

Signed-off-by: Michal Pecio <michal.pecio@gmail.com>
---
 drivers/usb/host/xhci-ring.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index db9bc7db5aac..475b4d69551b 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -2726,14 +2726,10 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 		 * Underrun Event for OUT Isoch endpoint.
 		 */
 		xhci_dbg(xhci, "Underrun event on slot %u ep %u\n", slot_id, ep_index);
-		if (ep->skip)
-			break;
-		return 0;
+		break;
 	case COMP_RING_OVERRUN:
 		xhci_dbg(xhci, "Overrun event on slot %u ep %u\n", slot_id, ep_index);
-		if (ep->skip)
-			break;
-		return 0;
+		break;
 	case COMP_MISSED_SERVICE_ERROR:
 		/*
 		 * When encounter missed service error, one or more isoc tds
@@ -2824,6 +2820,15 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 				skipped, td ? "":"not ", slot_id, ep_index);
 	}
 
+	/*
+	 * In these events ep_trb_dma is NULL or points at enqueue from the time
+	 * of error occurrence. If it matches a new TD queued since then, don't
+	 * complete the TD now. And otherwise, don't print senseless warnings.
+	 */
+	if (trb_comp_code == COMP_RING_UNDERRUN ||
+			trb_comp_code == COMP_RING_OVERRUN)
+		return 0;
+
 	if (!ep_seg) {
 
 		/*