summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/usb/xhci.c251
1 files changed, 193 insertions, 58 deletions
diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c
index f234c7617f1..45aff8c94ef 100644
--- a/sys/dev/usb/xhci.c
+++ b/sys/dev/usb/xhci.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: xhci.c,v 1.96 2019/03/15 23:09:23 patrick Exp $ */
+/* $OpenBSD: xhci.c,v 1.97 2019/03/15 23:20:35 patrick Exp $ */
/*
* Copyright (c) 2014-2015 Martin Pieuchot
@@ -701,7 +701,8 @@ xhci_event_xfer(struct xhci_softc *sc, uint64_t paddr, uint32_t status,
struct xhci_xfer *xx;
uint8_t dci, slot, code;
uint32_t remain;
- int trb_idx;
+ int trb_idx, trb0_idx;
+ int frame_idx;
slot = XHCI_TRB_GET_SLOT(flags);
dci = XHCI_TRB_GET_EP(flags);
@@ -750,31 +751,120 @@ xhci_event_xfer(struct xhci_softc *sc, uint64_t paddr, uint32_t status,
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 (xp->pipe.methods != &xhci_device_isoc_methods) {
+ /*
+ * 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;
+ } else {
+ /*
+ * Complete the transfer if this is the last TRB
+ * in a TD
+ */
+ xx = (struct xhci_xfer *)xfer;
+ KASSERT(xx->index >= 0);
+ trb0_idx = ((xx->index + xp->ring.ntrb) -
+ xx->ntrb) % (xp->ring.ntrb - 1);
+
+ /* Find the according frame index for this TRB. */
+ frame_idx = 0;
+ while (trb0_idx != trb_idx) {
+ if ((xp->ring.trbs[trb0_idx].trb_flags &
+ XHCI_TRB_TYPE_MASK) == XHCI_TRB_TYPE_ISOCH)
+ frame_idx++;
+ if (trb0_idx++ == (xp->ring.ntrb - 1))
+ trb0_idx = 0;
+ }
+
+ /*
+ * If we queued two TRBs for a frame and this is
+ * the second TRB, check if the first TRB needs
+ * accounting since it might not have raised an
+ * interrupt in case of full data received.
+ */
+ if ((xp->ring.trbs[trb_idx].trb_flags &
+ XHCI_TRB_TYPE_MASK) == XHCI_TRB_TYPE_NORMAL) {
+ frame_idx--;
+ if (trb_idx == 0)
+ trb0_idx = xp->ring.ntrb - 2;
+ else
+ trb0_idx = trb_idx - 1;
+ if (xfer->frlengths[frame_idx] == 0) {
+ xfer->frlengths[frame_idx] =
+ XHCI_TRB_LEN(
+ xp->ring.trbs[trb0_idx].trb_status);
+ }
+ }
+
+ xfer->frlengths[frame_idx] += XHCI_TRB_LEN(
+ xp->ring.trbs[trb_idx].trb_status) - remain;
+ xfer->actlen += xfer->frlengths[frame_idx];
+ if (xx->index != trb_idx)
+ return;
+ }
xfer->status = USBD_NORMAL_COMPLETION;
break;
case XHCI_CODE_SHORT_XFER:
- xfer->actlen = xfer->length - remain;
+ if (xp->pipe.methods != &xhci_device_isoc_methods) {
+ xfer->actlen = xfer->length - remain;
+ /*
+ * If this is not the last TRB of a transfer, we should
+ * theoretically clear the IOC at the end of the chain
+ * but the HC might have already processed it before we
+ * had a chance to schedule the softinterrupt.
+ */
+ xx = (struct xhci_xfer *)xfer;
+ if (xx->index != trb_idx) {
+ DPRINTF(("%s: short xfer %p for %u\n",
+ DEVNAME(sc), xfer, xx->index));
+ return;
+ }
+ } else {
+ xx = (struct xhci_xfer *)xfer;
+ KASSERT(xx->index >= 0);
+ trb0_idx = ((xx->index + xp->ring.ntrb) -
+ xx->ntrb) % (xp->ring.ntrb - 1);
+
+ /* Find the according frame index for this TRB. */
+ frame_idx = 0;
+ while (trb0_idx != trb_idx) {
+ if ((xp->ring.trbs[trb0_idx].trb_flags &
+ XHCI_TRB_TYPE_MASK) == XHCI_TRB_TYPE_ISOCH)
+ frame_idx++;
+ if (trb0_idx++ == (xp->ring.ntrb - 1))
+ trb0_idx = 0;
+ }
- /*
- * If this is not the last TRB of a transfer, we should
- * theoretically clear the IOC at the end of the chain
- * but the HC might have already processed it before we
- * had a chance to schedule the softinterrupt.
- */
- xx = (struct xhci_xfer *)xfer;
- if (xx->index != trb_idx) {
- DPRINTF(("%s: short xfer %p for %u\n", DEVNAME(sc),
- xfer, xx->index));
- return;
- }
+ /*
+ * If we queued two TRBs for a frame and this is
+ * the second TRB, check if the first TRB needs
+ * accounting since it might not have raised an
+ * interrupt in case of full data received.
+ */
+ if ((xp->ring.trbs[trb_idx].trb_flags &
+ XHCI_TRB_TYPE_MASK) == XHCI_TRB_TYPE_NORMAL) {
+ frame_idx--;
+ if (trb_idx == 0)
+ trb0_idx = xp->ring.ntrb - 2;
+ else
+ trb0_idx = trb_idx - 1;
+ if (xfer->frlengths[frame_idx] == 0) {
+ xfer->frlengths[frame_idx] =
+ XHCI_TRB_LEN(
+ xp->ring.trbs[trb0_idx].trb_status);
+ }
+ }
+
+ xfer->frlengths[frame_idx] += XHCI_TRB_LEN(
+ xp->ring.trbs[trb_idx].trb_status) - remain;
+ xfer->actlen += xfer->frlengths[frame_idx];
+ if (xx->index != trb_idx)
+ return;
+ }
xfer->status = USBD_NORMAL_COMPLETION;
break;
case XHCI_CODE_TXERR:
@@ -1026,13 +1116,8 @@ xhci_pipe_open(struct usbd_pipe *pipe)
break;
case UE_ISOCHRONOUS:
-#if notyet
pipe->methods = &xhci_device_isoc_methods;
break;
-#else
- DPRINTF(("%s: isochronous xfer not supported \n", __func__));
- return (USBD_INVAL);
-#endif
case UE_BULK:
pipe->methods = &xhci_device_bulk_methods;
break;
@@ -2863,9 +2948,9 @@ xhci_device_isoc_start(struct usbd_xfer *xfer)
struct xhci_xfer *xx = (struct xhci_xfer *)xfer;
struct xhci_trb *trb0, *trb;
uint32_t len, remain, flags;
- uint64_t paddr = DMAADDR(&xfer->dmabuf, 0);
- uint32_t len0, tbc, tlbpc;
- int s, i, ntrb = xfer->nframes;
+ uint64_t paddr;
+ uint32_t tbc, tlbpc;
+ int s, i, j, ntrb = xfer->nframes;
uint8_t toggle;
KASSERT(!(xfer->rqflags & URQ_REQUEST));
@@ -2886,50 +2971,68 @@ xhci_device_isoc_start(struct usbd_xfer *xfer)
if (xx->ntrb > 0)
return (USBD_IN_PROGRESS);
+ paddr = DMAADDR(&xfer->dmabuf, 0);
+
+ /* How many TRBs do for all Transfers? */
+ for (i = 0, ntrb = 0; i < xfer->nframes; i++) {
+ /* How many TRBs do we need for this transfer? */
+ ntrb += howmany(xfer->frlengths[i], XHCI_TRB_MAXSIZE);
+
+ /* If the buffer crosses a 64k boundary, we need one more. */
+ len = XHCI_TRB_MAXSIZE - (paddr & (XHCI_TRB_MAXSIZE - 1));
+ if (len < xfer->frlengths[i])
+ ntrb++;
+
+ paddr += xfer->frlengths[i];
+ }
+
if (xp->free_trbs < ntrb)
return (USBD_NOMEM);
- len0 = xfer->frlengths[0];
+ paddr = DMAADDR(&xfer->dmabuf, 0);
- /* We'll toggle the first TRB once we're finished with the chain. */
- trb0 = xhci_xfer_get_trb(sc, xfer, &toggle, (ntrb == 1));
+ for (i = 0, trb0 = NULL; i < xfer->nframes; i++) {
+ /* How many TRBs do we need for this transfer? */
+ ntrb = howmany(xfer->frlengths[i], XHCI_TRB_MAXSIZE);
- flags = XHCI_TRB_TYPE_ISOCH | XHCI_TRB_SIA | (toggle ^ 1);
- if (usbd_xfer_isread(xfer))
- flags |= XHCI_TRB_ISP;
- flags |= (ntrb == 1) ? XHCI_TRB_IOC : XHCI_TRB_CHAIN;
+ /* If the buffer crosses a 64k boundary, we need one more. */
+ len = XHCI_TRB_MAXSIZE - (paddr & (XHCI_TRB_MAXSIZE - 1));
+ if (len < xfer->frlengths[i])
+ ntrb++;
+ else
+ len = xfer->frlengths[i];
- tbc = xhci_xfer_tbc(xfer, len0, &tlbpc);
- flags |= XHCI_TRB_ISOC_TBC(tbc) | XHCI_TRB_ISOC_TLBPC(tlbpc);
+ KASSERT(ntrb < 3);
- trb0->trb_paddr = htole64(DMAADDR(&xfer->dmabuf, 0));
- trb0->trb_status = htole32(
- XHCI_TRB_INTR(0) | XHCI_TRB_LEN(len0) |
- xhci_xfer_tdsize(xfer, xfer->length, len0)
- );
- trb0->trb_flags = htole32(flags);
- bus_dmamap_sync(xp->ring.dma.tag, xp->ring.dma.map,
- TRBOFF(&xp->ring, trb0), sizeof(struct xhci_trb),
- BUS_DMASYNC_PREWRITE);
+ /*
+ * We'll commit the first TRB once we're finished with the
+ * chain.
+ */
+ trb = xhci_xfer_get_trb(sc, xfer, &toggle, (ntrb == 1));
- remain = xfer->length - len0;
- paddr += len0;
+ DPRINTF(("%s:%d: ring %p trb0_idx %lu ntrb %d paddr %llx "
+ "len %u\n", __func__, __LINE__,
+ &xp->ring.trbs[0], (trb - &xp->ring.trbs[0]), ntrb, paddr,
+ len));
- /* Chain more TRBs if needed. */
- for (i = ntrb - 1; i > 0; i--) {
- len = xfer->frlengths[ntrb - i];
+ /* Record the first TRB so we can toggle later. */
+ if (trb0 == NULL) {
+ trb0 = trb;
+ toggle ^= 1;
+ }
- /* Next (or Last) TRB. */
- trb = xhci_xfer_get_trb(sc, xfer, &toggle, (i == 1));
- flags = XHCI_TRB_TYPE_NORMAL | toggle;
+ flags = XHCI_TRB_TYPE_ISOCH | XHCI_TRB_SIA | toggle;
if (usbd_xfer_isread(xfer))
flags |= XHCI_TRB_ISP;
- flags |= (i == 1) ? XHCI_TRB_IOC : XHCI_TRB_CHAIN;
+ flags |= (ntrb == 1) ? XHCI_TRB_IOC : XHCI_TRB_CHAIN;
+
+ tbc = xhci_xfer_tbc(xfer, xfer->frlengths[i], &tlbpc);
+ flags |= XHCI_TRB_ISOC_TBC(tbc) | XHCI_TRB_ISOC_TLBPC(tlbpc);
trb->trb_paddr = htole64(paddr);
trb->trb_status = htole32(
XHCI_TRB_INTR(0) | XHCI_TRB_LEN(len) |
- xhci_xfer_tdsize(xfer, remain, len)
+ xhci_xfer_tdsize(xfer, xfer->frlengths[i], len)
);
trb->trb_flags = htole32(flags);
@@ -2937,8 +3040,40 @@ xhci_device_isoc_start(struct usbd_xfer *xfer)
TRBOFF(&xp->ring, trb), sizeof(struct xhci_trb),
BUS_DMASYNC_PREWRITE);
- remain -= len;
+ remain = xfer->frlengths[i] - len;
paddr += len;
+
+ /* Chain more TRBs if needed. */
+ for (j = ntrb - 1; j > 0; j--) {
+ len = min(remain, XHCI_TRB_MAXSIZE);
+
+ /* Next (or Last) TRB. */
+ trb = xhci_xfer_get_trb(sc, xfer, &toggle, (j == 1));
+ flags = XHCI_TRB_TYPE_NORMAL | toggle;
+ if (usbd_xfer_isread(xfer))
+ flags |= XHCI_TRB_ISP;
+ flags |= (j == 1) ? XHCI_TRB_IOC : XHCI_TRB_CHAIN;
+ DPRINTF(("%s:%d: ring %p trb0_idx %lu ntrb %d "
+ "paddr %llx len %u\n", __func__, __LINE__,
+ &xp->ring.trbs[0], (trb - &xp->ring.trbs[0]), ntrb,
+ paddr, len));
+
+ trb->trb_paddr = htole64(paddr);
+ trb->trb_status = htole32(
+ XHCI_TRB_INTR(0) | XHCI_TRB_LEN(len) |
+ xhci_xfer_tdsize(xfer, remain, len)
+ );
+ trb->trb_flags = htole32(flags);
+
+ bus_dmamap_sync(xp->ring.dma.tag, xp->ring.dma.map,
+ TRBOFF(&xp->ring, trb), sizeof(struct xhci_trb),
+ BUS_DMASYNC_PREWRITE);
+
+ remain -= len;
+ paddr += len;
+ }
+
+ xfer->frlengths[i] = 0;
}
/* First TRB. */