/* * Copyright (c) 1997, 1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD: src/sys/i386/i386/busdma_machdep.c,v 1.16.2.2 2003/01/23 00:55:27 scottl Exp $ * $DragonFly: src/sys/i386/i386/Attic/busdma_machdep.c,v 1.11 2004/10/26 04:22:32 dillon Exp $ */ #include #include #include #include #include #include #include /* XXX needed for to access pmap to convert per-proc virtual to physical */ #include #include #include #include #include #define MAX_BPAGES 128 struct bus_dma_tag { bus_dma_tag_t parent; bus_size_t alignment; bus_size_t boundary; bus_addr_t lowaddr; bus_addr_t highaddr; bus_dma_filter_t *filter; void *filterarg; bus_size_t maxsize; u_int nsegments; bus_size_t maxsegsz; int flags; int ref_count; int map_count; bus_dma_segment_t *segments; }; struct bounce_page { vm_offset_t vaddr; /* kva of bounce buffer */ bus_addr_t busaddr; /* Physical address */ vm_offset_t datavaddr; /* kva of client data */ bus_size_t datacount; /* client data count */ STAILQ_ENTRY(bounce_page) links; }; int busdma_swi_pending; static STAILQ_HEAD(bp_list, bounce_page) bounce_page_list; static int free_bpages; static int reserved_bpages; static int active_bpages; static int total_bpages; static bus_addr_t bounce_lowaddr = BUS_SPACE_MAXADDR; struct bus_dmamap { struct bp_list bpages; int pagesneeded; int pagesreserved; bus_dma_tag_t dmat; void *buf; /* unmapped buffer pointer */ bus_size_t buflen; /* unmapped buffer length */ bus_dmamap_callback_t *callback; void *callback_arg; STAILQ_ENTRY(bus_dmamap) links; }; static STAILQ_HEAD(, bus_dmamap) bounce_map_waitinglist; static STAILQ_HEAD(, bus_dmamap) bounce_map_callbacklist; static struct bus_dmamap nobounce_dmamap; static int alloc_bounce_pages(bus_dma_tag_t dmat, u_int numpages); static int reserve_bounce_pages(bus_dma_tag_t dmat, bus_dmamap_t map); static bus_addr_t add_bounce_page(bus_dma_tag_t dmat, bus_dmamap_t map, vm_offset_t vaddr, bus_size_t size); static void free_bounce_page(bus_dma_tag_t dmat, struct bounce_page *bpage); static __inline int run_filter(bus_dma_tag_t dmat, bus_addr_t paddr); static __inline int run_filter(bus_dma_tag_t dmat, bus_addr_t paddr) { int retval; retval = 0; do { if (paddr > dmat->lowaddr && paddr <= dmat->highaddr && (dmat->filter == NULL || (*dmat->filter)(dmat->filterarg, paddr) != 0)) retval = 1; dmat = dmat->parent; } while (retval == 0 && dmat != NULL); return (retval); } #define BUS_DMA_MIN_ALLOC_COMP BUS_DMA_BUS4 /* * Allocate a device specific dma_tag. */ int bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment, bus_size_t boundary, bus_addr_t lowaddr, bus_addr_t highaddr, bus_dma_filter_t *filter, void *filterarg, bus_size_t maxsize, int nsegments, bus_size_t maxsegsz, int flags, bus_dma_tag_t *dmat) { bus_dma_tag_t newtag; int error = 0; /* Return a NULL tag on failure */ *dmat = NULL; newtag = malloc(sizeof(*newtag), M_DEVBUF, M_INTWAIT); newtag->parent = parent; newtag->alignment = alignment; newtag->boundary = boundary; newtag->lowaddr = trunc_page((vm_paddr_t)lowaddr) + (PAGE_SIZE - 1); newtag->highaddr = trunc_page((vm_paddr_t)highaddr) + (PAGE_SIZE - 1); newtag->filter = filter; newtag->filterarg = filterarg; newtag->maxsize = maxsize; newtag->nsegments = nsegments; newtag->maxsegsz = maxsegsz; newtag->flags = flags; newtag->ref_count = 1; /* Count ourself */ newtag->map_count = 0; newtag->segments = NULL; /* Take into account any restrictions imposed by our parent tag */ if (parent != NULL) { newtag->lowaddr = MIN(parent->lowaddr, newtag->lowaddr); newtag->highaddr = MAX(parent->highaddr, newtag->highaddr); /* * XXX Not really correct??? Probably need to honor boundary * all the way up the inheritence chain. */ newtag->boundary = MAX(parent->boundary, newtag->boundary); if (newtag->filter == NULL) { /* * Short circuit looking at our parent directly * since we have encapsulated all of its information */ newtag->filter = parent->filter; newtag->filterarg = parent->filterarg; newtag->parent = parent->parent; } if (newtag->parent != NULL) { parent->ref_count++; } } if (newtag->lowaddr < ptoa(Maxmem) && (flags & BUS_DMA_ALLOCNOW) != 0) { /* Must bounce */ if (lowaddr > bounce_lowaddr) { /* * Go through the pool and kill any pages * that don't reside below lowaddr. */ panic("bus_dma_tag_create: page reallocation " "not implemented"); } if (ptoa(total_bpages) < maxsize) { int pages; pages = atop(maxsize) - total_bpages; /* Add pages to our bounce pool */ if (alloc_bounce_pages(newtag, pages) < pages) error = ENOMEM; } /* Performed initial allocation */ newtag->flags |= BUS_DMA_MIN_ALLOC_COMP; } if (error != 0) { free(newtag, M_DEVBUF); } else { *dmat = newtag; } return (error); } int bus_dma_tag_destroy(bus_dma_tag_t dmat) { if (dmat != NULL) { if (dmat->map_count != 0) return (EBUSY); while (dmat != NULL) { bus_dma_tag_t parent; parent = dmat->parent; dmat->ref_count--; if (dmat->ref_count == 0) { if (dmat->segments != NULL) free(dmat->segments, M_DEVBUF); free(dmat, M_DEVBUF); /* * Last reference count, so * release our reference * count on our parent. */ dmat = parent; } else dmat = NULL; } } return (0); } /* * Allocate a handle for mapping from kva/uva/physical * address space into bus device space. */ int bus_dmamap_create(bus_dma_tag_t dmat, int flags, bus_dmamap_t *mapp) { int error; error = 0; if (dmat->segments == NULL) { KKASSERT(dmat->nsegments && dmat->nsegments < 16384); dmat->segments = malloc(sizeof(bus_dma_segment_t) * dmat->nsegments, M_DEVBUF, M_INTWAIT); } if (dmat->lowaddr < ptoa(Maxmem)) { /* Must bounce */ int maxpages; *mapp = malloc(sizeof(**mapp), M_DEVBUF, M_INTWAIT); if (*mapp == NULL) { return (ENOMEM); } else { /* Initialize the new map */ bzero(*mapp, sizeof(**mapp)); STAILQ_INIT(&((*mapp)->bpages)); } /* * Attempt to add pages to our pool on a per-instance * basis up to a sane limit. */ maxpages = MIN(MAX_BPAGES, Maxmem - atop(dmat->lowaddr)); if ((dmat->flags & BUS_DMA_MIN_ALLOC_COMP) == 0 || (dmat->map_count > 0 && total_bpages < maxpages)) { int pages; if (dmat->lowaddr > bounce_lowaddr) { /* * Go through the pool and kill any pages * that don't reside below lowaddr. */ panic("bus_dmamap_create: page reallocation " "not implemented"); } pages = atop(dmat->maxsize); pages = MIN(maxpages - total_bpages, pages); error = alloc_bounce_pages(dmat, pages); if ((dmat->flags & BUS_DMA_MIN_ALLOC_COMP) == 0) { if (error == 0) dmat->flags |= BUS_DMA_MIN_ALLOC_COMP; } else { error = 0; } } } else { *mapp = NULL; } if (error == 0) dmat->map_count++; return (error); } /* * Destroy a handle for mapping from kva/uva/physical * address space into bus device space. */ int bus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map) { if (map != NULL) { if (STAILQ_FIRST(&map->bpages) != NULL) return (EBUSY); free(map, M_DEVBUF); } dmat->map_count--; return (0); } /* * Allocate a piece of memory that can be efficiently mapped into * bus device space based on the constraints lited in the dma tag. * * mapp is degenerate. By definition this allocation should not require * bounce buffers so do not allocate a dma map. */ int bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags, bus_dmamap_t *mapp) { int mflags; /* If we succeed, no mapping/bouncing will be required */ *mapp = NULL; if (dmat->segments == NULL) { KKASSERT(dmat->nsegments < 16384); dmat->segments = malloc(sizeof(bus_dma_segment_t) * dmat->nsegments, M_DEVBUF, M_INTWAIT); } if (flags & BUS_DMA_NOWAIT) mflags = M_NOWAIT; else mflags = M_WAITOK; if (flags & BUS_DMA_ZERO) mflags |= M_ZERO; if ((dmat->maxsize <= PAGE_SIZE) && dmat->lowaddr >= ptoa(Maxmem)) { *vaddr = malloc(dmat->maxsize, M_DEVBUF, mflags); /* * XXX Check whether the allocation crossed a page boundary * and retry with power-of-2 alignment in that case. */ if ((((intptr_t)*vaddr) & PAGE_MASK) != (((intptr_t)*vaddr + dmat->maxsize) & PAGE_MASK)) { size_t size; free(*vaddr, M_DEVBUF); /* XXX check for overflow? */ for (size = 1; size <= dmat->maxsize; size <<= 1) ; *vaddr = malloc(size, M_DEVBUF, mflags); } } else { /* * XXX Use Contigmalloc until it is merged into this facility * and handles multi-seg allocations. Nobody is doing * multi-seg allocations yet though. */ *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF, mflags, 0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul, dmat->boundary); } if (*vaddr == NULL) return (ENOMEM); return (0); } /* * Free a piece of memory and it's allociated dmamap, that was allocated * via bus_dmamem_alloc. Make the same choice for free/contigfree. */ void bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map) { /* * dmamem does not need to be bounced, so the map should be * NULL */ if (map != NULL) panic("bus_dmamem_free: Invalid map freed\n"); if ((dmat->maxsize <= PAGE_SIZE) && dmat->lowaddr >= ptoa(Maxmem)) free(vaddr, M_DEVBUF); else contigfree(vaddr, dmat->maxsize, M_DEVBUF); } #define BUS_DMAMAP_NSEGS ((BUS_SPACE_MAXSIZE / PAGE_SIZE) + 1) /* * Map the buffer buf into bus space using the dmamap map. */ int bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf, bus_size_t buflen, bus_dmamap_callback_t *callback, void *callback_arg, int flags) { vm_offset_t vaddr; vm_paddr_t paddr; bus_dma_segment_t *sg; int seg; int error; vm_paddr_t nextpaddr; if (map == NULL) map = &nobounce_dmamap; error = 0; /* * If we are being called during a callback, pagesneeded will * be non-zero, so we can avoid doing the work twice. */ if (dmat->lowaddr < ptoa(Maxmem) && map->pagesneeded == 0) { vm_offset_t vendaddr; /* * Count the number of bounce pages * needed in order to complete this transfer */ vaddr = trunc_page((vm_offset_t)buf); vendaddr = (vm_offset_t)buf + buflen; while (vaddr < vendaddr) { paddr = pmap_kextract(vaddr); if (run_filter(dmat, paddr) != 0) { map->pagesneeded++; } vaddr += PAGE_SIZE; } } /* Reserve Necessary Bounce Pages */ if (map->pagesneeded != 0) { int s; s = splhigh(); if (reserve_bounce_pages(dmat, map) != 0) { /* Queue us for resources */ map->dmat = dmat; map->buf = buf; map->buflen = buflen; map->callback = callback; map->callback_arg = callback_arg; STAILQ_INSERT_TAIL(&bounce_map_waitinglist, map, links); splx(s); return (EINPROGRESS); } splx(s); } vaddr = (vm_offset_t)buf; sg = dmat->segments; seg = 1; sg->ds_len = 0; nextpaddr = 0; do { bus_size_t size; paddr = pmap_kextract(vaddr); size = PAGE_SIZE - (paddr & PAGE_MASK); if (size > buflen) size = buflen; if (map->pagesneeded != 0 && run_filter(dmat, paddr)) { paddr = add_bounce_page(dmat, map, vaddr, size); } if (sg->ds_len == 0) { sg->ds_addr = paddr; sg->ds_len = size; } else if (paddr == nextpaddr) { sg->ds_len += size; } else { /* Go to the next segment */ sg++; seg++; if (seg > dmat->nsegments) break; sg->ds_addr = paddr; sg->ds_len = size; } vaddr += size; nextpaddr = paddr + size; buflen -= size; } while (buflen > 0); if (buflen != 0) { printf("bus_dmamap_load: Too many segs! buf_len = 0x%lx\n", (u_long)buflen); error = EFBIG; } (*callback)(callback_arg, dmat->segments, seg, error); return (0); } /* * Utility function to load a linear buffer. lastaddrp holds state * between invocations (for multiple-buffer loads). segp contains * the starting segment on entrace, and the ending segment on exit. * first indicates if this is the first invocation of this function. */ static int _bus_dmamap_load_buffer(bus_dma_tag_t dmat, void *buf, bus_size_t buflen, struct thread *td, int flags, vm_offset_t *lastaddrp, int *segp, int first) { bus_dma_segment_t *segs; bus_size_t sgsize; bus_addr_t curaddr, lastaddr, baddr, bmask; vm_offset_t vaddr = (vm_offset_t)buf; int seg; pmap_t pmap; if (td->td_proc != NULL) pmap = vmspace_pmap(td->td_proc->p_vmspace); else pmap = NULL; segs = dmat->segments; lastaddr = *lastaddrp; bmask = ~(dmat->boundary - 1); for (seg = *segp; buflen > 0 ; ) { /* * Get the physical address for this segment. */ if (pmap) curaddr = pmap_extract(pmap, vaddr); else curaddr = pmap_kextract(vaddr); /* * Compute the segment size, and adjust counts. */ sgsize = PAGE_SIZE - ((u_long)curaddr & PAGE_MASK); if (buflen < sgsize) sgsize = buflen; /* * Make sure we don't cross any boundaries. */ if (dmat->boundary > 0) { baddr = (curaddr + dmat->boundary) & bmask; if (sgsize > (baddr - curaddr)) sgsize = (baddr - curaddr); } /* * Insert chunk into a segment, coalescing with * previous segment if possible. */ if (first) { segs[seg].ds_addr = curaddr; segs[seg].ds_len = sgsize; first = 0; } else { if (curaddr == lastaddr && (segs[seg].ds_len + sgsize) <= dmat->maxsegsz && (dmat->boundary == 0 || (segs[seg].ds_addr & bmask) == (curaddr & bmask))) segs[seg].ds_len += sgsize; else { if (++seg >= dmat->nsegments) break; segs[seg].ds_addr = curaddr; segs[seg].ds_len = sgsize; } } lastaddr = curaddr + sgsize; vaddr += sgsize; buflen -= sgsize; } *segp = seg; *lastaddrp = lastaddr; /* * Did we fit? */ return (buflen != 0 ? EFBIG : 0); /* XXX better return value here? */ } /* * Like _bus_dmamap_load(), but for mbufs. */ int bus_dmamap_load_mbuf(bus_dma_tag_t dmat, bus_dmamap_t map, struct mbuf *m0, bus_dmamap_callback2_t *callback, void *callback_arg, int flags) { int nsegs, error; KASSERT(dmat->lowaddr >= ptoa(Maxmem) || map != NULL, ("bus_dmamap_load_mbuf: No support for bounce pages!")); KASSERT(m0->m_flags & M_PKTHDR, ("bus_dmamap_load_mbuf: no packet header")); nsegs = 0; error = 0; if (m0->m_pkthdr.len <= dmat->maxsize) { int first = 1; vm_offset_t lastaddr = 0; struct mbuf *m; for (m = m0; m != NULL && error == 0; m = m->m_next) { error = _bus_dmamap_load_buffer(dmat, m->m_data, m->m_len, curthread, flags, &lastaddr, &nsegs, first); first = 0; } } else { error = EINVAL; } if (error) { /* force "no valid mappings" in callback */ (*callback)(callback_arg, dmat->segments, 0, 0, error); } else { (*callback)(callback_arg, dmat->segments, nsegs+1, m0->m_pkthdr.len, error); } return (error); } /* * Like _bus_dmamap_load(), but for uios. */ int bus_dmamap_load_uio(bus_dma_tag_t dmat, bus_dmamap_t map, struct uio *uio, bus_dmamap_callback2_t *callback, void *callback_arg, int flags) { vm_offset_t lastaddr; int nsegs, error, first, i; bus_size_t resid; struct iovec *iov; struct thread *td = NULL; KASSERT(dmat->lowaddr >= ptoa(Maxmem) || map != NULL, ("bus_dmamap_load_uio: No support for bounce pages!")); resid = uio->uio_resid; iov = uio->uio_iov; if (uio->uio_segflg == UIO_USERSPACE) { td = uio->uio_td; KASSERT(td != NULL && td->td_proc != NULL, ("bus_dmamap_load_uio: USERSPACE but no proc")); } nsegs = 0; error = 0; first = 1; for (i = 0; i < uio->uio_iovcnt && resid != 0 && !error; i++) { /* * Now at the first iovec to load. Load each iovec * until we have exhausted the residual count. */ bus_size_t minlen = resid < iov[i].iov_len ? resid : iov[i].iov_len; caddr_t addr = (caddr_t) iov[i].iov_base; error = _bus_dmamap_load_buffer(dmat, addr, minlen, td, flags, &lastaddr, &nsegs, first); first = 0; resid -= minlen; } if (error) { /* force "no valid mappings" in callback */ (*callback)(callback_arg, dmat->segments, 0, 0, error); } else { (*callback)(callback_arg, dmat->segments, nsegs+1, uio->uio_resid, error); } return (error); } /* * Release the mapping held by map. */ void _bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map) { struct bounce_page *bpage; while ((bpage = STAILQ_FIRST(&map->bpages)) != NULL) { STAILQ_REMOVE_HEAD(&map->bpages, links); free_bounce_page(dmat, bpage); } } void _bus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, bus_dmasync_op_t op) { struct bounce_page *bpage; if ((bpage = STAILQ_FIRST(&map->bpages)) != NULL) { /* * Handle data bouncing. We might also * want to add support for invalidating * the caches on broken hardware */ switch (op) { case BUS_DMASYNC_PREWRITE: while (bpage != NULL) { bcopy((void *)bpage->datavaddr, (void *)bpage->vaddr, bpage->datacount); bpage = STAILQ_NEXT(bpage, links); } break; case BUS_DMASYNC_POSTREAD: while (bpage != NULL) { bcopy((void *)bpage->vaddr, (void *)bpage->datavaddr, bpage->datacount); bpage = STAILQ_NEXT(bpage, links); } break; case BUS_DMASYNC_PREREAD: case BUS_DMASYNC_POSTWRITE: /* No-ops */ break; } } } static int alloc_bounce_pages(bus_dma_tag_t dmat, u_int numpages) { int count; count = 0; if (total_bpages == 0) { STAILQ_INIT(&bounce_page_list); STAILQ_INIT(&bounce_map_waitinglist); STAILQ_INIT(&bounce_map_callbacklist); } while (numpages > 0) { struct bounce_page *bpage; int s; bpage = (struct bounce_page *)malloc(sizeof(*bpage), M_DEVBUF, M_INTWAIT); if (bpage == NULL) break; bzero(bpage, sizeof(*bpage)); bpage->vaddr = (vm_offset_t)contigmalloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT, 0ul, dmat->lowaddr, PAGE_SIZE, 0); if (bpage->vaddr == NULL) { free(bpage, M_DEVBUF); break; } bpage->busaddr = pmap_kextract(bpage->vaddr); s = splhigh(); STAILQ_INSERT_TAIL(&bounce_page_list, bpage, links); total_bpages++; free_bpages++; splx(s); count++; numpages--; } return (count); } static int reserve_bounce_pages(bus_dma_tag_t dmat, bus_dmamap_t map) { int pages; pages = MIN(free_bpages, map->pagesneeded - map->pagesreserved); free_bpages -= pages; reserved_bpages += pages; map->pagesreserved += pages; pages = map->pagesneeded - map->pagesreserved; return (pages); } static bus_addr_t add_bounce_page(bus_dma_tag_t dmat, bus_dmamap_t map, vm_offset_t vaddr, bus_size_t size) { int s; struct bounce_page *bpage; if (map->pagesneeded == 0) panic("add_bounce_page: map doesn't need any pages"); map->pagesneeded--; if (map->pagesreserved == 0) panic("add_bounce_page: map doesn't need any pages"); map->pagesreserved--; s = splhigh(); bpage = STAILQ_FIRST(&bounce_page_list); if (bpage == NULL) panic("add_bounce_page: free page list is empty"); STAILQ_REMOVE_HEAD(&bounce_page_list, links); reserved_bpages--; active_bpages++; splx(s); bpage->datavaddr = vaddr; bpage->datacount = size; STAILQ_INSERT_TAIL(&(map->bpages), bpage, links); return (bpage->busaddr); } static void free_bounce_page(bus_dma_tag_t dmat, struct bounce_page *bpage) { int s; struct bus_dmamap *map; bpage->datavaddr = 0; bpage->datacount = 0; s = splhigh(); STAILQ_INSERT_HEAD(&bounce_page_list, bpage, links); free_bpages++; active_bpages--; if ((map = STAILQ_FIRST(&bounce_map_waitinglist)) != NULL) { if (reserve_bounce_pages(map->dmat, map) == 0) { STAILQ_REMOVE_HEAD(&bounce_map_waitinglist, links); STAILQ_INSERT_TAIL(&bounce_map_callbacklist, map, links); busdma_swi_pending = 1; setsoftvm(); } } splx(s); } void busdma_swi() { int s; struct bus_dmamap *map; s = splhigh(); while ((map = STAILQ_FIRST(&bounce_map_callbacklist)) != NULL) { STAILQ_REMOVE_HEAD(&bounce_map_callbacklist, links); splx(s); bus_dmamap_load(map->dmat, map, map->buf, map->buflen, map->callback, map->callback_arg, /*flags*/0); s = splhigh(); } splx(s); }