mostly refactoring, but does also change error message if we get a bad length in the event trb.
Adds a helper function to sum trb lengths of partial TDs Signed-off-by: Mathias Nyman <mathias.ny...@linux.intel.com> --- drivers/usb/host/xhci-ring.c | 108 ++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 62 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 65b11a3..6ff7d33 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1919,6 +1919,21 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, return ret; } +/* sum trb lengths from ring dequeue up to stop_trb, _excluding_ stop_trb */ +static int sum_trb_lengths(struct xhci_hcd *xhci, struct xhci_ring *ring, + union xhci_trb *stop_trb) +{ + u32 sum; + union xhci_trb *trb = ring->dequeue; + struct xhci_segment *seg = ring->deq_seg; + + for (sum = 0; trb != stop_trb; next_trb(xhci, ring, &seg, &trb)) { + if (!trb_is_noop(trb) && !trb_is_link(trb)) + sum += TRB_LEN(le32_to_cpu(trb->generic.field[2])); + } + return sum; +} + /* * Process control tds, update urb status and actual_length. */ @@ -2140,88 +2155,57 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xhci_virt_ep *ep, int *status) { struct xhci_ring *ep_ring; - union xhci_trb *cur_trb; - struct xhci_segment *cur_seg; u32 trb_comp_code; + u32 remaining, requested, event_trb_len; ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + event_trb_len = TRB_LEN(le32_to_cpu(event_trb->generic.field[2])); + requested = td->urb->transfer_buffer_length; switch (trb_comp_code) { case COMP_SUCCESS: - /* Double check that the HW transferred everything. */ - if (event_trb != td->last_trb || - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) != 0) { + /* handle success with untransferred data as short packet */ + if (event_trb != td->last_trb || remaining) { xhci_warn(xhci, "WARN Successful completion on short TX\n"); - if ((xhci->quirks & XHCI_TRUST_TX_LENGTH)) - trb_comp_code = COMP_SHORT_TX; + xhci_dbg(xhci, "ep %#x - asked for %d bytes, %d bytes untransferred\n", + td->urb->ep->desc.bEndpointAddress, + requested, remaining); } *status = 0; break; - case COMP_STOP_SHORT: case COMP_SHORT_TX: + xhci_dbg(xhci, "ep %#x - asked for %d bytes, %d bytes untransferred\n", + td->urb->ep->desc.bEndpointAddress, + requested, remaining); *status = 0; break; + case COMP_STOP_SHORT: + td->urb->actual_length = remaining; + goto finish_td; + case COMP_STOP_INVAL: + /* stopped on ep trb with invalid length, exclude it */ + event_trb_len = 0; + remaining = 0; + break; default: - /* Others already handled above */ + /* do nothing */ break; } - if (trb_comp_code == COMP_SHORT_TX) - xhci_dbg(xhci, "ep %#x - asked for %d bytes, " - "%d bytes untransferred\n", - td->urb->ep->desc.bEndpointAddress, - td->urb->transfer_buffer_length, - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len))); - /* Stopped - short packet completion */ - if (trb_comp_code == COMP_STOP_SHORT) { - td->urb->actual_length = - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); - if (td->urb->transfer_buffer_length < - td->urb->actual_length) { - xhci_warn(xhci, "HC gave bad length of %d bytes txed\n", - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len))); - td->urb->actual_length = 0; - /* status will be set by usb core for canceled urbs */ - } - /* Fast path - was this the last TRB in the TD for this URB? */ - } else if (event_trb == td->last_trb) { - if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) != 0) { - td->urb->actual_length = - td->urb->transfer_buffer_length - - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); - if (td->urb->transfer_buffer_length < - td->urb->actual_length) { - xhci_warn(xhci, "HC gave bad length of %d bytes left\n", - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len))); - td->urb->actual_length = 0; - *status = 0; - } - /* Don't overwrite a previously set error code */ - if (*status == -EINPROGRESS) - *status = 0; - } - } else { - /* Slow path - walk the list, starting from the dequeue - * pointer, to get the actual length transferred. - */ + if (event_trb == td->last_trb) + td->urb->actual_length = requested - remaining; + else + td->urb->actual_length = + sum_trb_lengths(xhci, ep_ring, event_trb) + + event_trb_len - remaining; +finish_td: + if (remaining > requested) { + xhci_warn(xhci, "bad transfer trb length %d in event trb\n", + remaining); td->urb->actual_length = 0; - for (cur_trb = ep_ring->dequeue, cur_seg = ep_ring->deq_seg; - cur_trb != event_trb; - next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) { - if (!trb_is_noop(cur_trb) && !trb_is_link(cur_trb)) - td->urb->actual_length += - TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])); - } - /* If the ring didn't stop on a Link or No-op TRB, add - * in the actual bytes transferred from the Normal TRB - */ - if (trb_comp_code != COMP_STOP_INVAL) - td->urb->actual_length += - TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])) - - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); } - return finish_td(xhci, td, event_trb, event, ep, status, false); } -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html