On Tue, Nov 12, 2019 at 10:45:39AM +0100, Gerhard Roth wrote:
> Hi,
> 
> xhci's calculation of 'xfer->actlen' is wrong if the xfer was split into
> multiple TRBs. That's because the code just looks at the remainder
> reported by the status TRB. However, this remainder only refers to the
> total size of this single TRB; not to the total size of the xfer.
> 
> Example: assume a 16 kb xfer is split into two TRBs for 8 kb each.
>       Then we get a short read of 6 kb. The status TRB will refer
>       to the first TRB with remainder set to 2 kb. Currently xhci
>       would assume that actlen is 'xfer->length' (16 kb) minus the
>       remainder (2 kb). That yields a wrong actlen of 14 kb instead
>       of 6 kb.
> 
> The patch below fixes this by adding up all the remainders of all the
> transfer TRBs up to the current one.
> 
> Gerhard
> 

Very goood catch, this is very similar to the issue we had with isoc
transfers.  Diff looks good to me!

> 
> Index: sys/dev/usb/xhci.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/usb/xhci.c,v
> retrieving revision 1.106
> diff -u -p -u -p -r1.106 xhci.c
> --- sys/dev/usb/xhci.c        6 Oct 2019 17:30:00 -0000       1.106
> +++ sys/dev/usb/xhci.c        12 Nov 2019 09:29:47 -0000
> @@ -810,6 +810,28 @@ xhci_event_xfer(struct xhci_softc *sc, u
>       xhci_xfer_done(xfer);
>  }
>  
> +uint32_t
> +xhci_xfer_length_generic(struct xhci_xfer *xx, struct xhci_pipe *xp,
> +    int trb_idx)
> +{
> +     int      trb0_idx;
> +     uint32_t len = 0;
> +
> +     KASSERT(xx->index >= 0);
> +     trb0_idx =
> +         ((xx->index + xp->ring.ntrb) - xx->ntrb) % (xp->ring.ntrb - 1);
> +
> +     while (1) {
> +             len +=
> +                 le32toh(XHCI_TRB_LEN(xp->ring.trbs[trb0_idx].trb_status));
> +             if (trb0_idx == trb_idx)
> +                     break;
> +             if (++trb0_idx == xp->ring.ntrb)
> +                     trb0_idx = 0;
> +     }
> +     return len;
> +}
> +
>  int
>  xhci_event_xfer_generic(struct xhci_softc *sc, struct usbd_xfer *xfer,
>      struct xhci_pipe *xp, uint32_t remain, int trb_idx,
> @@ -823,12 +845,22 @@ xhci_event_xfer_generic(struct xhci_soft
>                * This might be the last TRB of a TD that ended up
>                * with a Short Transfer condition, see below.
>                */
> -             if (xfer->actlen == 0)
> -                     xfer->actlen = xfer->length - remain;
> +             if (xfer->actlen == 0) {
> +                     if (remain)
> +                             xfer->actlen =
> +                                 xhci_xfer_length_generic(xx, xp, trb_idx) -
> +                                 remain;
> +                     else
> +                             xfer->actlen = xfer->length;
> +             }
>               xfer->status = USBD_NORMAL_COMPLETION;
>               break;
>       case XHCI_CODE_SHORT_XFER:
> -             xfer->actlen = xfer->length - remain;
> +             /*
> +              * Use values from the transfer TRB instead of the status TRB.
> +              */
> +             xfer->actlen = xhci_xfer_length_generic(xx, xp, trb_idx) -
> +                 remain;
>               /*
>                * If this is not the last TRB of a transfer, we should
>                * theoretically clear the IOC at the end of the chain
> 

Reply via email to