busdma_machdep.c revision 109919
1/*
2 * Copyright (c) 2002 Peter Grehan
3 * Copyright (c) 1997, 1998 Justin T. Gibbs.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer,
11 *    without modification, immediately at the beginning of the file.
12 * 2. The name of the author may not be used to endorse or promote products
13 *    derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
19 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 *   From i386/busdma_machdep.c,v 1.26 2002/04/19 22:58:09 alfred
28 */
29
30#ifndef lint
31static const char rcsid[] =
32  "$FreeBSD: head/sys/powerpc/powerpc/busdma_machdep.c 109919 2003-01-27 04:27:01Z benno $";
33#endif /* not lint */
34
35/*
36 * MacPPC bus dma support routines
37 */
38
39#include <sys/param.h>
40#include <sys/systm.h>
41#include <sys/malloc.h>
42#include <sys/bus.h>
43#include <sys/interrupt.h>
44#include <sys/lock.h>
45#include <sys/proc.h>
46#include <sys/mutex.h>
47#include <sys/mbuf.h>
48#include <sys/uio.h>
49
50#include <vm/vm.h>
51#include <vm/vm_page.h>
52#include <vm/vm_map.h>
53
54#include <machine/bus.h>
55#include <machine/cpufunc.h>
56
57struct bus_dma_tag {
58	bus_dma_tag_t     parent;
59	bus_size_t        alignment;
60	bus_size_t        boundary;
61	bus_addr_t        lowaddr;
62	bus_addr_t        highaddr;
63	bus_dma_filter_t *filter;
64	void             *filterarg;
65	bus_size_t        maxsize;
66	u_int             nsegments;
67	bus_size_t        maxsegsz;
68	int               flags;
69	int               ref_count;
70	int               map_count;
71};
72
73struct bus_dmamap {
74        bus_dma_tag_t          dmat;
75        void                  *buf;             /* unmapped buffer pointer */
76        bus_size_t             buflen;          /* unmapped buffer length */
77        bus_dmamap_callback_t *callback;
78        void                  *callback_arg;
79};
80
81/*
82 * Allocate a device specific dma_tag.
83 */
84int
85bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment,
86		   bus_size_t boundary, bus_addr_t lowaddr,
87		   bus_addr_t highaddr, bus_dma_filter_t *filter,
88		   void *filterarg, bus_size_t maxsize, int nsegments,
89		   bus_size_t maxsegsz, int flags, bus_dma_tag_t *dmat)
90{
91	bus_dma_tag_t newtag;
92	int error = 0;
93
94	/* Return a NULL tag on failure */
95	*dmat = NULL;
96
97	newtag = (bus_dma_tag_t)malloc(sizeof(*newtag), M_DEVBUF, M_NOWAIT);
98	if (newtag == NULL)
99		return (ENOMEM);
100
101	newtag->parent = parent;
102	newtag->alignment = alignment;
103	newtag->boundary = boundary;
104	newtag->lowaddr = trunc_page((vm_offset_t)lowaddr) + (PAGE_SIZE - 1);
105	newtag->highaddr = trunc_page((vm_offset_t)highaddr) + (PAGE_SIZE - 1);
106	newtag->filter = filter;
107	newtag->filterarg = filterarg;
108        newtag->maxsize = maxsize;
109        newtag->nsegments = nsegments;
110	newtag->maxsegsz = maxsegsz;
111	newtag->flags = flags;
112	newtag->ref_count = 1; /* Count ourself */
113	newtag->map_count = 0;
114
115        /*
116	 * Take into account any restrictions imposed by our parent tag
117	 */
118        if (parent != NULL) {
119                newtag->lowaddr = min(parent->lowaddr, newtag->lowaddr);
120                newtag->highaddr = max(parent->highaddr, newtag->highaddr);
121
122                /*
123                 * XXX Not really correct??? Probably need to honor boundary
124                 *     all the way up the inheritence chain.
125                 */
126                newtag->boundary = max(parent->boundary, newtag->boundary);
127                if (newtag->filter == NULL) {
128                        /*
129                         * Short circuit looking at our parent directly
130                         * since we have encapsulated all of its information
131                         */
132                        newtag->filter = parent->filter;
133                        newtag->filterarg = parent->filterarg;
134                        newtag->parent = parent->parent;
135		}
136                if (newtag->parent != NULL) {
137                        parent->ref_count++;
138		}
139	}
140
141	*dmat = newtag;
142	return (error);
143}
144
145int
146bus_dma_tag_destroy(bus_dma_tag_t dmat)
147{
148	if (dmat != NULL) {
149
150                if (dmat->map_count != 0)
151                        return (EBUSY);
152
153                while (dmat != NULL) {
154                        bus_dma_tag_t parent;
155
156                        parent = dmat->parent;
157                        dmat->ref_count--;
158                        if (dmat->ref_count == 0) {
159                                free(dmat, M_DEVBUF);
160                                /*
161                                 * Last reference count, so
162                                 * release our reference
163                                 * count on our parent.
164                                 */
165                                dmat = parent;
166                        } else
167                                dmat = NULL;
168                }
169        }
170        return (0);
171}
172
173/*
174 * Allocate a handle for mapping from kva/uva/physical
175 * address space into bus device space.
176 */
177int
178bus_dmamap_create(bus_dma_tag_t dmat, int flags, bus_dmamap_t *mapp)
179{
180	*mapp = NULL;
181	dmat->map_count++;
182
183	return (0);
184}
185
186/*
187 * Destroy a handle for mapping from kva/uva/physical
188 * address space into bus device space.
189 */
190int
191bus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map)
192{
193        if (map != NULL) {
194		panic("dmamap_destroy: NULL?\n");
195        }
196        dmat->map_count--;
197        return (0);
198}
199
200/*
201 * Allocate a piece of memory that can be efficiently mapped into
202 * bus device space based on the constraints lited in the dma tag.
203 * A dmamap to for use with dmamap_load is also allocated.
204 */
205int
206bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags,
207                 bus_dmamap_t *mapp)
208{
209        *mapp = NULL;
210
211        if (dmat->maxsize <= PAGE_SIZE) {
212                *vaddr = malloc(dmat->maxsize, M_DEVBUF,
213                             (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : 0);
214        } else {
215                /*
216                 * XXX Use Contigmalloc until it is merged into this facility
217                 *     and handles multi-seg allocations.  Nobody is doing
218                 *     multi-seg allocations yet though.
219                 */
220                *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF,
221                    (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : 0,
222                    0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul,
223                    dmat->boundary);
224        }
225
226        if (*vaddr == NULL)
227                return (ENOMEM);
228
229        return (0);
230}
231
232/*
233 * Free a piece of memory and it's allocated dmamap, that was allocated
234 * via bus_dmamem_alloc.  Make the same choice for free/contigfree.
235 */
236void
237bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map)
238{
239        if (map != NULL)
240                panic("bus_dmamem_free: Invalid map freed\n");
241        if (dmat->maxsize <= PAGE_SIZE)
242		free(vaddr, M_DEVBUF);
243        else
244		contigfree(vaddr, dmat->maxsize, M_DEVBUF);
245}
246
247/*
248 * Map the buffer buf into bus space using the dmamap map.
249 */
250int
251bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf,
252                bus_size_t buflen, bus_dmamap_callback_t *callback,
253                void *callback_arg, int flags)
254{
255        vm_offset_t             vaddr;
256        vm_offset_t             paddr;
257#ifdef __GNUC__
258        bus_dma_segment_t       dm_segments[dmat->nsegments];
259#else
260        bus_dma_segment_t       dm_segments[BUS_DMAMAP_NSEGS];
261#endif
262        bus_dma_segment_t      *sg;
263        int                     seg;
264        int                     error = 0;
265        vm_offset_t             nextpaddr;
266
267        if (map != NULL)
268		panic("bus_dmamap_load: Invalid map\n");
269
270        vaddr = (vm_offset_t)buf;
271        sg = &dm_segments[0];
272        seg = 1;
273        sg->ds_len = 0;
274        nextpaddr = 0;
275
276        do {
277		bus_size_t      size;
278
279                paddr = pmap_kextract(vaddr);
280                size = PAGE_SIZE - (paddr & PAGE_MASK);
281                if (size > buflen)
282                        size = buflen;
283
284                if (sg->ds_len == 0) {
285                        sg->ds_addr = paddr;
286                        sg->ds_len = size;
287                } else if (paddr == nextpaddr) {
288                        sg->ds_len += size;
289                } else {
290                        /* Go to the next segment */
291                        sg++;
292                        seg++;
293                        if (seg > dmat->nsegments)
294				break;
295                        sg->ds_addr = paddr;
296                        sg->ds_len = size;
297                }
298                vaddr += size;
299                nextpaddr = paddr + size;
300                buflen -= size;
301
302	} while (buflen > 0);
303
304	if (buflen != 0) {
305		printf("bus_dmamap_load: Too many segs! buf_len = 0x%lx\n",
306		    (u_long)buflen);
307		error = EFBIG;
308	}
309
310	(*callback)(callback_arg, dm_segments, seg, error);
311
312	return (0);
313}
314
315/*
316 * Utility function to load a linear buffer.  lastaddrp holds state
317 * between invocations (for multiple-buffer loads).  segp contains
318 * the starting segment on entrance, and the ending segment on exit.
319 * first indicates if this is the first invocation of this function.
320 */
321static int
322bus_dmamap_load_buffer(bus_dma_tag_t dmat, bus_dma_segment_t segs[],
323    void *buf, bus_size_t buflen, struct thread *td,
324    int flags, vm_offset_t *lastaddrp, int *segp,
325    int first)
326{
327	bus_size_t sgsize;
328	bus_addr_t curaddr, lastaddr, baddr, bmask;
329	vm_offset_t vaddr = (vm_offset_t)buf;
330	int seg;
331	pmap_t pmap;
332
333	if (td != NULL)
334		pmap = vmspace_pmap(td->td_proc->p_vmspace);
335	else
336		pmap = NULL;
337
338	lastaddr = *lastaddrp;
339	bmask = ~(dmat->boundary - 1);
340
341	for (seg = *segp; buflen > 0 ; ) {
342		/*
343		 * Get the physical address for this segment.
344		 */
345		if (pmap)
346			curaddr = pmap_extract(pmap, vaddr);
347		else
348			curaddr = pmap_kextract(vaddr);
349
350		/*
351		 * Compute the segment size, and adjust counts.
352		 */
353		sgsize = PAGE_SIZE - ((u_long)curaddr & PAGE_MASK);
354		if (buflen < sgsize)
355			sgsize = buflen;
356
357		/*
358		 * Make sure we don't cross any boundaries.
359		 */
360		if (dmat->boundary > 0) {
361			baddr = (curaddr + dmat->boundary) & bmask;
362			if (sgsize > (baddr - curaddr))
363				sgsize = (baddr - curaddr);
364		}
365
366		/*
367		 * Insert chunk into a segment, coalescing with
368		 * the previous segment if possible.
369		 */
370		if (first) {
371			segs[seg].ds_addr = curaddr;
372			segs[seg].ds_len = sgsize;
373			first = 0;
374		} else {
375			if (curaddr == lastaddr &&
376			    (segs[seg].ds_len + sgsize) <= dmat->maxsegsz &&
377			    (dmat->boundary == 0 ||
378			     (segs[seg].ds_addr & bmask) == (curaddr & bmask)))
379				segs[seg].ds_len += sgsize;
380			else {
381				if (++seg >= dmat->nsegments)
382					break;
383				segs[seg].ds_addr = curaddr;
384				segs[seg].ds_len = sgsize;
385			}
386		}
387
388		lastaddr = curaddr + sgsize;
389		vaddr += sgsize;
390		buflen -= sgsize;
391	}
392
393	*segp = seg;
394	*lastaddrp = lastaddr;
395
396	/*
397	 * Did we fit?
398	 */
399	return (buflen != 0 ? EFBIG : 0); /* XXX better return value here? */
400}
401
402/*
403 * Like bus_dmamap_load(), but for mbufs.
404 */
405int
406bus_dmamap_load_mbuf(bus_dma_tag_t dmat, bus_dmamap_t map, struct mbuf *m0,
407		     bus_dmamap_callback2_t *callback, void *callback_arg,
408		     int flags)
409{
410#ifdef __GNUC__
411	bus_dma_segment_t dm_segments[dmat->nsegments];
412#else
413	bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
414#endif
415	int nsegs = 0, error = 0;
416
417	KASSERT(m0->m_flags & M_PKTHDR,
418	    ("bus_dmamap_load_mbuf: no packet header"));
419
420	if (m0->m_pkthdr.len <= dmat->maxsize) {
421		int first = 1;
422		vm_offset_t lastaddr = 0;
423		struct mbuf *m;
424
425		for (m = m0; m != NULL && error == 0; m = m->m_next) {
426			error = bus_dmamap_load_buffer(dmat, dm_segments,
427			    m->m_data, m->m_len, NULL, flags,
428			    &lastaddr, &nsegs, first);
429			first = 0;
430		}
431	} else {
432		error = EINVAL;
433	}
434
435	if (error) {
436		/*
437		 * force "no valid mappings" on error in callback.
438		 */
439		(*callback)(callback_arg, dm_segments, 0, 0, error);
440	} else {
441		(*callback)(callback_arg, dm_segments, nsegs+1,
442		    m0->m_pkthdr.len, error);
443	}
444	return (error);
445}
446
447/*
448 * Like bus_dmamap_load(), but for uios.
449 */
450int
451bus_dmamap_load_uio(bus_dma_tag_t dmat, bus_dmamap_t map, struct uio *uio,
452    bus_dmamap_callback2_t *callback, void *callback_arg,
453    int flags)
454{
455	vm_offset_t lastaddr;
456#ifdef __GNUC__
457	bus_dma_segment_t dm_segments[dmat->nsegments];
458#else
459	bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
460#endif
461	int nsegs, i, error, first;
462	bus_size_t resid;
463	struct iovec *iov;
464	struct proc *td = NULL;
465
466	resid = uio->uio_resid;
467	iov = uio->uio_iov;
468
469	if (uio->uio_segflg == UIO_USERSPACE) {
470		td = uio->uio_td;
471		KASSERT(td != NULL,
472		    ("bus_dmamap_load_uio: USERSPACE but no proc"));
473	}
474
475	first = 1;
476	nsegs = error = 0;
477	for (i = 0; i < uio->uio_iovcnt && resid != 0 && !error; i++) {
478		/*
479		 * Now at the first iovec to load.  Load each iovec
480		 * until we have exhausted the residual count.
481		 */
482		bus_size_t minlen =
483		    resid < iov[i].iov_len ? resid : iov[i].iov_len;
484		caddr_t addr = (caddr_t) iov[i].iov_base;
485
486		error = bus_dmamap_load_buffer(dmat, dm_segments, addr,
487		    minlen, td, flags, &lastaddr, &nsegs, first);
488
489		first = 0;
490
491		resid -= minlen;
492	}
493
494	if (error) {
495		/*
496		 * force "no valid mappings" on error in callback.
497		 */
498		(*callback)(callback_arg, dm_segments, 0, 0, error);
499	} else {
500		(*callback)(callback_arg, dm_segments, nsegs+1,
501		    uio->uio_resid, error);
502	}
503
504	return (error);
505}
506
507/*
508 * Release the mapping held by map. A no-op on PowerPC.
509 */
510void
511bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map)
512{}
513
514void
515bus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, bus_dmasync_op_t op)
516{
517
518	if ((op == BUS_DMASYNC_PREREAD) || (op == BUS_DMASYNC_PREWRITE))
519		powerpc_mb();
520}
521