diff options
author | iedowse <iedowse@FreeBSD.org> | 2006-05-28 05:27:09 +0000 |
---|---|---|
committer | iedowse <iedowse@FreeBSD.org> | 2006-05-28 05:27:09 +0000 |
commit | 225f58e59f70fbec54ed3ef9c9e4190dee7f16c0 (patch) | |
tree | b49c0a89d3a65700c3feb75bdd142516eff11782 /sys/dev/usb/uhci.c | |
parent | 9cd9aeea7a6956872906dbb99a9013fe89b5cbc3 (diff) | |
download | FreeBSD-src-225f58e59f70fbec54ed3ef9c9e4190dee7f16c0.zip FreeBSD-src-225f58e59f70fbec54ed3ef9c9e4190dee7f16c0.tar.gz |
Use the limited scatter-gather capabilities of ehci, ohci and uhci
host controllers to avoid the need to allocate any multi-page
physically contiguous memory blocks. This makes it possible to use
USB devices reliably on low-memory systems or when memory is too
fragmented for contiguous allocations to succeed.
The USB subsystem now uses bus_dmamap_load() directly on the buffers
supplied by USB peripheral drivers, so this also avoids having to
copy data back and forth before and after transfers. The ehci and
ohci controllers support scatter/gather as long as the buffer is
contiguous in the virtual address space. For uhci the hardware
cannot handle a physical address discontinuity within a USB packet,
so it is necessary to copy small memory fragments at times.
Diffstat (limited to 'sys/dev/usb/uhci.c')
-rw-r--r-- | sys/dev/usb/uhci.c | 264 |
1 files changed, 219 insertions, 45 deletions
diff --git a/sys/dev/usb/uhci.c b/sys/dev/usb/uhci.c index 6c27a15..aee88b9 100644 --- a/sys/dev/usb/uhci.c +++ b/sys/dev/usb/uhci.c @@ -189,6 +189,10 @@ Static uhci_soft_td_t *uhci_alloc_std(uhci_softc_t *); Static void uhci_free_std(uhci_softc_t *, uhci_soft_td_t *); Static uhci_soft_qh_t *uhci_alloc_sqh(uhci_softc_t *); Static void uhci_free_sqh(uhci_softc_t *, uhci_soft_qh_t *); +Static usbd_status uhci_aux_dma_alloc(uhci_softc_t *, uhci_soft_td_t *, + void *data, int len); +Static uhci_physaddr_t uhci_aux_dma_prepare(uhci_soft_td_t *, int); +Static void uhci_aux_dma_complete(uhci_soft_td_t *, int); #if 0 Static void uhci_enter_ctl_q(uhci_softc_t *, uhci_soft_qh_t *, uhci_intr_info_t *); @@ -198,7 +202,8 @@ Static void uhci_exit_ctl_q(uhci_softc_t *, uhci_soft_qh_t *); Static void uhci_free_std_chain(uhci_softc_t *, uhci_soft_td_t *, uhci_soft_td_t *); Static usbd_status uhci_alloc_std_chain(struct uhci_pipe *, - uhci_softc_t *, int, int, u_int16_t, usb_dma_t *, + uhci_softc_t *, int, int, u_int16_t, + usbd_xfer_handle xfer, uhci_soft_td_t **, uhci_soft_td_t **); Static void uhci_poll_hub(void *); Static void uhci_waitintr(uhci_softc_t *, usbd_xfer_handle); @@ -206,6 +211,7 @@ Static void uhci_check_intr(uhci_softc_t *, uhci_intr_info_t *); Static void uhci_idone(uhci_intr_info_t *); Static void uhci_abort_xfer(usbd_xfer_handle, usbd_status status); +Static void uhci_transfer_complete(usbd_xfer_handle xfer); Static void uhci_timeout(void *); Static void uhci_timeout_task(void *); @@ -968,7 +974,8 @@ uhci_poll_hub(void *addr) { usbd_xfer_handle xfer = addr; usbd_pipe_handle pipe = xfer->pipe; - uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; + usbd_device_handle dev = pipe->device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; int s; u_char *p; @@ -976,7 +983,7 @@ uhci_poll_hub(void *addr) usb_callout(sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); - p = KERNADDR(&xfer->dmabuf, 0); + p = xfer->buffer; p[0] = 0; if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC|UHCI_PORTSC_OCIC)) p[0] |= 1<<1; @@ -989,9 +996,9 @@ uhci_poll_hub(void *addr) xfer->actlen = 1; xfer->status = USBD_NORMAL_COMPLETION; s = splusb(); - xfer->device->bus->intr_context++; - usb_transfer_complete(xfer); - xfer->device->bus->intr_context--; + dev->bus->intr_context++; + uhci_transfer_complete(xfer); + dev->bus->intr_context--; splx(s); } @@ -1497,7 +1504,7 @@ uhci_idone(uhci_intr_info_t *ii) } end: - usb_transfer_complete(xfer); + uhci_transfer_complete(xfer); DPRINTFN(12, ("uhci_idone: ii=%p done\n", ii)); } @@ -1658,6 +1665,9 @@ uhci_alloc_std(uhci_softc_t *sc) std = KERNADDR(&dma, offs); std->physaddr = DMAADDR(&dma, offs); std->link.std = sc->sc_freetds; + std->aux_dma.block = NULL; + std->aux_data = NULL; + std->aux_len = 0; sc->sc_freetds = std; } } @@ -1678,6 +1688,12 @@ uhci_free_std(uhci_softc_t *sc, uhci_soft_td_t *std) } std->td.td_token = htole32(TD_IS_FREE); #endif + if (std->aux_dma.block != NULL) { + usb_freemem(&sc->sc_bus, &std->aux_dma); + std->aux_dma.block = NULL; + std->aux_data = NULL; + std->aux_len = 0; + } std->link.std = sc->sc_freetds; sc->sc_freetds = std; } @@ -1731,12 +1747,12 @@ uhci_free_std_chain(uhci_softc_t *sc, uhci_soft_td_t *std, usbd_status uhci_alloc_std_chain(struct uhci_pipe *upipe, uhci_softc_t *sc, int len, - int rd, u_int16_t flags, usb_dma_t *dma, + int rd, u_int16_t flags, usbd_xfer_handle xfer, uhci_soft_td_t **sp, uhci_soft_td_t **ep) { - uhci_soft_td_t *p, *lastp; - uhci_physaddr_t lastlink; - int i, ntd, l, tog, maxp; + struct usb_dma_mapping *dma = &xfer->dmamap; + uhci_soft_td_t *p, *prevp, *startp; + int err, i, ntd, l, tog, maxp, seg, segoff; u_int32_t status; int addr = upipe->pipe.device->address; int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; @@ -1759,29 +1775,31 @@ uhci_alloc_std_chain(struct uhci_pipe *upipe, uhci_softc_t *sc, int len, return (USBD_NORMAL_COMPLETION); } tog = upipe->nexttoggle; - if (ntd % 2 == 0) - tog ^= 1; - upipe->nexttoggle = tog ^ 1; - lastp = NULL; - lastlink = UHCI_PTR_T; - ntd--; + prevp = NULL; + startp = NULL; status = UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) | UHCI_TD_ACTIVE); if (upipe->pipe.device->speed == USB_SPEED_LOW) status |= UHCI_TD_LS; if (flags & USBD_SHORT_XFER_OK) status |= UHCI_TD_SPD; - for (i = ntd; i >= 0; i--) { + seg = 0; + segoff = 0; + for (i = 0; i < ntd; i++) { p = uhci_alloc_std(sc); if (p == NULL) { - uhci_free_std_chain(sc, lastp, NULL); + uhci_free_std_chain(sc, startp, NULL); return (USBD_NOMEM); } - p->link.std = lastp; - p->td.td_link = htole32(lastlink | UHCI_PTR_VF | UHCI_PTR_TD); - lastp = p; - lastlink = p->physaddr; + p->link.std = NULL; + if (prevp != NULL) { + prevp->link.std = p; + prevp->td.td_link = htole32(p->physaddr | UHCI_PTR_VF | + UHCI_PTR_TD); + } else { + startp = p; + } p->td.td_status = htole32(status); - if (i == ntd) { + if (i == ntd - 1) { /* last TD */ l = len % maxp; if (l == 0 && !(flags & USBD_FORCE_SHORT_XFER)) @@ -1792,15 +1810,100 @@ uhci_alloc_std_chain(struct uhci_pipe *upipe, uhci_softc_t *sc, int len, p->td.td_token = htole32(rd ? UHCI_TD_IN (l, endpt, addr, tog) : UHCI_TD_OUT(l, endpt, addr, tog)); - p->td.td_buffer = htole32(DMAADDR(dma, i * maxp)); + + KASSERT(seg < dma->nsegs, + ("uhci_alloc_std_chain: too few segments")); + if (l > dma->segs[seg].ds_len - segoff) { + /* UHCI can't handle non-contiguous data. */ + err = uhci_aux_dma_alloc(sc, p, (char *)xfer->buffer + + i * maxp, l); + if (err) { + uhci_free_std_chain(sc, startp, NULL); + return (err); + } + p->td.td_buffer = htole32(uhci_aux_dma_prepare(p, rd)); + l -= dma->segs[seg].ds_len - segoff; + seg++; + KASSERT(seg < dma->nsegs, + ("uhci_alloc_std_chain: too few segments 2")); + segoff = 0; + } else { + p->td.td_buffer = htole32(dma->segs[seg].ds_addr + + segoff); + } + segoff += l; + if (segoff >= dma->segs[seg].ds_len) { + KASSERT(segoff == dma->segs[seg].ds_len, + ("uhci_alloc_std_chain: overlap")); + if (i * maxp + l != len) { + seg++; + segoff = 0; + } + } + prevp = p; tog ^= 1; } - *sp = lastp; + prevp->td.td_link = htole32(UHCI_PTR_T | UHCI_PTR_VF | UHCI_PTR_TD); + upipe->nexttoggle = tog; + *sp = startp; DPRINTFN(10, ("uhci_alloc_std_chain: nexttog=%d\n", upipe->nexttoggle)); return (USBD_NORMAL_COMPLETION); } +/* + * Allocate a physically contiguous buffer to handle cases where UHCI + * cannot handle a packet because it is not physically contiguous. + * If the usb_dma_t was already allocated this just ensures it is + * large enough for the specified size. + */ +Static usbd_status +uhci_aux_dma_alloc(uhci_softc_t *sc, uhci_soft_td_t *std, void *data, int len) +{ + int err, align; + + if (std->aux_dma.block == NULL || std->aux_dma.block->size < len) { + /* Align to avoid crossing a page boundary. */ + if (powerof2(len)) + align = len; + else + align = 1 << fls(len); + + if (std->aux_dma.block != NULL) + usb_freemem(&sc->sc_bus, &std->aux_dma); + std->aux_dma.block = NULL; + err = usb_allocmem(&sc->sc_bus, len, align, &std->aux_dma); + if (err) + return (err); + } + std->aux_data = data; + std->aux_len = len; + + return (USBD_NORMAL_COMPLETION); +} + +Static uhci_physaddr_t +uhci_aux_dma_prepare(uhci_soft_td_t *std, int isread) +{ + if (!isread) { + bcopy(std->aux_data, KERNADDR(&std->aux_dma, 0), std->aux_len); + bus_dmamap_sync(std->aux_dma.block->tag, + std->aux_dma.block->map, BUS_DMASYNC_PREWRITE); + } + + return (DMAADDR(&std->aux_dma, 0)); +} + +Static void +uhci_aux_dma_complete(uhci_soft_td_t *std, int isread) +{ + if (isread) { + bus_dmamap_sync(std->aux_dma.block->tag, + std->aux_dma.block->map, BUS_DMASYNC_POSTREAD); + bcopy(KERNADDR(&std->aux_dma, 0), std->aux_data, std->aux_len); + } +} + void uhci_device_clear_toggle(usbd_pipe_handle pipe) { @@ -1862,8 +1965,8 @@ uhci_device_bulk_start(usbd_xfer_handle xfer) upipe->u.bulk.isread = isread; upipe->u.bulk.length = len; - err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, - &xfer->dmabuf, &data, &dataend); + err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, xfer, + &data, &dataend); if (err) return (err); dataend->td.td_status |= htole32(UHCI_TD_IOC); @@ -1949,7 +2052,7 @@ uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) xfer->status = status; /* make software ignore it */ usb_uncallout(xfer->timeout_handle, uhci_timeout, xfer); usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); - usb_transfer_complete(xfer); + uhci_transfer_complete(xfer); splx(s); return; } @@ -2019,10 +2122,50 @@ uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) uxfer->uhci_xfer_flags &= ~UHCI_XFER_ABORTWAIT; wakeup(&uxfer->uhci_xfer_flags); } - usb_transfer_complete(xfer); + uhci_transfer_complete(xfer); splx(s); } +/* + * Perform any UHCI-specific transfer completion operations, then + * call usb_transfer_complete(). + */ +Static void +uhci_transfer_complete(usbd_xfer_handle xfer) +{ + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_soft_td_t *p; + int i, isread, n; + + /* XXX, must be an easier way to detect reads... */ + isread = ((xfer->rqflags & URQ_REQUEST) && + (xfer->request.bmRequestType & UT_READ)) || + (xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN); + + /* Copy back from any auxillary buffers after a read operation. */ + if (xfer->nframes == 0) { + for (p = ii->stdstart; p != NULL; p = p->link.std) { + if (p->aux_data != NULL) + uhci_aux_dma_complete(p, isread); + } + } else { + if (xfer->nframes != 0) { + /* Isoc transfer, do things differently. */ + n = UXFER(xfer)->curframe; + for (i = 0; i < xfer->nframes; i++) { + p = upipe->u.iso.stds[n]; + if (p->aux_data != NULL) + uhci_aux_dma_complete(p, isread); + if (++n >= UHCI_VFRAMELIST_COUNT) + n = 0; + } + } + } + + usb_transfer_complete(xfer); +} + /* Close a device bulk pipe. */ void uhci_device_bulk_close(usbd_pipe_handle pipe) @@ -2122,9 +2265,8 @@ uhci_device_intr_start(usbd_xfer_handle xfer) upipe->u.intr.isread = isread; - err = uhci_alloc_std_chain(upipe, sc, xfer->length, isread, - xfer->flags, &xfer->dmabuf, &data, - &dataend); + err = uhci_alloc_std_chain(upipe, sc, xfer->length, isread, xfer->flags, + xfer, &data, &dataend); if (err) return (err); dataend->td.td_status |= htole32(UHCI_TD_IOC); @@ -2262,7 +2404,7 @@ uhci_device_request(usbd_xfer_handle xfer) if (len != 0) { upipe->nexttoggle = 1; err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, - &xfer->dmabuf, &data, &dataend); + xfer, &data, &dataend); if (err) return (err); next = data; @@ -2389,8 +2531,9 @@ uhci_device_isoc_enter(usbd_xfer_handle xfer) uhci_softc_t *sc = (uhci_softc_t *)dev->bus; struct iso *iso = &upipe->u.iso; uhci_soft_td_t *std; - u_int32_t buf, len, status; - int s, i, next, nframes; + void *dataptr; + u_int32_t len, status; + int err, s, i, isread, next, nframes, seg, segoff; DPRINTFN(5,("uhci_device_isoc_enter: used=%d next=%d xfer=%p " "nframes=%d\n", @@ -2420,7 +2563,10 @@ uhci_device_isoc_enter(usbd_xfer_handle xfer) xfer->status = USBD_IN_PROGRESS; UXFER(xfer)->curframe = next; - buf = DMAADDR(&xfer->dmabuf, 0); + seg = 0; + segoff = 0; + dataptr = xfer->allocbuf; /* Normal buffers not possible for isoc? */ + isread = xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN; status = UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(0) | UHCI_TD_ACTIVE | UHCI_TD_IOS); @@ -2431,7 +2577,35 @@ uhci_device_isoc_enter(usbd_xfer_handle xfer) if (++next >= UHCI_VFRAMELIST_COUNT) next = 0; len = xfer->frlengths[i]; - std->td.td_buffer = htole32(buf); + KASSERT(seg < xfer->dmamap.nsegs, + ("uhci_device_isoc_enter: too few segments")); + if (len + segoff > xfer->dmamap.segs[seg].ds_len) { + /* UHCI can't handle non-contiguous data. */ + err = uhci_aux_dma_alloc(sc, std, dataptr, len); + /* XXX */ + if (err) + printf("uhci_device_isoc_enter: aux alloc\n"); + std->td.td_buffer = htole32(uhci_aux_dma_prepare(std, + isread)); + segoff += len; + while (segoff >= xfer->dmamap.segs[seg].ds_len) { + KASSERT(seg < xfer->dmamap.nsegs - 1 || + segoff == xfer->dmamap.segs[seg].ds_len, + ("uhci_device_isoc_enter: overlap2")); + segoff -= xfer->dmamap.segs[seg].ds_len; + seg++; + } + } else { + std->td.td_buffer = + htole32(xfer->dmamap.segs[seg].ds_addr + segoff); + segoff += len; + if (segoff >= xfer->dmamap.segs[seg].ds_len) { + KASSERT(segoff == xfer->dmamap.segs[seg].ds_len, + ("uhci_device_isoc_enter: overlap")); + segoff = 0; + seg++; + } + } if (i == nframes - 1) status |= UHCI_TD_IOC; std->td.td_status = htole32(status); @@ -2443,7 +2617,7 @@ uhci_device_isoc_enter(usbd_xfer_handle xfer) uhci_dump_td(std); } #endif - buf += len; + dataptr = (char *)dataptr + len; } iso->next = next; iso->inuse += xfer->nframes; @@ -2542,7 +2716,7 @@ uhci_device_isoc_abort(usbd_xfer_handle xfer) UXFER(xfer)->iinfo.isdone = 1; #endif /* Run callback and remove from interrupt list. */ - usb_transfer_complete(xfer); + uhci_transfer_complete(xfer); splx(s); } @@ -2721,8 +2895,8 @@ uhci_device_intr_done(usbd_xfer_handle xfer) /* This alloc cannot fail since we freed the chain above. */ uhci_alloc_std_chain(upipe, sc, xfer->length, - upipe->u.intr.isread, xfer->flags, - &xfer->dmabuf, &data, &dataend); + upipe->u.intr.isread, xfer->flags, xfer, + &data, &dataend); dataend->td.td_status |= htole32(UHCI_TD_IOC); #ifdef USB_DEBUG @@ -3210,7 +3384,7 @@ uhci_root_ctrl_start(usbd_xfer_handle xfer) index = UGETW(req->wIndex); if (len != 0) - buf = KERNADDR(&xfer->dmabuf, 0); + buf = xfer->buffer; #define C(x,y) ((x) | ((y) << 8)) switch(C(req->bRequest, req->bmRequestType)) { @@ -3500,7 +3674,7 @@ uhci_root_ctrl_start(usbd_xfer_handle xfer) ret: xfer->status = err; s = splusb(); - usb_transfer_complete(xfer); + uhci_transfer_complete(xfer); splx(s); return (USBD_IN_PROGRESS); } @@ -3536,7 +3710,7 @@ uhci_root_intr_abort(usbd_xfer_handle xfer) #ifdef DIAGNOSTIC UXFER(xfer)->iinfo.isdone = 1; #endif - usb_transfer_complete(xfer); + uhci_transfer_complete(xfer); } usbd_status |