busdma_machdep.c revision 99657
138514Sdfr/*
238514Sdfr * Copyright (c) 2002 Peter Grehan
338514Sdfr * Copyright (c) 1997, 1998 Justin T. Gibbs.
438514Sdfr * All rights reserved.
538514Sdfr *
638514Sdfr * Redistribution and use in source and binary forms, with or without
738514Sdfr * modification, are permitted provided that the following conditions
838514Sdfr * are met:
938514Sdfr * 1. Redistributions of source code must retain the above copyright
1038514Sdfr *    notice, this list of conditions, and the following disclaimer,
1138514Sdfr *    without modification, immediately at the beginning of the file.
1238514Sdfr * 2. The name of the author may not be used to endorse or promote products
1338514Sdfr *    derived from this software without specific prior written permission.
1438514Sdfr *
1538514Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1638514Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1738514Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1838514Sdfr * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
1938514Sdfr * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2038514Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2138514Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2238514Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2338514Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2438514Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2538514Sdfr * SUCH DAMAGE.
2650477Speter *
2738514Sdfr *   From i386/busdma_machdep.c,v 1.26 2002/04/19 22:58:09 alfred
2838514Sdfr */
2938514Sdfr
3038514Sdfr#ifndef lint
3138514Sdfrstatic const char rcsid[] =
3238514Sdfr  "$FreeBSD: head/sys/powerpc/powerpc/busdma_machdep.c 99657 2002-07-09 12:47:14Z benno $";
3338514Sdfr#endif /* not lint */
3438514Sdfr
3538514Sdfr/*
3638514Sdfr * MacPPC bus dma support routines
3738514Sdfr */
3838514Sdfr
3938514Sdfr#include <sys/param.h>
4039071Sdfr#include <sys/systm.h>
4139071Sdfr#include <sys/malloc.h>
4254655Seivind#include <sys/bus.h>
4339071Sdfr#include <sys/interrupt.h>
4452128Speter#include <sys/lock.h>
4539071Sdfr#include <sys/proc.h>
4639071Sdfr#include <sys/mutex.h>
4739071Sdfr
4852128Speter#include <vm/vm.h>
4939071Sdfr#include <vm/vm_page.h>
5039071Sdfr
5139071Sdfr#include <machine/bus.h>
5240156Speter
5338514Sdfrstruct bus_dma_tag {
5438514Sdfr	bus_dma_tag_t     parent;
5543301Sdillon	bus_size_t        alignment;
5643309Sdillon	bus_size_t        boundary;
5738514Sdfr	bus_addr_t        lowaddr;
5843301Sdillon	bus_addr_t        highaddr;
5938514Sdfr	bus_dma_filter_t *filter;
6040156Speter	void             *filterarg;
6140156Speter	bus_size_t        maxsize;
6238514Sdfr	u_int             nsegments;
6338514Sdfr	bus_size_t        maxsegsz;
6440156Speter	int               flags;
6538514Sdfr	int               ref_count;
6638514Sdfr	int               map_count;
6738514Sdfr};
6838514Sdfr
6938514Sdfrstruct bus_dmamap {
7038514Sdfr        bus_dma_tag_t          dmat;
7140156Speter        void                  *buf;             /* unmapped buffer pointer */
7238514Sdfr        bus_size_t             buflen;          /* unmapped buffer length */
7338514Sdfr        bus_dmamap_callback_t *callback;
7440156Speter        void                  *callback_arg;
7540156Speter};
7640156Speter
7740156Speter/*
7840156Speter * Allocate a device specific dma_tag.
7940156Speter */
8038514Sdfrint
8139071Sdfrbus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment,
8239071Sdfr		   bus_size_t boundary, bus_addr_t lowaddr,
8339071Sdfr		   bus_addr_t highaddr, bus_dma_filter_t *filter,
8439071Sdfr		   void *filterarg, bus_size_t maxsize, int nsegments,
8539071Sdfr		   bus_size_t maxsegsz, int flags, bus_dma_tag_t *dmat)
8638514Sdfr{
8738514Sdfr	bus_dma_tag_t newtag;
8838514Sdfr	int error = 0;
8938514Sdfr
9038514Sdfr	/* Return a NULL tag on failure */
9138514Sdfr	*dmat = NULL;
9240254Speter
9339071Sdfr	newtag = (bus_dma_tag_t)malloc(sizeof(*newtag), M_DEVBUF, M_NOWAIT);
9439071Sdfr	if (newtag == NULL)
9539071Sdfr		return (ENOMEM);
9639071Sdfr
9739071Sdfr	newtag->parent = parent;
9839071Sdfr	newtag->alignment = alignment;
9939071Sdfr	newtag->boundary = boundary;
10039071Sdfr	newtag->lowaddr = trunc_page((vm_offset_t)lowaddr) + (PAGE_SIZE - 1);
10139071Sdfr	newtag->highaddr = trunc_page((vm_offset_t)highaddr) + (PAGE_SIZE - 1);
10239071Sdfr	newtag->filter = filter;
10340254Speter	newtag->filterarg = filterarg;
10440254Speter        newtag->maxsize = maxsize;
10540254Speter        newtag->nsegments = nsegments;
10640254Speter	newtag->maxsegsz = maxsegsz;
10740254Speter	newtag->flags = flags;
10840292Speter	newtag->ref_count = 1; /* Count ourself */
10940292Speter	newtag->map_count = 0;
11038514Sdfr
11138514Sdfr        /*
11238514Sdfr	 * Take into account any restrictions imposed by our parent tag
11338514Sdfr	 */
11438514Sdfr        if (parent != NULL) {
11540254Speter                newtag->lowaddr = min(parent->lowaddr, newtag->lowaddr);
11638514Sdfr                newtag->highaddr = max(parent->highaddr, newtag->highaddr);
11738514Sdfr
11838514Sdfr                /*
11938514Sdfr                 * XXX Not really correct??? Probably need to honor boundary
12038514Sdfr                 *     all the way up the inheritence chain.
12138514Sdfr                 */
12238514Sdfr                newtag->boundary = max(parent->boundary, newtag->boundary);
12338514Sdfr                if (newtag->filter == NULL) {
12438514Sdfr                        /*
12540156Speter                         * Short circuit looking at our parent directly
12640156Speter                         * since we have encapsulated all of its information
12740156Speter                         */
12840156Speter                        newtag->filter = parent->filter;
12940156Speter                        newtag->filterarg = parent->filterarg;
13040156Speter                        newtag->parent = parent->parent;
13138514Sdfr		}
13238514Sdfr                if (newtag->parent != NULL) {
13338514Sdfr                        parent->ref_count++;
13438514Sdfr		}
13538514Sdfr	}
13638514Sdfr
13738514Sdfr	*dmat = newtag;
13840156Speter	return (error);
13940156Speter}
14038514Sdfr
14138514Sdfrint
14238514Sdfrbus_dma_tag_destroy(bus_dma_tag_t dmat)
14338514Sdfr{
14440397Speter	if (dmat != NULL) {
14538514Sdfr
14638514Sdfr                if (dmat->map_count != 0)
14739071Sdfr                        return (EBUSY);
14839071Sdfr
14939071Sdfr                while (dmat != NULL) {
15038514Sdfr                        bus_dma_tag_t parent;
15140156Speter
15240156Speter                        parent = dmat->parent;
15340156Speter                        dmat->ref_count--;
15440156Speter                        if (dmat->ref_count == 0) {
15540156Speter                                free(dmat, M_DEVBUF);
15640156Speter                                /*
15740156Speter                                 * Last reference count, so
15838514Sdfr                                 * release our reference
15938514Sdfr                                 * count on our parent.
16038514Sdfr                                 */
16146694Speter                                dmat = parent;
16250275Sbde                        } else
16340156Speter                                dmat = NULL;
16440156Speter                }
16540254Speter        }
16640156Speter        return (0);
16740156Speter}
16840156Speter
16940156Speter/*
17040156Speter * Allocate a handle for mapping from kva/uva/physical
17140156Speter * address space into bus device space.
17240156Speter */
17340254Speterint
17438514Sdfrbus_dmamap_create(bus_dma_tag_t dmat, int flags, bus_dmamap_t *mapp)
17543185Sdfr{
17638514Sdfr	*mapp = NULL;
17740156Speter	dmat->map_count++;
17838514Sdfr
17938514Sdfr	return (0);
18040156Speter}
18138514Sdfr
18238514Sdfr/*
18340254Speter * Destroy a handle for mapping from kva/uva/physical
18440254Speter * address space into bus device space.
18540254Speter */
18640254Speterint
18740254Speterbus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map)
18840254Speter{
18940254Speter        if (map != NULL) {
19040254Speter		panic("dmamap_destroy: NULL?\n");
19140254Speter        }
19240254Speter        dmat->map_count--;
19340292Speter        return (0);
19440292Speter}
19540254Speter
19640254Speter/*
19740254Speter * Allocate a piece of memory that can be efficiently mapped into
19840254Speter * bus device space based on the constraints lited in the dma tag.
19940254Speter * A dmamap to for use with dmamap_load is also allocated.
20040254Speter */
20140254Speterint
20240254Speterbus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags,
20340254Speter                 bus_dmamap_t *mapp)
20440254Speter{
20540254Speter        *mapp = NULL;
20640254Speter
20740254Speter        if (dmat->maxsize <= PAGE_SIZE) {
20840254Speter                *vaddr = malloc(dmat->maxsize, M_DEVBUF,
20940254Speter                             (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK);
21040254Speter        } else {
21140254Speter                /*
21240254Speter                 * XXX Use Contigmalloc until it is merged into this facility
21340254Speter                 *     and handles multi-seg allocations.  Nobody is doing
21440254Speter                 *     multi-seg allocations yet though.
21540254Speter                 */
21640254Speter                *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF,
21740254Speter                    (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK,
21840254Speter                    0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul,
21940254Speter                    dmat->boundary);
22040254Speter        }
22140254Speter
22240254Speter        if (*vaddr == NULL)
22340254Speter                return (ENOMEM);
22440254Speter
22540254Speter        return (0);
22640254Speter}
22740254Speter
22840254Speter/*
22940254Speter * Free a piece of memory and it's allociated dmamap, that was allocated
23040254Speter * via bus_dmamem_alloc.  Make the same choice for free/contigfree.
23140254Speter */
23240254Spetervoid
23340254Speterbus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map)
23440254Speter{
23538514Sdfr        if (map != NULL)
23638514Sdfr                panic("bus_dmamem_free: Invalid map freed\n");
23738514Sdfr        if (dmat->maxsize <= PAGE_SIZE)
23839071Sdfr		free(vaddr, M_DEVBUF);
23939071Sdfr        else
24038514Sdfr		contigfree(vaddr, dmat->maxsize, M_DEVBUF);
24138514Sdfr}
24238514Sdfr
24338514Sdfr/*
24438514Sdfr * Map the buffer buf into bus space using the dmamap map.
24538514Sdfr */
24638514Sdfrint
24738514Sdfrbus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf,
24838514Sdfr                bus_size_t buflen, bus_dmamap_callback_t *callback,
24938514Sdfr                void *callback_arg, int flags)
25038514Sdfr{
25138514Sdfr        vm_offset_t             vaddr;
25238514Sdfr        vm_offset_t             paddr;
25338514Sdfr#ifdef __GNUC__
25438514Sdfr        bus_dma_segment_t       dm_segments[dmat->nsegments];
25539071Sdfr#else
25638514Sdfr        bus_dma_segment_t       dm_segments[BUS_DMAMAP_NSEGS];
25740254Speter#endif
25840254Speter        bus_dma_segment_t      *sg;
25940254Speter        int                     seg;
26038514Sdfr        int                     error = 0;
26139071Sdfr        vm_offset_t             nextpaddr;
26238514Sdfr
26338514Sdfr        if (map != NULL)
26438514Sdfr		panic("bus_dmamap_load: Invalid map\n");
26538514Sdfr
26639071Sdfr        vaddr = (vm_offset_t)buf;
26739071Sdfr        sg = &dm_segments[0];
26839071Sdfr        seg = 1;
26939071Sdfr        sg->ds_len = 0;
27039071Sdfr        nextpaddr = 0;
27139071Sdfr
27239071Sdfr        do {
27339071Sdfr		bus_size_t      size;
27439071Sdfr
27539071Sdfr                paddr = pmap_kextract(vaddr);
27639071Sdfr                size = PAGE_SIZE - (paddr & PAGE_MASK);
27739071Sdfr                if (size > buflen)
27839071Sdfr                        size = buflen;
27939071Sdfr
28039071Sdfr                if (sg->ds_len == 0) {
28139071Sdfr                        sg->ds_addr = paddr;
28239071Sdfr                        sg->ds_len = size;
28339071Sdfr                } else if (paddr == nextpaddr) {
28439071Sdfr                        sg->ds_len += size;
28539071Sdfr                } else {
28639071Sdfr                        /* Go to the next segment */
28739071Sdfr                        sg++;
28839071Sdfr                        seg++;
28939071Sdfr                        if (seg > dmat->nsegments)
29039071Sdfr				break;
29139071Sdfr                        sg->ds_addr = paddr;
29239071Sdfr                        sg->ds_len = size;
29339071Sdfr                }
29439071Sdfr                vaddr += size;
29539071Sdfr                nextpaddr = paddr + size;
29639071Sdfr                buflen -= size;
29739071Sdfr
29839071Sdfr        } while (buflen > 0);
29939071Sdfr
30039071Sdfr        if (buflen != 0) {
30138514Sdfr                printf("bus_dmamap_load: Too many segs! buf_len = 0x%lx\n",
30238514Sdfr                       (u_long)buflen);
30339071Sdfr                error = EFBIG;
30439071Sdfr        }
30539071Sdfr
30639071Sdfr        (*callback)(callback_arg, dm_segments, seg, error);
30739071Sdfr
30839071Sdfr        return (0);
30939071Sdfr}
31039071Sdfr
31140254Speter/*
31240254Speter * Release the mapping held by map.
31340254Speter */
31440254Spetervoid
31540254Speterbus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map)
31638514Sdfr{}
31738514Sdfr
31838514Sdfrvoid
31939071Sdfrbus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, bus_dmasync_op_t op)
32039071Sdfr{}
32139071Sdfr
32239071Sdfr
32339071Sdfr
32439071Sdfr
32538514Sdfr
32640156Speter