diff options
author | Patrick Wildt <patrick@cvs.openbsd.org> | 2019-11-18 20:08:50 +0000 |
---|---|---|
committer | Patrick Wildt <patrick@cvs.openbsd.org> | 2019-11-18 20:08:50 +0000 |
commit | 487ef54b0376dfbcc776a0a1f9fbdad7820c2084 (patch) | |
tree | ad5fcaf869c46c84402f70856f193613798be384 | |
parent | 4be8d94c013aa05efe90ccaa5c92fbf14d9a66fd (diff) |
Fix actual length calculation of short transfers in xhci(4). So far
we have subtracted the remaining length from the total transfer
length, which essentially means that we assume that all TRBs have
successfully been transferred apart from the remainder. Actually
we might get a short completion in the middle of a chain of TRBs,
which means that all TRBs until this TRB have completed successfully
apart from a remainder. Thus we have to count the length of all
TRBs until and including the one that we went short on, and remove
the remainder. All following TRBs in the same transfer must be
ignored.
Found by and fixed with gerhard@
ok mglocker@
-rw-r--r-- | sys/dev/usb/xhci.c | 45 |
1 files changed, 37 insertions, 8 deletions
diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c index f9d7eb3a401..0e27cacc34c 100644 --- a/sys/dev/usb/xhci.c +++ b/sys/dev/usb/xhci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: xhci.c,v 1.106 2019/10/06 17:30:00 mpi Exp $ */ +/* $OpenBSD: xhci.c,v 1.107 2019/11/18 20:08:49 patrick Exp $ */ /* * Copyright (c) 2014-2015 Martin Pieuchot @@ -810,6 +810,29 @@ xhci_event_xfer(struct xhci_softc *sc, uint64_t paddr, uint32_t status, 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, type; + + trb0_idx = + ((xx->index + xp->ring.ntrb) - xx->ntrb) % (xp->ring.ntrb - 1); + + while (1) { + type = xp->ring.trbs[trb0_idx].trb_flags & XHCI_TRB_TYPE_MASK; + if (type == XHCI_TRB_TYPE_NORMAL || type == XHCI_TRB_TYPE_DATA) + 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, @@ -819,16 +842,22 @@ xhci_event_xfer_generic(struct xhci_softc *sc, struct usbd_xfer *xfer, switch (code) { case XHCI_CODE_SUCCESS: - /* - * 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 |