summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmad Fatoum <a.fatoum@pengutronix.de>2024-02-19 14:38:29 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2024-02-20 12:07:03 +0100
commitbe01454be27a4aff1c66d27b30aebb5af0322eeb (patch)
treed7fc288e916c6183676b1c673beb291cefaee0f4
parent6fba5a3b0bd7a3ee6283146aa1fed220b33f9808 (diff)
downloadbarebox-be01454be27a.tar.gz
barebox-be01454be27a.tar.xz
usb: xhci: reset endpoint on USB stall
This ports U-Boot commit d5daa02d8d9e7c403a3339db1966e8413e64e408: | Author: Stefan Agner <stefan@agner.ch> | AuthorDate: Mon Sep 27 14:42:58 2021 +0200 | | usb: xhci: reset endpoint on USB stall | | There are devices which cause a USB stall when trying to read strings. | Specifically Arduino Mega R3 stalls when trying to read the product | string. | | The stall currently remains unhandled, and subsequent retries submit new | transfers on a stopped endpoint which ultimately cause a crash in | abort_td(): | WARN halted endpoint, queueing URB anyway. | XHCI control transfer timed out, aborting... | Unexpected XHCI event TRB, skipping... (3affe040 00000000 13000000 02008401) | BUG at drivers/usb/host/xhci-ring.c:505/abort_td()! | BUG! | resetting ... | | Linux seems to be able to recover from the stall by issuing a | TRB_RESET_EP command. | | Introduce reset_ep() which issues a TRB_RESET_EP followed by setting the | transfer ring dequeue pointer via TRB_SET_DEQ. This allows to properly | recover from a USB stall error and continue communicating with the USB | device. | | Signed-off-by: Stefan Agner <stefan@agner.ch> Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de> Link: https://lore.barebox.org/20240219133835.3886399-10-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r--drivers/usb/host/xhci-ring.c40
1 files changed, 40 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index c645285da7..0566134453 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -501,6 +501,42 @@ union xhci_trb *xhci_wait_for_event(struct xhci_ctrl *ctrl, trb_type expected,
}
/*
+ * Send reset endpoint command for given endpoint. This recovers from a
+ * halted endpoint (e.g. due to a stall error).
+ */
+static void reset_ep(struct usb_device *udev, int ep_index, unsigned int timeout_ms)
+{
+ struct xhci_ctrl *ctrl = xhci_get_ctrl(udev);
+ struct xhci_ring *ring = ctrl->devs[udev->slot_id]->eps[ep_index].ring;
+ union xhci_trb *event;
+ u64 addr;
+ u32 field;
+
+ dev_info(&udev->dev, "Resetting EP %d...\n", ep_index);
+
+ xhci_queue_command(ctrl, 0, udev->slot_id, ep_index, TRB_RESET_EP);
+ event = xhci_wait_for_event(ctrl, TRB_COMPLETION, timeout_ms);
+ if (!event)
+ return;
+
+ field = le32_to_cpu(event->trans_event.flags);
+ BUG_ON(TRB_TO_SLOT_ID(field) != udev->slot_id);
+ xhci_acknowledge_event(ctrl);
+
+ addr = xhci_trb_virt_to_dma(ring->enq_seg,
+ (void *)((uintptr_t)ring->enqueue | ring->cycle_state));
+ xhci_queue_command(ctrl, addr, udev->slot_id, ep_index, TRB_SET_DEQ);
+ event = xhci_wait_for_event(ctrl, TRB_COMPLETION, timeout_ms);
+ if (!event)
+ return;
+
+ BUG_ON(TRB_TO_SLOT_ID(le32_to_cpu(event->event_cmd.flags))
+ != udev->slot_id || GET_COMP_CODE(le32_to_cpu(
+ event->event_cmd.status)) != COMP_SUCCESS);
+ xhci_acknowledge_event(ctrl);
+}
+
+/*
* Stops transfer processing for an endpoint and throws away all unprocessed
* TRBs by setting the xHC's dequeue pointer to our enqueue pointer. The next
* xhci_bulk_tx/xhci_ctrl_tx on this enpoint will add new transfers there and
@@ -979,6 +1015,10 @@ int xhci_ctrl_tx(struct usb_device *udev, unsigned long pipe,
record_transfer_result(udev, event, length);
xhci_acknowledge_event(ctrl);
+ if (udev->status == USB_ST_STALLED) {
+ reset_ep(udev, ep_index, timeout_ms);
+ return -EPIPE;
+ }
/* Invalidate buffer to make it available to usb-core */
if (length > 0)