While recently testing UDL on XHCI I faced a lot of USB timeouts, and
therefore screen rendering issues.
The reason is that XHCI currently only supports single bulk transfers
up to 64k, while UDL can schedule a bulk transfer up to 1m.
The following diff adds XHCI bulk transfer support >64k and makes UDL
work fine for me on XHCI.
mpi@ already did an initial re-view, and I have adapted some of his
comments already in this diff.
More testing and comments welcome.
Index: 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
--- xhci.c 6 Oct 2019 17:30:00 -0000 1.106
+++ xhci.c 17 Nov 2019 11:22:22 -0000
@@ -816,9 +816,29 @@ xhci_event_xfer_generic(struct xhci_soft
uint8_t code, uint8_t slot, uint8_t dci)
{
struct xhci_xfer *xx = (struct xhci_xfer *)xfer;
-
+ int trb0_idx;
+
switch (code) {
case XHCI_CODE_SUCCESS:
+ if (trb_idx == 0)
+ trb0_idx = xp->ring.ntrb - 2;
+ else
+ trb0_idx = trb_idx - 1;
+
+ if (xp->ring.trbs[trb0_idx].trb_flags & XHCI_TRB_CHAIN) {
+ xfer->actlen += XHCI_TRB_LEN(xp->ring.trbs[trb0_idx].
+ trb_status) - remain;
+ }
+ xfer->actlen += XHCI_TRB_LEN(xp->ring.trbs[trb_idx].
+ trb_status) - remain;
+
+ DPRINTF(("XHCI_CODE_SUCCESS: "
+ "index=%d, trb_idx=%d, actlen=%d, length=%d, remain=%d\n"
+ xx->index, trb_idx, xfer->actlen, xfer->length, remain));
+
+ if (xx->index != trb_idx)
+ return (1);
+
/*
* This might be the last TRB of a TD that ended up
* with a Short Transfer condition, see below.
@@ -2856,68 +2876,93 @@ xhci_device_generic_start(struct usbd_xf
struct xhci_trb *trb0, *trb;
uint32_t len, remain, flags;
uint32_t mps = UGETW(xfer->pipe->endpoint->edesc->wMaxPacketSize);
- uint64_t paddr = DMAADDR(&xfer->dmabuf, 0);
+ uint64_t paddr;
uint8_t toggle;
- int s, i, ntrb;
+ int s, i, j, ntrb;
+ int chlengths[XHCI_MAX_BULK_CHUNKS];
+ int nchunks;
KASSERT(!(xfer->rqflags & URQ_REQUEST));
if (sc->sc_bus.dying || xp->halted)
return (USBD_IOERROR);
- /* How many TRBs do we need for this transfer? */
- ntrb = howmany(xfer->length, XHCI_TRB_MAXSIZE);
+ /* Split the payload in chunks of XHCI_TRB_MAXSIZE. */
+ nchunks = xfer->length / XHCI_TRB_MAXSIZE;
+ remain = xfer->length % 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->length)
- ntrb++;
- else
- len = xfer->length;
+ if ((nchunks + (remain ? 1 : 0)) >= XHCI_MAX_BULK_CHUNKS)
+ return (USBD_NOMEM);
- /* If we need to append a zero length packet, we need one more. */
- if ((xfer->flags & USBD_FORCE_SHORT_XFER || xfer->length == 0) &&
- (xfer->length % UE_GET_SIZE(mps) == 0))
- ntrb++;
+ for (i = 0; i < nchunks; i++)
+ chlengths[i] = XHCI_TRB_MAXSIZE;
+ if (remain != 0) {
+ chlengths[i++] = remain;
+ nchunks++;
+ }
+
+ paddr = DMAADDR(&xfer->dmabuf, 0);
+
+ /* How many TRBs do for all Transfers? */
+ for (i = 0, ntrb = 0; i < nchunks; i++) {
+ /* How many TRBs do we need for this transfer? */
+ ntrb += howmany(chlengths[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 < chlengths[i])
+ ntrb++;
+
+ /* If we need to append a zero length packet, we need 1 more. */
+ if ((xfer->flags & USBD_FORCE_SHORT_XFER || xfer->length == 0)
+ && (xfer->length % UE_GET_SIZE(mps) == 0))
+ ntrb++;
+
+ paddr += chlengths[i];
+ }
if (xp->free_trbs < ntrb)
return (USBD_NOMEM);
- /* We'll toggle the first TRB once we're finished with the chain. */
- trb0 = xhci_xfer_get_trb(sc, xfer, &toggle, (ntrb == 1));
- flags = XHCI_TRB_TYPE_NORMAL | (toggle ^ 1);
- if (usbd_xfer_isread(xfer))
- flags |= XHCI_TRB_ISP;
- flags |= (ntrb == 1) ? XHCI_TRB_IOC : XHCI_TRB_CHAIN;
-
- trb0->trb_paddr = htole64(DMAADDR(&xfer->dmabuf, 0));
- trb0->trb_status = htole32(
- XHCI_TRB_INTR(0) | XHCI_TRB_LEN(len) |
- xhci_xfer_tdsize(xfer, xfer->length, len)
- );
- 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);
+ paddr = DMAADDR(&xfer->dmabuf, 0);
- remain = xfer->length - len;
- paddr += len;
+ for (i = 0, trb0 = NULL; i < nchunks; i++) {
+ /* How many TRBs do we need for this transfer? */
+ ntrb = howmany(chlengths[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 < chlengths[i])
+ ntrb++;
+ else
+ len = chlengths[i];
- /* Chain more TRBs if needed. */
- for (i = ntrb - 1; i > 0; i--) {
- len = min(remain, XHCI_TRB_MAXSIZE);
+ /* If we need to append a zero length packet, we need 1 more. */
+ if ((xfer->flags & USBD_FORCE_SHORT_XFER || xfer->length == 0)
+ && (xfer->length % UE_GET_SIZE(mps) == 0))
+ ntrb++;
+
+ /*
+ * We'll commit the first TRB once we're finished with the
+ * chain.
+ */
+ trb = xhci_xfer_get_trb(sc, xfer, &toggle, (ntrb == 1));
+
+ /* 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;
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;
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, chlengths[i], len)
);
trb->trb_flags = htole32(flags);
@@ -2925,10 +2970,36 @@ xhci_device_generic_start(struct usbd_xf
TRBOFF(&xp->ring, trb), sizeof(struct xhci_trb),
BUS_DMASYNC_PREWRITE);
- remain -= len;
+ remain = chlengths[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;
+
+ 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;
+ }
+ }
+
/* First TRB. */
trb0->trb_flags ^= htole32(XHCI_TRB_CYCLE);
bus_dmamap_sync(xp->ring.dma.tag, xp->ring.dma.map,
Index: xhcivar.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/xhcivar.h,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 xhcivar.h
--- xhcivar.h 6 Oct 2019 17:30:00 -0000 1.11
+++ xhcivar.h 17 Nov 2019 11:22:22 -0000
@@ -25,6 +25,7 @@
#define XHCI_MAX_CMDS (16 * 1)
#define XHCI_MAX_EVTS (16 * 13)
#define XHCI_MAX_XFER (16 * 16)
+#define XHCI_MAX_BULK_CHUNKS 64
struct usbd_dma_info {
bus_dma_tag_t tag;