busdma_machdep.c revision 108939
116Salm/*
216Salm * Copyright (c) 2002 Peter Grehan
316Salm * 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 108939 2003-01-08 11:46:53Z grehan $";
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
56struct bus_dma_tag {
57	bus_dma_tag_t     parent;
58	bus_size_t        alignment;
59	bus_size_t        boundary;
60	bus_addr_t        lowaddr;
61	bus_addr_t        highaddr;
62	bus_dma_filter_t *filter;
63	void             *filterarg;
64	bus_size_t        maxsize;
65	u_int             nsegments;
66	bus_size_t        maxsegsz;
67	int               flags;
68	int               ref_count;
69	int               map_count;
70};
71
72struct bus_dmamap {
73        bus_dma_tag_t          dmat;
74        void                  *buf;             /* unmapped buffer pointer */
75        bus_size_t             buflen;          /* unmapped buffer length */
76        bus_dmamap_callback_t *callback;
77        void                  *callback_arg;
78};
79
80/*
81 * Allocate a device specific dma_tag.
82 */
83int
84bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment,
85		   bus_size_t boundary, bus_addr_t lowaddr,
86		   bus_addr_t highaddr, bus_dma_filter_t *filter,
87		   void *filterarg, bus_size_t maxsize, int nsegments,
88		   bus_size_t maxsegsz, int flags, bus_dma_tag_t *dmat)
89{
90	bus_dma_tag_t newtag;
91	int error = 0;
92
93	/* Return a NULL tag on failure */
94	*dmat = NULL;
95
96	newtag = (bus_dma_tag_t)malloc(sizeof(*newtag), M_DEVBUF, M_NOWAIT);
97	if (newtag == NULL)
98		return (ENOMEM);
99
100	newtag->parent = parent;
101	newtag->alignment = alignment;
102	newtag->boundary = boundary;
103	newtag->lowaddr = trunc_page((vm_offset_t)lowaddr) + (PAGE_SIZE - 1);
104	newtag->highaddr = trunc_page((vm_offset_t)highaddr) + (PAGE_SIZE - 1);
105	newtag->filter = filter;
106	newtag->filterarg = filterarg;
107        newtag->maxsize = maxsize;
108        newtag->nsegments = nsegments;
109	newtag->maxsegsz = maxsegsz;
110	newtag->flags = flags;
111	newtag->ref_count = 1; /* Count ourself */
112	newtag->map_count = 0;
113
114        /*
115	 * Take into account any restrictions imposed by our parent tag
116	 */
117        if (parent != NULL) {
118                newtag->lowaddr = min(parent->lowaddr, newtag->lowaddr);
119                newtag->highaddr = max(parent->highaddr, newtag->highaddr);
120
121                /*
122                 * XXX Not really correct??? Probably need to honor boundary
123                 *     all the way up the inheritence chain.
124                 */
125                newtag->boundary = max(parent->boundary, newtag->boundary);
126                if (newtag->filter == NULL) {
127                        /*
128                         * Short circuit looking at our parent directly
129                         * since we have encapsulated all of its information
130                         */
131                        newtag->filter = parent->filter;
132                        newtag->filterarg = parent->filterarg;
133                        newtag->parent = parent->parent;
134		}
135                if (newtag->parent != NULL) {
136                        parent->ref_count++;
137		}
138	}
139
140	*dmat = newtag;
141	return (error);
142}
143
144int
145bus_dma_tag_destroy(bus_dma_tag_t dmat)
146{
147	if (dmat != NULL) {
148
149                if (dmat->map_count != 0)
150                        return (EBUSY);
151
152                while (dmat != NULL) {
153                        bus_dma_tag_t parent;
154
155                        parent = dmat->parent;
156                        dmat->ref_count--;
157                        if (dmat->ref_count == 0) {
158                                free(dmat, M_DEVBUF);
159                                /*
160                                 * Last reference count, so
161                                 * release our reference
162                                 * count on our parent.
163                                 */
164                                dmat = parent;
165                        } else
166                                dmat = NULL;
167                }
168        }
169        return (0);
170}
171
172/*
173 * Allocate a handle for mapping from kva/uva/physical
174 * address space into bus device space.
175 */
176int
177bus_dmamap_create(bus_dma_tag_t dmat, int flags, bus_dmamap_t *mapp)
178{
179	*mapp = NULL;
180	dmat->map_count++;
181
182	return (0);
183}
184
185/*
186 * Destroy a handle for mapping from kva/uva/physical
187 * address space into bus device space.
188 */
189int
190bus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map)
191{
192        if (map != NULL) {
193		panic("dmamap_destroy: NULL?\n");
194        }
195        dmat->map_count--;
196        return (0);
197}
198
199/*
200 * Allocate a piece of memory that can be efficiently mapped into
201 * bus device space based on the constraints lited in the dma tag.
202 * A dmamap to for use with dmamap_load is also allocated.
203 */
204int
205bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags,
206                 bus_dmamap_t *mapp)
207{
208        *mapp = NULL;
209
210        if (dmat->maxsize <= PAGE_SIZE) {
211                *vaddr = malloc(dmat->maxsize, M_DEVBUF,
212                             (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK);
213        } else {
214                /*
215                 * XXX Use Contigmalloc until it is merged into this facility
216                 *     and handles multi-seg allocations.  Nobody is doing
217                 *     multi-seg allocations yet though.
218                 */
219                *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF,
220                    (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK,
221                    0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul,
222                    dmat->boundary);
223        }
224
225        if (*vaddr == NULL)
226                return (ENOMEM);
227
228        return (0);
229}
230
231/*
232 * Free a piece of memory and it's allocated dmamap, that was allocated
233 * via bus_dmamem_alloc.  Make the same choice for free/contigfree.
234 */
235void
236bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map)
237{
238        if (map != NULL)
239                panic("bus_dmamem_free: Invalid map freed\n");
240        if (dmat->maxsize <= PAGE_SIZE)
241		free(vaddr, M_DEVBUF);
242        else
243		contigfree(vaddr, dmat->maxsize, M_DEVBUF);
244}
245
246/*
247 * Map the buffer buf into bus space using the dmamap map.
248 */
249int
250bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf,
251                bus_size_t buflen, bus_dmamap_callback_t *callback,
252                void *callback_arg, int flags)
253{
254        vm_offset_t             vaddr;
255        vm_offset_t             paddr;
256#ifdef __GNUC__
257        bus_dma_segment_t       dm_segments[dmat->nsegments];
258#else
259        bus_dma_segment_t       dm_segments[BUS_DMAMAP_NSEGS];
260#endif
261        bus_dma_segment_t      *sg;
262        int                     seg;
263        int                     error = 0;
264        vm_offset_t             nextpaddr;
265
266        if (map != NULL)
267		panic("bus_dmamap_load: Invalid map\n");
268
269        vaddr = (vm_offset_t)buf;
270        sg = &dm_segments[0];
271        seg = 1;
272        sg->ds_len = 0;
273        nextpaddr = 0;
274
275        do {
276		bus_size_t      size;
277
278                paddr = pmap_kextract(vaddr);
279                size = PAGE_SIZE - (paddr & PAGE_MASK);
280                if (size > buflen)
281                        size = buflen;
282
283                if (sg->ds_len == 0) {
284                        sg->ds_addr = paddr;
285                        sg->ds_len = size;
286                } else if (paddr == nextpaddr) {
287                        sg->ds_len += size;
288                } else {
289                        /* Go to the next segment */
290                        sg++;
291                        seg++;
292                        if (seg > dmat->nsegments)
293				break;
294                        sg->ds_addr = paddr;
295                        sg->ds_len = size;
296                }
297                vaddr += size;
298                nextpaddr = paddr + size;
299                buflen -= size;
300
301	} while (buflen > 0);
302
303	if (buflen != 0) {
304		printf("bus_dmamap_load: Too many segs! buf_len = 0x%lx\n",
305		    (u_long)buflen);
306		error = EFBIG;
307	}
308
309	(*callback)(callback_arg, dm_segments, seg, error);
310
311	return (0);
312}
313
314/*
315 * Utility function to load a linear buffer.  lastaddrp holds state
316 * between invocations (for multiple-buffer loads).  segp contains
317 * the starting segment on entrance, and the ending segment on exit.
318 * first indicates if this is the first invocation of this function.
319 */
320static int
321bus_dmamap_load_buffer(bus_dma_tag_t dmat, bus_dma_segment_t segs[],
322    void *buf, bus_size_t buflen, struct thread *td,
323    int flags, vm_offset_t *lastaddrp, int *segp,
324    int first)
325{
326	bus_size_t sgsize;
327	bus_addr_t curaddr, lastaddr, baddr, bmask;
328	vm_offset_t vaddr = (vm_offset_t)buf;
329	int seg;
330	pmap_t pmap;
331
332	if (td != NULL)
333		pmap = vmspace_pmap(td->td_proc->p_vmspace);
334	else
335		pmap = NULL;
336
337	lastaddr = *lastaddrp;
338	bmask = ~(dmat->boundary - 1);
339
340	for (seg = *segp; buflen > 0 ; ) {
341		/*
342		 * Get the physical address for this segment.
343		 */
344		if (pmap)
345			curaddr = pmap_extract(pmap, vaddr);
346		else
347			curaddr = pmap_kextract(vaddr);
348
349		/*
350		 * Compute the segment size, and adjust counts.
351		 */
352		sgsize = PAGE_SIZE - ((u_long)curaddr & PAGE_MASK);
353		if (buflen < sgsize)
354			sgsize = buflen;
355
356		/*
357		 * Make sure we don't cross any boundaries.
358		 */
359		if (dmat->boundary > 0) {
360			baddr = (curaddr + dmat->boundary) & bmask;
361			if (sgsize > (baddr - curaddr))
362				sgsize = (baddr - curaddr);
363		}
364
365		/*
366		 * Insert chunk into a segment, coalescing with
367		 * the previous segment if possible.
368		 */
369		if (first) {
370			segs[seg].ds_addr = curaddr;
371			segs[seg].ds_len = sgsize;
372			first = 0;
373		} else {
374			if (curaddr == lastaddr &&
375			    (segs[seg].ds_len + sgsize) <= dmat->maxsegsz &&
376			    (dmat->boundary == 0 ||
377			     (segs[seg].ds_addr & bmask) == (curaddr & bmask)))
378				segs[seg].ds_len += sgsize;
379			else {
380				if (++seg >= dmat->nsegments)
381					break;
382				segs[seg].ds_addr = curaddr;
383				segs[seg].ds_len = sgsize;
384			}
385		}
386
387		lastaddr = curaddr + sgsize;
388		vaddr += sgsize;
389		buflen -= sgsize;
390	}
391
392	*segp = seg;
393	*lastaddrp = lastaddr;
394
395	/*
396	 * Did we fit?
397	 */
398	return (buflen != 0 ? EFBIG : 0); /* XXX better return value here? */
399}
400
401/*
402 * Like bus_dmamap_load(), but for mbufs.
403 */
404int
405bus_dmamap_load_mbuf(bus_dma_tag_t dmat, bus_dmamap_t map, struct mbuf *m0,
406		     bus_dmamap_callback2_t *callback, void *callback_arg,
407		     int flags)
408{
409#ifdef __GNUC__
410	bus_dma_segment_t dm_segments[dmat->nsegments];
411#else
412	bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
413#endif
414	int nsegs = 0, error = 0;
415
416	KASSERT(m0->m_flags & M_PKTHDR,
417	    ("bus_dmamap_load_mbuf: no packet header"));
418
419	if (m0->m_pkthdr.len <= dmat->maxsize) {
420		int first = 1;
421		vm_offset_t lastaddr = 0;
422		struct mbuf *m;
423
424		for (m = m0; m != NULL && error == 0; m = m->m_next) {
425			error = bus_dmamap_load_buffer(dmat, dm_segments,
426			    m->m_data, m->m_len, NULL, flags,
427			    &lastaddr, &nsegs, first);
428			first = 0;
429		}
430	} else {
431		error = EINVAL;
432	}
433
434	if (error) {
435		/*
436		 * force "no valid mappings" on error in callback.
437		 */
438		(*callback)(callback_arg, dm_segments, 0, 0, error);
439	} else {
440		(*callback)(callback_arg, dm_segments, nsegs+1,
441		    m0->m_pkthdr.len, error);
442	}
443	return (error);
444}
445
446/*
447 * Like bus_dmamap_load(), but for uios.
448 */
449int
450bus_dmamap_load_uio(bus_dma_tag_t dmat, bus_dmamap_t map, struct uio *uio,
451    bus_dmamap_callback2_t *callback, void *callback_arg,
452    int flags)
453{
454	vm_offset_t lastaddr;
455#ifdef __GNUC__
456	bus_dma_segment_t dm_segments[dmat->nsegments];
457#else
458	bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
459#endif
460	int nsegs, i, error, first;
461	bus_size_t resid;
462	struct iovec *iov;
463	struct proc *td = NULL;
464
465	resid = uio->uio_resid;
466	iov = uio->uio_iov;
467
468	if (uio->uio_segflg == UIO_USERSPACE) {
469		td = uio->uio_td;
470		KASSERT(td != NULL,
471		    ("bus_dmamap_load_uio: USERSPACE but no proc"));
472	}
473
474	first = 1;
475	nsegs = error = 0;
476	for (i = 0; i < uio->uio_iovcnt && resid != 0 && !error; i++) {
477		/*
478		 * Now at the first iovec to load.  Load each iovec
479		 * until we have exhausted the residual count.
480		 */
481		bus_size_t minlen =
482		    resid < iov[i].iov_len ? resid : iov[i].iov_len;
483		caddr_t addr = (caddr_t) iov[i].iov_base;
484
485		error = bus_dmamap_load_buffer(dmat, dm_segments, addr,
486		    minlen, td, flags, &lastaddr, &nsegs, first);
487
488		first = 0;
489
490		resid -= minlen;
491	}
492
493	if (error) {
494		/*
495		 * force "no valid mappings" on error in callback.
496		 */
497		(*callback)(callback_arg, dm_segments, 0, 0, error);
498	} else {
499		(*callback)(callback_arg, dm_segments, nsegs+1,
500		    uio->uio_resid, error);
501	}
502
503	return (error);
504}
505
506/*
507 * Release the mapping held by map. A no-op on PowerPC.
508 */
509void
510bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map)
511{}
512
513void
514bus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, bus_dmasync_op_t op)
515{}
516