diff mbox series

[3/5] usb: xhci: Unify event handler's 'empty list' and 'no match' cases

Message ID 20240910131517.2bc8a403@foxbook
State New
Headers show
Series [1/5] usb: xhci: Fix handling errors mid TD followed by other errors | expand

Commit Message

MichaƂ Pecio Sept. 10, 2024, 11:15 a.m. UTC
Sometimes handle_tx_event() gets an event which doesn't match any
pending TD. Or it may be that there are simply no pending TDs at all.

These two cases are hardly different, but they are handled by two
separate blocks of code. Some logic is pointlessly duplicated, some
logic is missing or buggy in one branch or in the other.

Reduce the 'empty list' case in the searching loop to a minimum and
merge the code removed from there with almost identical code after
the loop, which deals with the 'no match' case.

This fixes the "spurious success" check in the 'empty list' case not
verifying if the host actually has the quirk, and the lack of halted
endpoint recovery in the 'no match' case.

Remove an obsolete attempt at stall recovery. This code relied on a
bug which has been fixed earlier this year, and it was never really
fully effective because not all cancelled TDs get No-Op'ed.

Make the empty list warning print event completion code, like the
'no match' case does.

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

Patch

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 0eef7cd2f20a..56b0c0e85293 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -2606,7 +2606,7 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 	int ep_index;
 	struct xhci_td *td = NULL;
 	dma_addr_t ep_trb_dma;
-	struct xhci_segment *ep_seg;
+	struct xhci_segment *ep_seg = NULL;
 	union xhci_trb *ep_trb;
 	int status = -EINPROGRESS;
 	struct xhci_ep_ctx *ep_ctx;
@@ -2793,28 +2793,12 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 		 * TD list.
 		 */
 		if (list_empty(&ep_ring->td_list)) {
-			/*
-			 * Don't print wanings if it's due to a stopped endpoint
-			 * generating an extra completion event if the device
-			 * was suspended. Or, a event for the last TRB of a
-			 * short TD we already got a short event for.
-			 * The short TD is already removed from the TD list.
-			 */
-
-			if (!(trb_comp_code == COMP_STOPPED ||
-			      trb_comp_code == COMP_STOPPED_LENGTH_INVALID ||
-			      ep_ring->last_td_was_short)) {
-				xhci_warn(xhci, "WARN Event TRB for slot %u ep %d with no TDs queued?\n",
-					  slot_id, ep_index);
-			}
 			if (ep->skip) {
 				ep->skip = false;
 				xhci_dbg(xhci, "td_list is empty while skip flag set. Clear skip flag for slot %u ep %u.\n",
 					 slot_id, ep_index);
 			}
-
-			td = NULL;
-			goto check_endpoint_halted;
+			break;
 		}
 
 		td = list_first_entry(&ep_ring->td_list, struct xhci_td,
@@ -2848,11 +2832,8 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 	if (!ep_seg) {
 
 		/*
-		 * Skip the Force Stopped Event. The 'ep_trb' of FSE is not in the current
-		 * TD pointed by 'ep_ring->dequeue' because that the hardware dequeue
-		 * pointer still at the previous TRB of the current TD. The previous TRB
-		 * maybe a Link TD or the last TRB of the previous TD. The command
-		 * completion handle will take care the rest.
+		 * Ignore the Force Stopped Event. The endpoint may stop on
+		 * some Link or No-Op TRB outside our TDs. We don't care.
 		 */
 		if (trb_comp_code == COMP_STOPPED ||
 		    trb_comp_code == COMP_STOPPED_LENGTH_INVALID) {
@@ -2870,13 +2851,25 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 		}
 
 		/* HC is busted, give up! */
-		xhci_err(xhci,
-			"ERROR Transfer event TRB DMA ptr not "
-			"part of current TD ep_index %d "
-			"comp_code %u\n", ep_index,
-			trb_comp_code);
-		trb_in_td(xhci, td, ep_trb_dma, true);
-		return -ESHUTDOWN;
+		if (list_empty(&ep_ring->td_list)) {
+			xhci_warn(xhci, "WARN Event TRB for slot %u ep %d comp_code %u with no TDs queued?\n",
+					slot_id, ep_index, trb_comp_code);
+		} else {
+			xhci_err(xhci, "ERROR Transfer event TRB DMA ptr not part of current TD ep_index %d comp_code %u\n",
+					ep_index, trb_comp_code);
+			trb_in_td(xhci, td, ep_trb_dma, true);
+		}
+
+		/*
+		 * Bugs (in HW or SW) may cause the xHC to execute transfers as
+		 * they are being cancelled and forgotten about. Then we get some
+		 * event and have no idea which transfer caused it. If the event
+		 * indicates that the EP halted, try to fix that at least.
+		 */
+		if (xhci_halted_host_endpoint(ep_ctx, trb_comp_code))
+			xhci_handle_halted_endpoint(xhci, ep, td, EP_HARD_RESET);
+
+		return 0;
 	}
 
 	if (trb_comp_code == COMP_SHORT_PACKET)
@@ -2887,16 +2880,6 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 	ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma) / sizeof(*ep_trb)];
 	trace_xhci_handle_transfer(ep_ring, (struct xhci_generic_trb *) ep_trb);
 
-	/*
-	 * No-op TRB could trigger interrupts in a case where a URB was killed
-	 * and a STALL_ERROR happens right after the endpoint ring stopped.
-	 * Reset the halted endpoint. Otherwise, the endpoint remains stalled
-	 * indefinitely.
-	 */
-
-	if (trb_is_noop(ep_trb))
-		goto check_endpoint_halted;
-
 	td->status = status;
 
 	/* update the urb's actual_length and give back to the core */
@@ -2906,11 +2889,6 @@  static int handle_tx_event(struct xhci_hcd *xhci,
 		process_isoc_td(xhci, ep, ep_ring, td, ep_trb, event);
 	else
 		process_bulk_intr_td(xhci, ep, ep_ring, td, ep_trb, event);
-	return 0;
-
-check_endpoint_halted:
-	if (xhci_halted_host_endpoint(ep_ctx, trb_comp_code))
-		xhci_handle_halted_endpoint(xhci, ep, td, EP_HARD_RESET);
 
 	return 0;