xhci-hcd: support soft retry on SS transfer error
JC Kuo [Sun, 20 Dec 2015 06:37:02 +0000 (14:37 +0800)]
This commit implements XHCI "soft retry" for SuperSpeed endpoints which
encounters transfer errors.
When transfer error happens on an SuperSpeed endpoint, XHCI driver will
1. queue a "reset endpoint" command with TSP=1 (Transfer State Preserve)

2. invoke a HCD driver specific callback "->endpoint_soft_retry()" to let
HCD driver has a chance to configure its hardware

3. ring door bell for the endpoint upon seeing the command completion

bug 200162414

Change-Id: I19df13614f794437c7f4d4369dbdaed13e1da85a
Signed-off-by: JC Kuo <jckuo@nvidia.com>
Reviewed-on: http://git-master/r/1169995
GVS: Gerrit_Virtual_Submit
Reviewed-by: ChihMin Cheng <ccheng@nvidia.com>
Tested-by: Mark Kuo <mkuo@nvidia.com>
Reviewed-by: Ashutosh Jha <ajha@nvidia.com>

drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h
include/linux/usb/hcd.h

index cd6e255..5f12cd7 100644 (file)
@@ -1187,6 +1187,13 @@ static void handle_reset_ep_completion(struct xhci_hcd *xhci,
        } else {
                /* Clear our internal halted state */
                xhci->devs[slot_id]->eps[ep_index].ep_state &= ~EP_HALTED;
+
+               /* ring doorbell for the endpoint under soft-retry */
+               if (TRB_TSP | le32_to_cpu(trb->generic.field[3])) {
+                       xhci_dbg(xhci, "Ring doorbell for slot_id %d ep_index 0x%x\n",
+                                slot_id, ep_index);
+                       xhci_ring_ep_doorbell(xhci, slot_id, ep_index, 0);
+               }
        }
 }
 
@@ -2387,6 +2394,29 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td,
        return finish_td(xhci, td, event_trb, event, ep, status, false);
 }
 
+static void xhci_endpoint_soft_retry(struct xhci_hcd *xhci, unsigned slot_id,
+                       unsigned dci, bool on)
+{
+       struct xhci_virt_device *xdev = xhci->devs[slot_id];
+       struct usb_host_endpoint *ep;
+
+       if (!xhci->shared_hcd || !xhci->shared_hcd->driver
+                       || !xhci->shared_hcd->driver->endpoint_soft_retry)
+               return;
+
+       if (xdev->udev->speed != USB_SPEED_SUPER)
+               return;
+
+       if (dci & 0x1)
+               ep = xdev->udev->ep_in[(dci - 1)/2];
+       else
+               ep = xdev->udev->ep_out[dci/2];
+
+       if (!ep)
+               return;
+
+       xhci->shared_hcd->driver->endpoint_soft_retry(xhci->shared_hcd, ep, on);
+}
 /*
  * If this function returns an error condition, it means it got a Transfer
  * event with a corrupted Slot ID, Endpoint ID, or TRB DMA address.
@@ -2471,13 +2501,21 @@ static int handle_tx_event(struct xhci_hcd *xhci,
         */
        case COMP_SUCCESS:
                if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) == 0)
-                       break;
+                       goto check_soft_try;
                if (xhci->quirks & XHCI_TRUST_TX_LENGTH)
                        trb_comp_code = COMP_SHORT_TX;
                else
                        xhci_warn_ratelimited(xhci,
                                        "WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?\n");
        case COMP_SHORT_TX:
+check_soft_try:
+               if (ep_ring->soft_try) {
+                       xhci_dbg(xhci, "soft retry completed successfully\n");
+                       ep_ring->soft_try = false;
+                       xhci_endpoint_soft_retry(xhci,
+                                               slot_id, ep_index + 1, false);
+               }
+
                break;
        case COMP_STOP:
                xhci_dbg(xhci, "Stopped on Transfer TRB\n");
