For multi-TRB transfers a SHORT completion may occur on an intermediate
TRB, e.g. when a buffer is split due to the xHCI 64KB boundary
restriction.
In that case the residual value reported in the completion event applies
to the TRB that generated the event rather than to the entire TD.
The original implementation used the residual value as though it
referred to the whole transfer, which for intermediate SHORT completions
results in an incorrect transferred length calculation.

Add a helper function to map the completion event DMA address back to
the corresponding TRB and use that TRB's transfer length to calculate
the number of bytes transferred.

Signed-off-by: Wouter van Herpen <[email protected]>
---
 drivers/usb/host/xhci-ring.c | 52 +++++++++++++++++++++++++++++++++---
 1 file changed, 49 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 6f5c4ec97b6..721a97542a3 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -42,6 +42,21 @@ dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg,
        return seg->dma + (segment_offset * sizeof(*trb));
 }
 
+static union xhci_trb *xhci_dma_to_trb(struct xhci_segment *start_seg, 
dma_addr_t dma)
+{
+       struct xhci_segment *seg = start_seg;
+
+       if (!seg)
+               return NULL;
+
+       do {
+               if (dma >= seg->dma && dma < seg->dma + SEGMENT_SIZE)
+                       return &seg->trbs[(dma - seg->dma) / sizeof(union 
xhci_trb)];
+               seg = seg->next;
+       } while (seg && seg != start_seg);
+       return NULL;
+}
+
 /**
  * Is this TRB a link TRB or was the last TRB the last TRB in this event ring
  * segment?  I.e. would the updated event TRB pointer step off the end of the
@@ -658,6 +673,11 @@ int xhci_bulk_tx(struct usb_device *udev, unsigned long 
pipe,
        dma_addr_t last_transfer_trb_addr;
        int available_length;
 
+       union xhci_trb *completed_trb;
+       u32 residual;
+       u32 completed_trb_len;
+       dma_addr_t completed_trb_dma;
+
        debug("dev=%p, pipe=%lx, buffer=%p, length=%d\n",
                udev, pipe, buffer, length);
 
@@ -811,10 +831,36 @@ again:
                return -ETIMEDOUT;
        }
 
-       if ((uintptr_t)(le64_to_cpu(event->trans_event.buffer)) !=
+       completed_trb_dma = (dma_addr_t)le64_to_cpu(event->trans_event.buffer);
+       if ((uintptr_t)completed_trb_dma !=
            (uintptr_t)last_transfer_trb_addr) {
-               available_length -=
-                       
(int)EVENT_TRB_LEN(le32_to_cpu(event->trans_event.transfer_len));
+               residual = 
EVENT_TRB_LEN(le32_to_cpu(event->trans_event.transfer_len));
+               /*
+                * For multi-TRB transfers a SHORT completion may occur on an
+                * intermediate TRB. The residual value then applies to the
+                * TRB that generated the completion event, not the entire TD.
+                */
+               if (GET_COMP_CODE(le32_to_cpu(event->trans_event.transfer_len))
+                       == COMP_SHORT_TX) {
+                       /*
+                        * Trace the completion event back to the corresponding
+                        * TRB; use its length to calculate bytes transferred.
+                        */
+                       completed_trb = xhci_dma_to_trb(ring->first_seg, 
completed_trb_dma);
+                       if (completed_trb) {
+                               u32 trb_status = 
le32_to_cpu(completed_trb->generic.field[2]);
+
+                               completed_trb_len = TRB_LEN(trb_status);
+                               available_length = completed_trb_len - residual;
+                       } else {
+                               printf("xhci: unable to locate TRB for 
dma=0x%llx\n",
+                                      (unsigned long long)completed_trb_dma);
+                               return -EIO;
+                       }
+               } else {
+                       available_length -= residual;
+               }
+
                xhci_acknowledge_event(ctrl);
                goto again;
        }
-- 
2.39.2

Reply via email to