xhci: process isoc TD properly when there was a transaction error mid TD.
[platform/kernel/linux-starfive.git] / drivers / usb / host / xhci-ring.c
index 1dde53f..bd7b541 100644 (file)
@@ -798,7 +798,7 @@ static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci,
 static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci,
                struct xhci_ring *ring, struct xhci_td *td)
 {
-       struct device *dev = xhci_to_hcd(xhci)->self.controller;
+       struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
        struct xhci_segment *seg = td->bounce_seg;
        struct urb *urb = td->urb;
        size_t len;
@@ -2377,6 +2377,9 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
        /* handle completion code */
        switch (trb_comp_code) {
        case COMP_SUCCESS:
+               /* Don't overwrite status if TD had an error, see xHCI 4.9.1 */
+               if (td->error_mid_td)
+                       break;
                if (remaining) {
                        frame->status = short_framestatus;
                        if (xhci->quirks & XHCI_TRUST_TX_LENGTH)
@@ -2402,8 +2405,9 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
                break;
        case COMP_USB_TRANSACTION_ERROR:
                frame->status = -EPROTO;
+               sum_trbs_for_length = true;
                if (ep_trb != td->last_trb)
-                       return 0;
+                       td->error_mid_td = true;
                break;
        case COMP_STOPPED:
                sum_trbs_for_length = true;
@@ -2423,6 +2427,9 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
                break;
        }
 
+       if (td->urb_length_set)
+               goto finish_td;
+
        if (sum_trbs_for_length)
                frame->actual_length = sum_trb_lengths(xhci, ep->ring, ep_trb) +
                        ep_trb_len - remaining;
@@ -2431,6 +2438,14 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
 
        td->urb->actual_length += frame->actual_length;
 
+finish_td:
+       /* Don't give back TD yet if we encountered an error mid TD */
+       if (td->error_mid_td && ep_trb != td->last_trb) {
+               xhci_dbg(xhci, "Error mid isoc TD, wait for final completion event\n");
+               td->urb_length_set = true;
+               return 0;
+       }
+
        return finish_td(xhci, ep, ep_ring, td, trb_comp_code);
 }
 
@@ -2809,17 +2824,51 @@ static int handle_tx_event(struct xhci_hcd *xhci,
                }
 
                if (!ep_seg) {
-                       if (!ep->skip ||
-                           !usb_endpoint_xfer_isoc(&td->urb->ep->desc)) {
-                               /* Some host controllers give a spurious
-                                * successful event after a short transfer.
-                                * Ignore it.
-                                */
-                               if ((xhci->quirks & XHCI_SPURIOUS_SUCCESS) &&
-                                               ep_ring->last_td_was_short) {
-                                       ep_ring->last_td_was_short = false;
-                                       goto cleanup;
+
+                       if (ep->skip && usb_endpoint_xfer_isoc(&td->urb->ep->desc)) {
+                               skip_isoc_td(xhci, td, ep, status);
+                               goto cleanup;
+                       }
+
+                       /*
+                        * Some hosts give a spurious success event after a short
+                        * transfer. Ignore it.
+                        */
+                       if ((xhci->quirks & XHCI_SPURIOUS_SUCCESS) &&
+                           ep_ring->last_td_was_short) {
+                               ep_ring->last_td_was_short = false;
+                               goto cleanup;
+                       }
+
+                       /*
+                        * xhci 4.10.2 states isoc endpoints should continue
+                        * processing the next TD if there was an error mid TD.
+                        * So host like NEC don't generate an event for the last
+                        * isoc TRB even if the IOC flag is set.
+                        * xhci 4.9.1 states that if there are errors in mult-TRB
+                        * TDs xHC should generate an error for that TRB, and if xHC
+                        * proceeds to the next TD it should genete an event for
+                        * any TRB with IOC flag on the way. Other host follow this.
+                        * So this event might be for the next TD.
+                        */
+                       if (td->error_mid_td &&
+                           !list_is_last(&td->td_list, &ep_ring->td_list)) {
+                               struct xhci_td *td_next = list_next_entry(td, td_list);
+
+                               ep_seg = trb_in_td(xhci, td_next->start_seg, td_next->first_trb,
+                                                  td_next->last_trb, ep_trb_dma, false);
+                               if (ep_seg) {
+                                       /* give back previous TD, start handling new */
+                                       xhci_dbg(xhci, "Missing TD completion event after mid TD error\n");
+                                       ep_ring->dequeue = td->last_trb;
+                                       ep_ring->deq_seg = td->last_trb_seg;
+                                       inc_deq(xhci, ep_ring);
+                                       xhci_td_cleanup(xhci, td, ep_ring, td->status);
+                                       td = td_next;
                                }
+                       }
+
+                       if (!ep_seg) {
                                /* HC is busted, give up! */
                                xhci_err(xhci,
                                        "ERROR Transfer event TRB DMA ptr not "
@@ -2831,9 +2880,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
                                          ep_trb_dma, true);
                                return -ESHUTDOWN;
                        }
-
-                       skip_isoc_td(xhci, td, ep, status);
-                       goto cleanup;
                }
                if (trb_comp_code == COMP_SHORT_PACKET)
                        ep_ring->last_td_was_short = true;
@@ -2996,7 +3042,8 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
  */
 static void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
                                     struct xhci_interrupter *ir,
-                                    union xhci_trb *event_ring_deq)
+                                    union xhci_trb *event_ring_deq,
+                                    bool clear_ehb)
 {
        u64 temp_64;
        dma_addr_t deq;
@@ -3017,12 +3064,13 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
                        return;
 
                /* Update HC event ring dequeue pointer */
-               temp_64 &= ERST_PTR_MASK;
+               temp_64 &= ERST_DESI_MASK;
                temp_64 |= ((u64) deq & (u64) ~ERST_PTR_MASK);
        }
 
        /* Clear the event handler busy flag (RW1C) */
-       temp_64 |= ERST_EHB;
+       if (clear_ehb)
+               temp_64 |= ERST_EHB;
        xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue);
 }
 
@@ -3103,7 +3151,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
        while (xhci_handle_event(xhci, ir) > 0) {
                if (event_loop++ < TRBS_PER_SEGMENT / 2)
                        continue;
-               xhci_update_erst_dequeue(xhci, ir, event_ring_deq);
+               xhci_update_erst_dequeue(xhci, ir, event_ring_deq, false);
                event_ring_deq = ir->event_ring->dequeue;
 
                /* ring is half-full, force isoc trbs to interrupt more often */
@@ -3113,7 +3161,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
                event_loop = 0;
        }
 
-       xhci_update_erst_dequeue(xhci, ir, event_ring_deq);
+       xhci_update_erst_dequeue(xhci, ir, event_ring_deq, true);
        ret = IRQ_HANDLED;
 
 out:
@@ -3469,7 +3517,7 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred,
 static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
                         u32 *trb_buff_len, struct xhci_segment *seg)
 {
-       struct device *dev = xhci_to_hcd(xhci)->self.controller;
+       struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
        unsigned int unalign;
        unsigned int max_pkt;
        u32 new_buff_len;