summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Wildt <patrick@cvs.openbsd.org>2019-11-18 20:08:50 +0000
committerPatrick Wildt <patrick@cvs.openbsd.org>2019-11-18 20:08:50 +0000
commit487ef54b0376dfbcc776a0a1f9fbdad7820c2084 (patch)
treead5fcaf869c46c84402f70856f193613798be384
parent4be8d94c013aa05efe90ccaa5c92fbf14d9a66fd (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.c45
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