diff options
author | Stefan Agner | 2021-09-27 14:42:58 +0200 |
---|---|---|
committer | Marek Vasut | 2022-02-16 17:11:31 +0100 |
commit | d5daa02d8d9e7c403a3339db1966e8413e64e408 (patch) | |
tree | 0a9cb49242da3eb315b4e459cb474c47a911b028 /drivers/usb/host | |
parent | fb146fbc1ae551ce3bdb2966dd36a0b38e62b5dc (diff) |
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>
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/xhci-ring.c | 31 |
1 files changed, 31 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 0b3e7a2f550..eb6dfcdb09e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -482,6 +482,33 @@ 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) +{ + 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; + u32 field; + + printf("Resetting EP %d...\n", ep_index); + xhci_queue_command(ctrl, NULL, udev->slot_id, ep_index, TRB_RESET_EP); + event = xhci_wait_for_event(ctrl, TRB_COMPLETION); + field = le32_to_cpu(event->trans_event.flags); + BUG_ON(TRB_TO_SLOT_ID(field) != udev->slot_id); + xhci_acknowledge_event(ctrl); + + xhci_queue_command(ctrl, (void *)((uintptr_t)ring->enqueue | + ring->cycle_state), udev->slot_id, ep_index, TRB_SET_DEQ); + event = xhci_wait_for_event(ctrl, TRB_COMPLETION); + 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 @@ -928,6 +955,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); + return -EPIPE; + } /* Invalidate buffer to make it available to usb-core */ if (length > 0) |