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
