diff options
author | kevlo <kevlo@FreeBSD.org> | 2008-08-18 04:49:58 +0000 |
---|---|---|
committer | kevlo <kevlo@FreeBSD.org> | 2008-08-18 04:49:58 +0000 |
commit | d35c84c137e0eba240707bd36806a2a868607152 (patch) | |
tree | 8067f6263bf9193ec94886aac574a4fc399e5c2b | |
parent | 7caf1a51cf578a9ddcd84d79e22612b6f025cc10 (diff) | |
download | FreeBSD-src-d35c84c137e0eba240707bd36806a2a868607152.zip FreeBSD-src-d35c84c137e0eba240707bd36806a2a868607152.tar.gz |
Add isochronous transfer support for USB 2.0
Obtained from: NetBSD
-rw-r--r-- | sys/dev/usb/ehci.c | 814 | ||||
-rw-r--r-- | sys/dev/usb/ehcireg.h | 35 | ||||
-rw-r--r-- | sys/dev/usb/ehcivar.h | 32 | ||||
-rw-r--r-- | sys/dev/usb/usb.h | 2 |
4 files changed, 834 insertions, 49 deletions
diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c index 0d0eee0..3bf25d2 100644 --- a/sys/dev/usb/ehci.c +++ b/sys/dev/usb/ehci.c @@ -129,7 +129,10 @@ struct ehci_pipe { u_int length; } bulk; /* Iso pipe */ - /* XXX */ + struct { + u_int next_frame; + u_int cur_xfers; + } isoc; } u; }; @@ -139,6 +142,8 @@ static void ehci_softintr(void *); static int ehci_intr1(ehci_softc_t *); static void ehci_waitintr(ehci_softc_t *, usbd_xfer_handle); static void ehci_check_intr(ehci_softc_t *, struct ehci_xfer *); +static void ehci_check_qh_intr(ehci_softc_t *, struct ehci_xfer *); +static void ehci_check_itd_intr(ehci_softc_t *, struct ehci_xfer *); static void ehci_idone(struct ehci_xfer *); static void ehci_timeout(void *); static void ehci_timeout_task(void *); @@ -205,6 +210,12 @@ static usbd_status ehci_alloc_sqtd_chain(struct ehci_pipe *, static void ehci_free_sqtd_chain(ehci_softc_t *, ehci_soft_qh_t *, ehci_soft_qtd_t *, ehci_soft_qtd_t *); +static ehci_soft_itd_t *ehci_alloc_itd(ehci_softc_t *); +static void ehci_free_itd(ehci_softc_t *, ehci_soft_itd_t *); +static void ehci_rem_free_itd_chain(ehci_softc_t *, + struct ehci_xfer *); +static void ehci_abort_isoc_xfer(usbd_xfer_handle, usbd_status); + static usbd_status ehci_device_request(usbd_xfer_handle xfer); static usbd_status ehci_device_setintr(ehci_softc_t *, ehci_soft_qh_t *, @@ -228,6 +239,10 @@ static void ehci_dump_sqtds(ehci_soft_qtd_t *); static void ehci_dump_sqtd(ehci_soft_qtd_t *); static void ehci_dump_qtd(ehci_qtd_t *); static void ehci_dump_sqh(ehci_soft_qh_t *); +#if notyet +static void ehci_dump_sitd(struct ehci_soft_itd *); +static void ehci_dump_itd(struct ehci_soft_itd *); +#endif #ifdef DIAGNOSTIC static void ehci_dump_exfer(struct ehci_xfer *); #endif @@ -414,8 +429,19 @@ ehci_init(ehci_softc_t *sc) return (err); DPRINTF(("%s: flsize=%d\n", device_get_nameunit(sc->sc_bus.bdev),sc->sc_flsize)); sc->sc_flist = KERNADDR(&sc->sc_fldma, 0); + + for (i = 0; i < sc->sc_flsize; i++) { + sc->sc_flist[i] = EHCI_NULL; + } + EOWRITE4(sc, EHCI_PERIODICLISTBASE, DMAADDR(&sc->sc_fldma, 0)); + sc->sc_softitds = malloc(sc->sc_flsize * sizeof(ehci_soft_itd_t *), + M_USB, M_NOWAIT | M_ZERO); + if (sc->sc_softitds == NULL) + return (ENOMEM); + LIST_INIT(&sc->sc_freeitds); + /* Set up the bus struct. */ sc->sc_bus.methods = &ehci_bus_methods; sc->sc_bus.pipe_size = sizeof(struct ehci_pipe); @@ -714,19 +740,31 @@ ehci_softintr(void *v) void ehci_check_intr(ehci_softc_t *sc, struct ehci_xfer *ex) { - ehci_soft_qtd_t *sqtd, *lsqtd; - u_int32_t status; + int attr; DPRINTFN(/*15*/2, ("ehci_check_intr: ex=%p\n", ex)); + attr = ex->xfer.pipe->endpoint->edesc->bmAttributes; + if (UE_GET_XFERTYPE(attr) == UE_ISOCHRONOUS) + ehci_check_itd_intr(sc, ex); + else + ehci_check_qh_intr(sc, ex); +} + +void +ehci_check_qh_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + ehci_soft_qtd_t *sqtd, *lsqtd; + u_int32_t status; + if (ex->sqtdstart == NULL) { - printf("ehci_check_intr: sqtdstart=NULL\n"); + printf("ehci_check_qh_intr: not valid sqtd\n"); return; } lsqtd = ex->sqtdend; #ifdef DIAGNOSTIC if (lsqtd == NULL) { - printf("ehci_check_intr: lsqtd==0\n"); + printf("ehci_check_qh_intr: lsqtd==0\n"); return; } #endif @@ -761,6 +799,64 @@ ehci_check_intr(ehci_softc_t *sc, struct ehci_xfer *ex) } void +ehci_check_itd_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + ehci_soft_itd_t *itd; + int i; + + if (ex->itdstart == NULL) { + printf("ehci_check_itd_intr: not valid itd\n"); + return; + } + + itd = ex->itdend; +#ifdef DIAGNOSTIC + if (itd == NULL) { + printf("ehci_check_itd_intr: itdend == 0\n"); + return; + } +#endif + + /* + * Step 1, check no active transfers in last itd, meaning we're finished + */ + for (i = 0; i < 8; i++) { + if (le32toh(itd->itd.itd_ctl[i]) & EHCI_ITD_ACTIVE) + break; + } + + if (i == 8) { + goto done; /* All 8 descriptors inactive, it's done */ + } + + /* + * Step 2, check for errors in status bits, throughout chain... + */ + + DPRINTFN(12, ("ehci_check_itd_intr: active ex=%p\n", ex)); + + for (itd = ex->itdstart; itd != ex->itdend; itd = itd->xfer_next) { + for (i = 0; i < 8; i++) { + if (le32toh(itd->itd.itd_ctl[i]) & (EHCI_ITD_BUF_ERR | + EHCI_ITD_BABBLE | EHCI_ITD_ERROR)) + break; + } + if (i != 8) { /* Error in one of the itds */ + goto done; + } + } /* itd search loop */ + + DPRINTFN(12, ("ehci_check_itd_intr: ex %p itd %p still active\n", ex, + ex->itdstart)); + return; +done: + DPRINTFN(12, ("ehci_check_itd_intr: ex=%p done\n", ex)); + callout_stop(&ex->xfer.timeout_handle); + usb_rem_task(ex->xfer.pipe->device, &ex->abort_task); + ehci_idone(ex); +} + +void ehci_idone(struct ehci_xfer *ex) { usbd_xfer_handle xfer = &ex->xfer; @@ -826,9 +922,64 @@ ehci_idone(struct ehci_xfer *ex) } /* The transfer is done, compute actual length and status. */ + if (UE_GET_XFERTYPE(xfer->pipe->endpoint->edesc->bmAttributes) + == UE_ISOCHRONOUS) { + /* Isoc transfer */ + struct ehci_soft_itd *itd; + int i, nframes, len, uframes; + + nframes = 0; + actlen = 0; + + switch (xfer->pipe->endpoint->edesc->bInterval) { + case 0: + panic("ehci: isoc xfer suddenly has 0 bInterval, " + "invalid\n"); + case 1: + uframes = 1; + break; + case 2: + uframes = 2; + break; + case 3: + uframes = 4; + break; + default: + uframes = 8; + break; + } + + for (itd = ex->itdstart; itd != NULL; itd = itd->xfer_next) { + for (i = 0; i < 8; i += uframes) { + /* XXX - driver didn't fill in the frame full + * of uframes. This leads to scheduling + * inefficiencies, but working around + * this doubles complexity of tracking + * an xfer. + */ + if (nframes >= xfer->nframes) + break; + + status = le32toh(itd->itd.itd_ctl[i]); + len = EHCI_ITD_GET_LEN(status); + xfer->frlengths[nframes++] = len; + actlen += len; + } + if (nframes >= xfer->nframes) + break; + } + xfer->actlen = actlen; + xfer->status = USBD_NORMAL_COMPLETION; + + goto end; + } + + /* Continue processing xfers using queue heads */ + lsqtd = ex->sqtdend; actlen = 0; - for (sqtd = ex->sqtdstart; sqtd != lsqtd->nextqtd; sqtd=sqtd->nextqtd) { + for (sqtd = ex->sqtdstart; sqtd != lsqtd->nextqtd; + sqtd =sqtd->nextqtd) { nstatus = le32toh(sqtd->qtd.qtd_status); if (nstatus & EHCI_QTD_ACTIVE) break; @@ -871,7 +1022,11 @@ ehci_idone(struct ehci_xfer *ex) } else { xfer->status = USBD_NORMAL_COMPLETION; } - +end: + /* XXX transfer_complete memcpys out transfer data (for in endpoints) + * during this call, before methods->done is called: dma sync required + * beforehand? + */ usb_transfer_complete(xfer); DPRINTFN(/*12*/2, ("ehci_idone: ex=%p done\n", ex)); } @@ -1319,11 +1474,51 @@ ehci_dump_sqh(ehci_soft_qh_t *sqh) ehci_dump_qtd(&qh->qh_qtd); } +#if notyet +void +ehci_dump_itd(struct ehci_soft_itd *itd) +{ + ehci_isoc_trans_t t; + ehci_isoc_bufr_ptr_t b, b2, b3; + int i; + + printf("ITD: next phys=%X\n", itd->itd.itd_next); + + for (i = 0; i < 8;i++) { + t = le32toh(itd->itd.itd_ctl[i]); + printf("ITDctl %d: stat=%X len=%X ioc=%X pg=%X offs=%X\n", i, + EHCI_ITD_GET_STATUS(t), EHCI_ITD_GET_LEN(t), + EHCI_ITD_GET_IOC(t), EHCI_ITD_GET_PG(t), + EHCI_ITD_GET_OFFS(t)); + } + printf("ITDbufr: "); + for (i = 0; i < 7; i++) + printf("%X,", EHCI_ITD_GET_BPTR(le32toh(itd->itd.itd_bufr[i]))); + + b = le32toh(itd->itd.itd_bufr[0]); + b2 = le32toh(itd->itd.itd_bufr[1]); + b3 = le32toh(itd->itd.itd_bufr[2]); + printf("\nep=%X daddr=%X dir=%d maxpkt=%X multi=%X\n", + EHCI_ITD_GET_EP(b), EHCI_ITD_GET_DADDR(b), EHCI_ITD_GET_DIR(b2), + EHCI_ITD_GET_MAXPKT(b2), EHCI_ITD_GET_MULTI(b3)); +} + +void +ehci_dump_sitd(struct ehci_soft_itd *itd) +{ + printf("SITD %p next=%p prev=%p xfernext=%p physaddr=%X slot=%d\n", + itd, itd->u.frame_list.next, itd->u.frame_list.prev, + itd->xfer_next, itd->physaddr, itd->slot); +} +#endif + #ifdef DIAGNOSTIC -static void +void ehci_dump_exfer(struct ehci_xfer *ex) { - printf("ehci_dump_exfer: ex=%p\n", ex); + printf("ehci_dump_exfer: ex=%p sqtdstart=%p end=%p itdstart=%p " + "end=%p isdone=%d\n", ex, ex->sqtdstart, ex->sqtdend, ex->itdstart, + ex->itdend, ex->isdone); } #endif #endif @@ -1366,6 +1561,8 @@ ehci_open(usbd_pipe_handle pipe) pipe->methods = &ehci_root_intr_methods; break; default: + DPRINTF(("ehci_open: bad bEndpointAddress 0x%02x\n", + ed->bEndpointAddress)); return (USBD_INVAL); } return (USBD_NORMAL_COMPLETION); @@ -1379,42 +1576,46 @@ ehci_open(usbd_pipe_handle pipe) default: panic("ehci_open: bad device speed %d", dev->speed); } if (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_ISOCHRONOUS) { - printf("%s: *** WARNING: opening low/full speed device, this " - "does not work yet.\n", - device_get_nameunit(sc->sc_bus.bdev)); + printf("%s: *** Error: opening low/full speed isoc device on" + "ehci, this does not work yet. Feel free to implement\n", + device_get_nameunit(sc->sc_bus.bdev)); DPRINTFN(1,("ehci_open: hshubaddr=%d hshubport=%d\n", hshubaddr, hshubport)); return USBD_INVAL; } naks = 8; /* XXX */ - sqh = ehci_alloc_sqh(sc); - if (sqh == NULL) - goto bad0; - /* qh_link filled when the QH is added */ - sqh->qh.qh_endp = htole32( - EHCI_QH_SET_ADDR(addr) | - EHCI_QH_SET_ENDPT(UE_GET_ADDR(ed->bEndpointAddress)) | - EHCI_QH_SET_EPS(speed) | - (xfertype == UE_CONTROL ? EHCI_QH_DTC : 0) | - EHCI_QH_SET_MPL(UGETW(ed->wMaxPacketSize)) | - (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_CONTROL ? - EHCI_QH_CTL : 0) | - EHCI_QH_SET_NRL(naks) - ); - sqh->qh.qh_endphub = htole32( - EHCI_QH_SET_MULT(1) | - EHCI_QH_SET_HUBA(hshubaddr) | - EHCI_QH_SET_PORT(hshubport) | - EHCI_QH_SET_CMASK(0x1c) | - EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0) - ); - sqh->qh.qh_curqtd = EHCI_NULL; - /* The overlay qTD was already set up by ehci_alloc_sqh(). */ - sqh->qh.qh_qtd.qtd_status = - htole32(EHCI_QTD_SET_TOGGLE(pipe->endpoint->savedtoggle)); - - epipe->sqh = sqh; + /* Allocate sqh for everything, save isoc xfers */ + if (xfertype != UE_ISOCHRONOUS) { + sqh = ehci_alloc_sqh(sc); + if (sqh == NULL) + goto bad0; + /* qh_link filled when the QH is added */ + sqh->qh.qh_endp = htole32( + EHCI_QH_SET_ADDR(addr) | + EHCI_QH_SET_ENDPT(UE_GET_ADDR(ed->bEndpointAddress)) | + EHCI_QH_SET_EPS(speed) | + (xfertype == UE_CONTROL ? EHCI_QH_DTC : 0) | + EHCI_QH_SET_MPL(UGETW(ed->wMaxPacketSize)) | + (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_CONTROL ? + EHCI_QH_CTL : 0) | + EHCI_QH_SET_NRL(naks) + ); + sqh->qh.qh_endphub = htole32( + EHCI_QH_SET_MULT(1) | + EHCI_QH_SET_HUBA(hshubaddr) | + EHCI_QH_SET_PORT(hshubport) | + EHCI_QH_SET_CMASK(0x1c) | + EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0) + ); + sqh->qh.qh_curqtd = EHCI_NULL; + /* The overlay qTD was already set up by ehci_alloc_sqh(). */ + sqh->qh.qh_qtd.qtd_status = + htole32(EHCI_QTD_SET_TOGGLE(pipe->endpoint->savedtoggle)); + epipe->sqh = sqh; + } else { + sqh = NULL; + } switch (xfertype) { case UE_CONTROL: @@ -1445,14 +1646,29 @@ ehci_open(usbd_pipe_handle pipe) return (ehci_device_setintr(sc, sqh, ival)); case UE_ISOCHRONOUS: pipe->methods = &ehci_device_isoc_methods; - return (USBD_INVAL); + if (ed->bInterval == 0 || ed->bInterval > 16) { + printf("ehci: opening pipe with invalid bInterval\n"); + err = USBD_INVAL; + goto bad1; + } + if (UGETW(ed->wMaxPacketSize) == 0) { + printf("ehci: zero length endpoint open request\n"); + err = USBD_INVAL; + goto bad1; + } + epipe->u.isoc.next_frame = 0; + epipe->u.isoc.cur_xfers = 0; + break; default: + DPRINTF(("ehci: bad xfer type %d\n", xfertype)); return (USBD_INVAL); } return (USBD_NORMAL_COMPLETION); bad1: - ehci_free_sqh(sc, sqh); + if (sqh != NULL) + ehci_free_sqh(sc, sqh); + return (err); bad0: return (USBD_NOMEM); } @@ -1569,6 +1785,49 @@ ehci_sync_hc(ehci_softc_t *sc) DPRINTFN(2,("ehci_sync_hc: exit\n")); } +/*Call at splusb*/ +void +ehci_rem_free_itd_chain(ehci_softc_t *sc, struct ehci_xfer *exfer) +{ + struct ehci_soft_itd *itd, *prev; + + prev = NULL; + + if (exfer->itdstart == NULL || exfer->itdend == NULL) + panic("ehci isoc xfer being freed, but with no itd chain\n"); + + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + prev = itd->u.frame_list.prev; + /* Unlink itd from hardware chain, or frame array */ + if (prev == NULL) { /* We're at the table head */ + sc->sc_softitds[itd->slot] = itd->u.frame_list.next; + sc->sc_flist[itd->slot] = itd->itd.itd_next; + + if (itd->u.frame_list.next != NULL) + itd->u.frame_list.next->u.frame_list.prev = + NULL; + } else { + /* XXX this part is untested... */ + prev->itd.itd_next = itd->itd.itd_next; + prev->u.frame_list.next = itd->u.frame_list.next; + if (itd->u.frame_list.next != NULL) + itd->u.frame_list.next->u.frame_list.prev = + prev; + } + } + + prev = NULL; + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + if (prev != NULL) + ehci_free_itd(sc, prev); + prev = itd; + } + if (prev) + ehci_free_itd(sc, prev); + exfer->itdstart = NULL; + exfer->itdend = NULL; +} + /***********/ /* @@ -2463,6 +2722,76 @@ ehci_free_sqtd_chain(ehci_softc_t *sc, ehci_soft_qh_t *sqh, } } +ehci_soft_itd_t * +ehci_alloc_itd(ehci_softc_t *sc) +{ + struct ehci_soft_itd *itd, *freeitd; + usbd_status err; + int i, s, offs, frindex, previndex; + usb_dma_t dma; + + s = splusb(); + + /* Find an itd that wasn't freed this frame or last frame. This can + * discard itds that were freed before frindex wrapped around + * XXX - can this lead to thrashing? Could fix by enabling wrap-around + * interrupt and fiddling with list when that happens */ + frindex = (EOREAD4(sc, EHCI_FRINDEX) + 1) >> 3; + previndex = (frindex != 0) ? frindex - 1 : sc->sc_flsize; + + freeitd = NULL; + LIST_FOREACH(itd, &sc->sc_freeitds, u.free_list) { + if (itd == NULL) + break; + if (itd->slot != frindex && itd->slot != previndex) { + freeitd = itd; + break; + } + } + + if (freeitd == NULL) { + DPRINTFN(2, ("ehci_alloc_itd allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, EHCI_ITD_SIZE * EHCI_ITD_CHUNK, + EHCI_PAGE_SIZE, &dma); + + if (err) { + DPRINTF(("ehci_alloc_itd, alloc returned %d\n", err)); + return NULL; + } + + for (i = 0; i < EHCI_ITD_CHUNK; i++) { + offs = i * EHCI_ITD_SIZE; + itd = KERNADDR(&dma, offs); + itd->physaddr = DMAADDR(&dma, offs); + itd->dma = dma; + itd->offs = offs; + LIST_INSERT_HEAD(&sc->sc_freeitds, itd, u.free_list); + } + freeitd = LIST_FIRST(&sc->sc_freeitds); + } + + itd = freeitd; + LIST_REMOVE(itd, u.free_list); + memset(&itd->itd, 0, sizeof(ehci_itd_t)); + itd->u.frame_list.next = NULL; + itd->u.frame_list.prev = NULL; + itd->xfer_next = NULL; + itd->slot = 0; + splx(s); + + return (itd); +} + +void +ehci_free_itd(ehci_softc_t *sc, ehci_soft_itd_t *itd) +{ + int s; + + s = splusb(); + LIST_INSERT_AFTER(LIST_FIRST(&sc->sc_freeitds), itd, u.free_list); + splx(s); +} + /****************/ /* @@ -2522,7 +2851,7 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) return; } - if (xfer->device->bus->intr_context || !curproc) + if (xfer->device->bus->intr_context) panic("ehci_abort_xfer: not in process context"); /* @@ -2692,6 +3021,86 @@ ehci_timeout(void *addr) } void +ehci_abort_isoc_xfer(usbd_xfer_handle xfer, usbd_status status) +{ + ehci_isoc_trans_t trans_status; + struct ehci_pipe *epipe; + struct ehci_xfer *exfer; + ehci_softc_t *sc; + struct ehci_soft_itd *itd; + int s, i; + + epipe = (struct ehci_pipe *) xfer->pipe; + exfer = EXFER(xfer); + sc = (ehci_softc_t *)epipe->pipe.device->bus; + + DPRINTF(("ehci_abort_isoc_xfer: xfer %p pipe %p\n", xfer, epipe)); + + if (sc->sc_dying) { + s = splusb(); + xfer->status = status; + callout_stop(&xfer->timeout_handle); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); + usb_transfer_complete(xfer); + splx(s); + return; + } + + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) { + DPRINTFN(2, ("ehci_abort_isoc_xfer: already aborting\n")); + +#ifdef DIAGNOSTIC + if (status == USBD_TIMEOUT) + printf("ehci_abort_xfer: TIMEOUT while aborting\n"); +#endif + + xfer->status = status; + DPRINTFN(2, ("ehci_abort_xfer: waiting for abort to finish\n")); + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTWAIT; + while (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) + tsleep(&exfer->ehci_xfer_flags, PZERO, "ehciaw", 0); + return; + } + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTING; + + xfer->status = status; + callout_stop(&xfer->timeout_handle); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); + + s = splusb(); + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + + for (i = 0; i < 8; i++) { + trans_status = le32toh(itd->itd.itd_ctl[i]); + trans_status &= ~EHCI_ITD_ACTIVE; + itd->itd.itd_ctl[i] = htole32(trans_status); + } + + } + splx(s); + + s = splusb(); +#ifdef USB_USE_SOFTINTR + sc->sc_softwake = 1; +#endif /* USB_USE_SOFTINTR */ + usb_schedsoftintr(&sc->sc_bus); +#ifdef USB_USE_SOFTINTR + tsleep(&sc->sc_softwake, PZERO, "ehciab", 0); +#endif /* USB_USE_SOFTINTR */ + splx(s); + +#ifdef DIAGNOSTIC + exfer->isdone = 1; +#endif + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTING; + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTWAIT) { + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTWAIT; + wakeup(&exfer->ehci_xfer_flags); + } + usb_transfer_complete(xfer); +} + +void ehci_timeout_task(void *addr) { usbd_xfer_handle xfer = addr; @@ -3270,6 +3679,11 @@ ehci_device_intr_abort(usbd_xfer_handle xfer) DPRINTFN(1, ("ehci_device_intr_abort: remove\n")); xfer->pipe->intrxfer = NULL; } + /* + * XXX - abort_xfer uses ehci_sync_hc, which syncs via the advance + * async doorbell. That's dependant on the async list, wheras + * intr xfers are periodic, should not use this? + */ ehci_abort_xfer(xfer, USBD_CANCELLED); } @@ -3362,8 +3776,314 @@ ehci_device_intr_done(usbd_xfer_handle xfer) /************************/ -static usbd_status ehci_device_isoc_transfer(usbd_xfer_handle xfer) { return USBD_IOERROR; } -static usbd_status ehci_device_isoc_start(usbd_xfer_handle xfer) { return USBD_IOERROR; } -static void ehci_device_isoc_abort(usbd_xfer_handle xfer) { } -static void ehci_device_isoc_close(usbd_pipe_handle pipe) { } -static void ehci_device_isoc_done(usbd_xfer_handle xfer) { } +static usbd_status +ehci_device_isoc_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + err = usb_insert_transfer(xfer); + if (err && err != USBD_IN_PROGRESS) + return (err); + + return (ehci_device_isoc_start(xfer)); +} + +static usbd_status +ehci_device_isoc_start(usbd_xfer_handle xfer) +{ + struct ehci_pipe *epipe; + usbd_device_handle dev; + ehci_softc_t *sc; + struct ehci_xfer *exfer; + ehci_soft_itd_t *itd, *prev, *start, *stop; + usb_dma_t *dma_buf; + int i, j, k, frames, uframes, ufrperframe; + int s, trans_count, offs, total_length; + int frindex; + + start = NULL; + prev = NULL; + itd = NULL; + trans_count = 0; + total_length = 0; + exfer = (struct ehci_xfer *) xfer; + sc = (ehci_softc_t *)xfer->pipe->device->bus; + dev = xfer->pipe->device; + epipe = (struct ehci_pipe *)xfer->pipe; + + /* + * To allow continuous transfers, above we start all transfers + * immediately. However, we're still going to get usbd_start_next call + * this when another xfer completes. So, check if this is already + * in progress or not + */ + + if (exfer->itdstart != NULL) + return (USBD_IN_PROGRESS); + + DPRINTFN(2, ("ehci_device_isoc_start: xfer %p len %d flags %d\n", + xfer, xfer->length, xfer->flags)); + + if (sc->sc_dying) + return (USBD_IOERROR); + + /* + * To avoid complication, don't allow a request right now that'll span + * the entire frame table. To within 4 frames, to allow some leeway + * on either side of where the hc currently is. + */ + if ((1 << (epipe->pipe.endpoint->edesc->bInterval)) * + xfer->nframes >= (sc->sc_flsize - 4) * 8) { + printf("ehci: isoc descriptor requested that spans the entire" + " frametable, too many frames\n"); + return (USBD_INVAL); + } + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("ehci_device_isoc_start: request\n"); + + if (!exfer->isdone) + printf("ehci_device_isoc_start: not done, ex = %p\n", exfer); + exfer->isdone = 0; +#endif + + /* + * Step 1: Allocate and initialize itds, how many do we need? + * One per transfer if interval >= 8 microframes, fewer if we use + * multiple microframes per frame. + */ + + i = epipe->pipe.endpoint->edesc->bInterval; + if (i > 16 || i == 0) { + /* Spec page 271 says intervals > 16 are invalid */ + DPRINTF(("ehci_device_isoc_start: bInvertal %d invalid\n", i)); + return (USBD_INVAL); + } + + switch (i) { + case 1: + ufrperframe = 8; + break; + case 2: + ufrperframe = 4; + break; + case 3: + ufrperframe = 2; + break; + default: + ufrperframe = 1; + break; + } + frames = (xfer->nframes + (ufrperframe - 1)) / ufrperframe; + uframes = 8 / ufrperframe; + + if (frames == 0) { + DPRINTF(("ehci_device_isoc_start: frames == 0\n")); + return (USBD_INVAL); + } + + dma_buf = xfer->buffer; + offs = 0; + + for (i = 0; i < frames; i++) { + int froffs = offs; + itd = ehci_alloc_itd(sc); + + if (prev != NULL) { + prev->itd.itd_next = + htole32(itd->physaddr | EHCI_LINK_ITD); + prev->xfer_next = itd; + } else { + start = itd; + } + + /* + * Step 1.5, initialize uframes + */ + for (j = 0; j < 8; j += uframes) { + /* Calculate which page in the list this starts in */ + int addr = DMAADDR(dma_buf, froffs); + addr = EHCI_PAGE_OFFSET(addr); + addr += (offs - froffs); + addr = EHCI_PAGE(addr); + addr /= EHCI_PAGE_SIZE; + + /* This gets the initial offset into the first page, + * looks how far further along the current uframe + * offset is. Works out how many pages that is. + */ + + itd->itd.itd_ctl[j] = htole32 ( EHCI_ITD_ACTIVE | + EHCI_ITD_SET_LEN(xfer->frlengths[trans_count]) | + EHCI_ITD_SET_PG(addr) | + EHCI_ITD_SET_OFFS(EHCI_PAGE_OFFSET(DMAADDR(dma_buf, + offs)))); + + total_length += xfer->frlengths[trans_count]; + offs += xfer->frlengths[trans_count]; + trans_count++; + + if (trans_count >= xfer->nframes) { /*Set IOC*/ + itd->itd.itd_ctl[j] |= htole32(EHCI_ITD_IOC); + } + } + + /* Step 1.75, set buffer pointers. To simplify matters, all + * pointers are filled out for the next 7 hardware pages in + * the dma block, so no need to worry what pages to cover + * and what to not. + */ + + for (j=0; j < 7; j++) { + /* + * Don't try to lookup a page that's past the end + * of buffer + */ + int page_offs = EHCI_PAGE(froffs + + (EHCI_PAGE_SIZE * j)); + if (page_offs >= dma_buf->block->size) + break; + + int page = DMAADDR(dma_buf, page_offs); + page = EHCI_PAGE(page); + itd->itd.itd_bufr[j] = + htole32(EHCI_ITD_SET_BPTR(page) | EHCI_LINK_ITD); + } + + /* + * Other special values + */ + + k = epipe->pipe.endpoint->edesc->bEndpointAddress; + itd->itd.itd_bufr[0] |= htole32( + EHCI_ITD_SET_EP(UE_GET_ADDR(k)) | + EHCI_ITD_SET_DADDR(epipe->pipe.device->address)); + + k = (UE_GET_DIR(epipe->pipe.endpoint->edesc->bEndpointAddress)) + ? 1 : 0; + j = UE_GET_SIZE( + UGETW(epipe->pipe.endpoint->edesc->wMaxPacketSize)); + itd->itd.itd_bufr[1] |= htole32(EHCI_ITD_SET_DIR(k) | + EHCI_ITD_SET_MAXPKT(UE_GET_SIZE(j))); + + /* FIXME: handle invalid trans */ + itd->itd.itd_bufr[2] |= + htole32(EHCI_ITD_SET_MULTI(UE_GET_TRANS(j)+1)); + prev = itd; + } /* End of frame */ + + stop = itd; + stop->xfer_next = NULL; + exfer->isoc_len = total_length; + + /* + * Part 2: Transfer descriptors have now been set up, now they must + * be scheduled into the period frame list. Erk. Not wanting to + * complicate matters, transfer is denied if the transfer spans + * more than the period frame list. + */ + + s = splusb(); + + /* Start inserting frames */ + if (epipe->u.isoc.cur_xfers > 0) { + frindex = epipe->u.isoc.next_frame; + } else { + frindex = EOREAD4(sc, EHCI_FRINDEX); + frindex = frindex >> 3; /* Erase microframe index */ + frindex += 2; + } + + if (frindex >= sc->sc_flsize) + frindex &= (sc->sc_flsize - 1); + + /* Whats the frame interval? */ + i = (1 << epipe->pipe.endpoint->edesc->bInterval); + if (i / 8 == 0) + i = 1; + else + i /= 8; + + itd = start; + for (j = 0; j < frames; j++) { + if (itd == NULL) + panic("ehci: unexpectedly ran out of isoc itds," + "isoc_start\n"); + + itd->itd.itd_next = sc->sc_flist[frindex]; + if (itd->itd.itd_next == 0) + /* FIXME: frindex table gets initialized to NULL + * or EHCI_NULL? */ + itd->itd.itd_next = htole32(EHCI_NULL); + + sc->sc_flist[frindex] = htole32(EHCI_LINK_ITD | itd->physaddr); + + itd->u.frame_list.next = sc->sc_softitds[frindex]; + sc->sc_softitds[frindex] = itd; + if (itd->u.frame_list.next != NULL) + itd->u.frame_list.next->u.frame_list.prev = itd; + itd->slot = frindex; + itd->u.frame_list.prev = NULL; + + frindex += i; + if (frindex >= sc->sc_flsize) + frindex -= sc->sc_flsize; + + itd = itd->xfer_next; + } + + epipe->u.isoc.cur_xfers++; + epipe->u.isoc.next_frame = frindex; + + exfer->itdstart = start; + exfer->itdend = stop; + exfer->sqtdstart = NULL; + exfer->sqtdstart = NULL; + + ehci_add_intr_list(sc, exfer); + xfer->status = USBD_IN_PROGRESS; + xfer->done = 0; + splx(s); + + if (sc->sc_bus.use_polling) { + printf("Starting ehci isoc xfer with polling. Bad idea?\n"); + ehci_waitintr(sc, xfer); + } + + return (USBD_IN_PROGRESS); +} + +static void +ehci_device_isoc_abort(usbd_xfer_handle xfer) +{ + DPRINTFN(1, ("ehci_device_isoc_abort: xfer = %p\n", xfer)); + ehci_abort_isoc_xfer(xfer, USBD_CANCELLED); +} + +static void +ehci_device_isoc_close(usbd_pipe_handle pipe) +{ + printf("ehci_device_isoc_close: nothing in the pipe to free?\n"); +} + +static void +ehci_device_isoc_done(usbd_xfer_handle xfer) +{ + struct ehci_xfer *exfer; + ehci_softc_t *sc; + struct ehci_pipe *epipe; + int s; + + exfer = EXFER(xfer); + sc = (ehci_softc_t *)xfer->pipe->device->bus; + epipe = (struct ehci_pipe *) xfer->pipe; + + s = splusb(); + epipe->u.isoc.cur_xfers--; + if (xfer->status != USBD_NOMEM && ehci_active_intr_list(exfer)) { + ehci_del_intr_list(exfer); + ehci_rem_free_itd_chain(sc, exfer); + } + splx(s); +} diff --git a/sys/dev/usb/ehcireg.h b/sys/dev/usb/ehcireg.h index 8f2942e..071ed7f 100644 --- a/sys/dev/usb/ehcireg.h +++ b/sys/dev/usb/ehcireg.h @@ -196,10 +196,41 @@ typedef u_int32_t ehci_link_t; typedef u_int32_t ehci_physaddr_t; +typedef u_int32_t ehci_isoc_trans_t; +typedef u_int32_t ehci_isoc_bufr_ptr_t; + /* Isochronous Transfer Descriptor */ typedef struct { - ehci_link_t itd_next; - /* XXX many more */ + ehci_link_t itd_next; + ehci_isoc_trans_t itd_ctl[8]; +#define EHCI_ITD_GET_STATUS(x) (((x) >> 28) & 0xf) +#define EHCI_ITD_SET_STATUS(x) (((x) & 0xf) << 28) +#define EHCI_ITD_ACTIVE 0x80000000 +#define EHCI_ITD_BUF_ERR 0x40000000 +#define EHCI_ITD_BABBLE 0x20000000 +#define EHCI_ITD_ERROR 0x10000000 +#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xfff) +#define EHCI_ITD_SET_LEN(x) (((x) & 0xfff) << 16) +#define EHCI_ITD_IOC 0x8000 +#define EHCI_ITD_GET_IOC(x) (((x) >> 15) & 1) +#define EHCI_ITD_SET_IOC(x) (((x) << 15) & EHCI_ITD_IOC) +#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0xf) +#define EHCI_ITD_SET_PG(x) (((x) & 0xf) << 12) +#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xfff) +#define EHCI_ITD_SET_OFFS(x) (((x) & 0xfff) << 0) + ehci_isoc_bufr_ptr_t itd_bufr[7]; +#define EHCI_ITD_GET_BPTR(x) ((x) & 0xfffff000) +#define EHCI_ITD_SET_BPTR(x) ((x) & 0xfffff000) +#define EHCI_ITD_GET_EP(x) (((x) >> 8) & 0xf) +#define EHCI_ITD_SET_EP(x) (((x) & 0xf) << 8) +#define EHCI_ITD_GET_DADDR(x) ((x) & 0x7f) +#define EHCI_ITD_SET_DADDR(x) ((x) & 0x7f) +#define EHCI_ITD_GET_DIR(x) (((x) >> 11) & 1) +#define EHCI_ITD_SET_DIR(x) (((x) & 1) << 11) +#define EHCI_ITD_GET_MAXPKT(x) ((x) & 0x7ff) +#define EHCI_ITD_SET_MAXPKT(x) ((x) & 0x7ff) +#define EHCI_ITD_GET_MULTI(x) ((x) & 0x3) +#define EHCI_ITD_SET_MULTI(x) ((x) & 0x3) } ehci_itd_t; #define EHCI_ITD_ALIGN 32 diff --git a/sys/dev/usb/ehcivar.h b/sys/dev/usb/ehcivar.h index 68417bb..b6b5e3b 100644 --- a/sys/dev/usb/ehcivar.h +++ b/sys/dev/usb/ehcivar.h @@ -60,12 +60,36 @@ typedef struct ehci_soft_qh { #define EHCI_SQH_SIZE ((sizeof (struct ehci_soft_qh) + EHCI_QH_ALIGN - 1) / EHCI_QH_ALIGN * EHCI_QH_ALIGN) #define EHCI_SQH_CHUNK (EHCI_PAGE_SIZE / EHCI_SQH_SIZE) +typedef struct ehci_soft_itd { + ehci_itd_t itd; + union { + struct { + /* soft_itds links in a periodic frame*/ + struct ehci_soft_itd *next; + struct ehci_soft_itd *prev; + } frame_list; + /* circular list of free itds */ + LIST_ENTRY(ehci_soft_itd) free_list; + } u; + struct ehci_soft_itd *xfer_next; /* Next soft_itd in xfer */ + ehci_physaddr_t physaddr; + usb_dma_t dma; + int offs; + int slot; + struct timeval t; /* store free time */ +} ehci_soft_itd_t; +#define EHCI_ITD_SIZE ((sizeof(struct ehci_soft_itd) + EHCI_QH_ALIGN - 1) / EHCI_ITD_ALIGN * EHCI_ITD_ALIGN) +#define EHCI_ITD_CHUNK (EHCI_PAGE_SIZE / EHCI_ITD_SIZE) + struct ehci_xfer { struct usbd_xfer xfer; struct usb_task abort_task; LIST_ENTRY(ehci_xfer) inext; /* list of active xfers */ ehci_soft_qtd_t *sqtdstart; ehci_soft_qtd_t *sqtdend; + ehci_soft_itd_t *itdstart; + ehci_soft_itd_t *itdend; + u_int isoc_len; u_int32_t ehci_xfer_flags; #ifdef DIAGNOSTIC int isdone; @@ -94,6 +118,8 @@ struct ehci_soft_islot { #define EHCI_HASH_SIZE 128 #define EHCI_COMPANION_MAX 8 +#define EHCI_FREE_LIST_INTERVAL 100 + #define EHCI_SCFLG_DONEINIT 0x0001 /* ehci_init() has been called. */ #define EHCI_SCFLG_LOSTINTRBUG 0x0002 /* workaround for VIA / ATI chipsets */ @@ -133,10 +159,16 @@ typedef struct ehci_softc { struct ehci_soft_islot sc_islots[EHCI_INTRQHS]; + /* jcmm - an array matching sc_flist, but with software pointers, + * not hardware address pointers + */ + struct ehci_soft_itd **sc_softitds; + LIST_HEAD(, ehci_xfer) sc_intrhead; ehci_soft_qh_t *sc_freeqhs; ehci_soft_qtd_t *sc_freeqtds; + LIST_HEAD(sc_freeitds, ehci_soft_itd) sc_freeitds; int sc_noport; u_int8_t sc_addr; /* device address */ diff --git a/sys/dev/usb/usb.h b/sys/dev/usb/usb.h index d7a4a4d..f4bc88c 100644 --- a/sys/dev/usb/usb.h +++ b/sys/dev/usb/usb.h @@ -271,6 +271,8 @@ typedef struct { #define UE_ISO_SYNC 0x0c #define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE) uWord wMaxPacketSize; +#define UE_GET_TRANS(a) (((a) >> 11) & 0x3) +#define UE_GET_SIZE(a) ((a) & 0x7ff) uByte bInterval; } UPACKED usb_endpoint_descriptor_t; #define USB_ENDPOINT_DESCRIPTOR_SIZE 7 |