diff options
author | Marcus Glocker <mglocker@cvs.openbsd.org> | 2008-08-09 22:59:21 +0000 |
---|---|---|
committer | Marcus Glocker <mglocker@cvs.openbsd.org> | 2008-08-09 22:59:21 +0000 |
commit | cea45eb87f5be96da379089698d4d9071d48462d (patch) | |
tree | fabdb421d282c534baf9b0b59e90364a5ff88067 /sys/dev/usb/ehci.c | |
parent | a5fa09894833397c174e0d04b16e94d9649e52b6 (diff) |
Add isochronous xfer support for ehci(4). From NetBSD.
OK brad@
Diffstat (limited to 'sys/dev/usb/ehci.c')
-rw-r--r-- | sys/dev/usb/ehci.c | 809 |
1 files changed, 759 insertions, 50 deletions
diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c index da641102b1d..450a2d02434 100644 --- a/sys/dev/usb/ehci.c +++ b/sys/dev/usb/ehci.c @@ -1,8 +1,9 @@ -/* $OpenBSD: ehci.c,v 1.84 2008/06/29 10:04:15 yuo Exp $ */ +/* $OpenBSD: ehci.c,v 1.85 2008/08/09 22:59:20 mglocker Exp $ */ /* $NetBSD: ehci.c,v 1.66 2004/06/30 03:11:56 mycroft Exp $ */ /* - * Copyright (c) 2004 The NetBSD Foundation, Inc. + * Copyright (c) 2004,2005 The NetBSD Foundation, Inc. + * Copyright (c) 2008 Jeremy Morse <jeremy.morse@gmail.com> * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -117,7 +118,10 @@ struct ehci_pipe { u_int length; } bulk; /* Iso pipe */ - /* XXX */ + struct { + u_int next_frame; + u_int cur_xfers; + } isoc; } u; }; @@ -131,6 +135,8 @@ void ehci_softintr(void *); int ehci_intr1(ehci_softc_t *); void ehci_waitintr(ehci_softc_t *, usbd_xfer_handle); void ehci_check_intr(ehci_softc_t *, struct ehci_xfer *); +void ehci_check_qh_intr(ehci_softc_t *, struct ehci_xfer *); +void ehci_check_itd_intr(ehci_softc_t *, struct ehci_xfer *); void ehci_idone(struct ehci_xfer *); void ehci_timeout(void *); void ehci_timeout_task(void *); @@ -196,6 +202,13 @@ usbd_status ehci_alloc_sqtd_chain(struct ehci_pipe *, void ehci_free_sqtd_chain(ehci_softc_t *, ehci_soft_qtd_t *, ehci_soft_qtd_t *); +ehci_soft_itd_t *ehci_alloc_itd(ehci_softc_t *sc); +void ehci_free_itd(ehci_softc_t *sc, ehci_soft_itd_t *itd); +void ehci_rem_free_itd_chain(ehci_softc_t *sc, + struct ehci_xfer *exfer); +void ehci_abort_isoc_xfer(usbd_xfer_handle xfer, + usbd_status status); + usbd_status ehci_device_request(usbd_xfer_handle xfer); usbd_status ehci_device_setintr(ehci_softc_t *, ehci_soft_qh_t *, @@ -219,6 +232,10 @@ void ehci_dump_sqtds(ehci_soft_qtd_t *); void ehci_dump_sqtd(ehci_soft_qtd_t *); void ehci_dump_qtd(ehci_qtd_t *); void ehci_dump_sqh(ehci_soft_qh_t *); +#if notyet +void ehci_dump_sitd(struct ehci_soft_itd *itd); +void ehci_dump_itd(struct ehci_soft_itd *); +#endif #ifdef DIAGNOSTIC void ehci_dump_exfer(struct ehci_xfer *); #endif @@ -382,8 +399,19 @@ ehci_init(ehci_softc_t *sc) return (err); DPRINTF(("%s: flsize=%d\n", sc->sc_bus.bdev.dv_xname,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_WAITOK | 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); @@ -654,19 +682,34 @@ 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); + + return; +} + +void +ehci_check_qh_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + ehci_soft_qtd_t *sqtd, *lsqtd; + __uint32_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 @@ -701,6 +744,63 @@ 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 (letoh32(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 (letoh32(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)); + timeout_del(&ex->xfer.timeout_handle); + ehci_idone(ex); +} + +void ehci_idone(struct ehci_xfer *ex) { usbd_xfer_handle xfer = &ex->xfer; @@ -729,7 +829,6 @@ ehci_idone(struct ehci_xfer *ex) splx(s); } #endif - if (xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT) { DPRINTF(("ehci_idone: aborted xfer=%p\n", xfer)); @@ -743,6 +842,55 @@ ehci_idone(struct ehci_xfer *ex) #endif /* 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"); + 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 = letoh32(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; @@ -789,7 +937,10 @@ ehci_idone(struct ehci_xfer *ex) xfer->status = USBD_IOERROR; /* more info XXX */ } 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)); } @@ -1252,13 +1403,54 @@ 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 = letoh32(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(letoh32(itd->itd.itd_bufr[i]))); + + b = letoh32(itd->itd.itd_bufr[0]); + b2 = letoh32(itd->itd.itd_bufr[1]); + b3 = letoh32(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 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 /* EHCI_DEBUG */ usbd_status @@ -1312,8 +1504,8 @@ 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 isochronous " - "device, this does not work yet.\n", + printf("%s: *** Error: opening low/full speed isoc device on" + "ehci, this does not work yet. Feel free to implement\n", sc->sc_bus.bdev.dv_xname); DPRINTFN(1,("ehci_open: hshubaddr=%d hshubport=%d\n", hshubaddr, hshubport)); @@ -1321,33 +1513,40 @@ ehci_open(usbd_pipe_handle pipe) } 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) | /* XXX */ - EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0)); - sqh->qh.qh_curqtd = EHCI_NULL; - /* Fill the overlay qTD */ - sqh->qh.qh_qtd.qtd_next = EHCI_NULL; - sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL; - 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) + return (USBD_NOMEM); + /* 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) | /* XXX */ + EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0) + ); + sqh->qh.qh_curqtd = EHCI_NULL; + /* Fill the overlay qTD */ + sqh->qh.qh_qtd.qtd_next = EHCI_NULL; + sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL; + sqh->qh.qh_qtd.qtd_status = + htole32(EHCI_QTD_SET_TOGGLE(pipe->endpoint->savedtoggle)); + epipe->sqh = sqh; + } else { + sqh = NULL; + } /*xfertype == UE_ISOC*/ switch (xfertype) { case UE_CONTROL: @@ -1358,7 +1557,7 @@ ehci_open(usbd_pipe_handle pipe) printf("ehci_open: usb_allocmem()=%d\n", err); #endif if (err) - goto bad1; + goto bad; pipe->methods = &ehci_device_ctrl_methods; s = splusb(); ehci_add_qh(sqh, sc->sc_async_head); @@ -1378,16 +1577,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 bad; + } + if (UGETW(ed->wMaxPacketSize) == 0) { + printf("ehci: zero length endpoint open request\n"); + err = USBD_INVAL; + goto bad; + } + 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); -bad0: - return (USBD_NOMEM); +bad: + if (sqh != NULL) + ehci_free_sqh(sc, sqh); + return (err); } /* @@ -1496,6 +1708,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"); + + 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; +} + /***********/ /* @@ -2327,6 +2582,76 @@ ehci_free_sqtd_chain(ehci_softc_t *sc, ehci_soft_qtd_t *sqtd, } } +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); +} + /****************/ /* @@ -2385,7 +2710,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"); /* @@ -2555,6 +2880,83 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) #undef exfer } + 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, wake; + + 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; + timeout_del(&xfer->timeout_handle); + 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_ABORTING; + while (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) + tsleep(&exfer->ehci_xfer_flags, PZERO, "ehciiaw", 0); + return; + } + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTING; + + xfer->status = status; + timeout_del(&xfer->timeout_handle); + + s = splusb(); + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + for (i = 0; i < 8; i++) { + trans_status = letoh32(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 + wake = exfer->ehci_xfer_flags & EHCI_XFER_ABORTING; + exfer->ehci_xfer_flags &= ~(EHCI_XFER_ABORTING | EHCI_XFER_ABORTWAIT); + usb_transfer_complete(xfer); + if (wake) + wakeup(&exfer->ehci_xfer_flags); + + return; +} + void ehci_timeout(void *addr) { @@ -3133,6 +3535,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); } @@ -3210,8 +3617,310 @@ ehci_device_intr_done(usbd_xfer_handle xfer) /************************/ -usbd_status ehci_device_isoc_transfer(usbd_xfer_handle xfer) { return USBD_IOERROR; } -usbd_status ehci_device_isoc_start(usbd_xfer_handle xfer) { return USBD_IOERROR; } -void ehci_device_isoc_abort(usbd_xfer_handle xfer) { } -void ehci_device_isoc_close(usbd_pipe_handle pipe) { } -void ehci_device_isoc_done(usbd_xfer_handle xfer) { } +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)); +} + +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; + 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"); + + 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); + } else if (i >= 4) { + frames = xfer->nframes; + uframes = 8; + } else { + frames = xfer->nframes + 0x7; /* 7 added for rounding up */ + uframes = 0; + switch (i) { + case 1: frames /= 8; uframes = 1; break; + case 2: frames /= 4; uframes = 2; break; + case 3: frames /= 2; uframes = 4; break; + } + } + + if (frames == 0) { + DPRINTF(("ehci_device_isoc_start: frames == 0\n")); + return (USBD_INVAL); + } + + dma_buf = &xfer->dmabuf; + 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"); + + 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); +} + +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); +} + +void +ehci_device_isoc_close(usbd_pipe_handle pipe) +{ + DPRINTFN(1, ("ehci_device_isoc_close: nothing in the pipe to free?\n")); +} + +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); +} |