@@ -2495,8 +2533,33 @@ static int handle_tx_event(struct xhci_hcd *xhci,
                status = -EILSEQ;
                break;
        case COMP_SPLIT_ERR:
+               xhci_dbg(xhci, "Transfer error on endpoint\n");
+               status = -EPROTO;
+               break;
        case COMP_TX_ERR:
                xhci->xhci_ereport.comp_tx_err++;
+
+               if (xdev->udev->speed == USB_SPEED_SUPER &&
+                                               ep_ring->type != TYPE_ISOC) {
+                       if (!ep_ring->soft_try) {
+                               xhci_dbg(xhci, "SuperSpeed transfer error, do soft retry\n");
+                               ret = xhci_queue_soft_retry(xhci,
+                                               slot_id, ep_index);
+                               if (!ret) {
+                                       xhci_endpoint_soft_retry(xhci,
+                                               slot_id, ep_index + 1, true);
+                                       xhci_ring_cmd_db(xhci);
+                                       ep_ring->soft_try = true;
+                                       goto cleanup;
+                               }
+                       } else {
+                               xhci_dbg(xhci, "soft retry complete but transfer still failed\n");
+                               ep_ring->soft_try = false;
+                       }
+                       xhci_endpoint_soft_retry(xhci,
+                                               slot_id, ep_index + 1, false);
+               }
+
                xhci_dbg(xhci, "Transfer error on endpoint\n");
                status = -EPROTO;
                break;
@@ -4169,3 +4232,14 @@ int xhci_queue_reset_ep(struct xhci_hcd *xhci, int slot_id,
        return queue_command(xhci, 0, 0, 0, trb_slot_id | trb_ep_index | type,
                        false);
 }
+
+int xhci_queue_soft_retry(struct xhci_hcd *xhci, int slot_id,
+               unsigned int ep_index)
+{
+       u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id);
+       u32 trb_ep_index = EP_ID_FOR_TRB(ep_index);
+       u32 type = TRB_TYPE(TRB_RESET_EP) | TRB_TSP;
+
+       return queue_command(xhci, 0, 0, 0, trb_slot_id | trb_ep_index | type,
+                       false);
+}
index 6016af0..f000873 100644 (file)
@@ -1129,6 +1129,8 @@ struct xhci_event_cmd {
 
 /* Block Event Interrupt */
 #define        TRB_BEI                 (1<<9)
+/* Transfer State Preserve */
+#define TRB_TSP                        (1<<9)
 
 /* Control transfer TRB specific fields */
 #define TRB_DIR_IN             (1<<16)
@@ -1316,6 +1318,7 @@ struct xhci_ring {
        unsigned int            num_trbs_free_temp;
        enum xhci_ring_type     type;
        bool                    last_td_was_short;
+       bool                    soft_try;
 };
 
 struct xhci_erst_entry {
@@ -1821,6 +1824,8 @@ int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr,
                u32 slot_id, bool command_must_succeed);
 int xhci_queue_reset_ep(struct xhci_hcd *xhci, int slot_id,
                unsigned int ep_index);
+int xhci_queue_soft_retry(struct xhci_hcd *xhci, int slot_id,
+               unsigned int ep_index);
 int xhci_queue_reset_device(struct xhci_hcd *xhci, u32 slot_id);
 void xhci_find_new_dequeue_state(struct xhci_hcd *xhci,
                unsigned int slot_id, unsigned int ep_index,
index 8669452..8574c5e 100644 (file)
@@ -363,6 +363,11 @@ struct hc_driver {
        int     (*disable_usb3_lpm_timeout)(struct usb_hcd *,
                        struct usb_device *, enum usb3_link_state state);
        int     (*find_raw_port_number)(struct usb_hcd *, int);
+       /* (optional) called from xhci ISR before (on=1) and after (on=0)
+          soft retry. It gives HCD driver a chance to configure it hardware to
+          deal with intermittent SuperSpeed transfer errors */
+       void    (*endpoint_soft_retry)(struct usb_hcd *hcd,
+                       struct usb_host_endpoint *ep, bool on);
 };
 
 extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb);