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 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD: releng/5.2/sys/powerpc/powerpc/busdma_machdep.c 118081 2003-07-27 13:52:10Z mux $");
32
33 /*
34 * MacPPC bus dma support routines
35 */
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/malloc.h>
40 #include <sys/bus.h>
41 #include <sys/interrupt.h>
42 #include <sys/lock.h>
43 #include <sys/proc.h>
44 #include <sys/mutex.h>
45 #include <sys/mbuf.h>
46 #include <sys/uio.h>
47
48 #include <vm/vm.h>
49 #include <vm/vm_page.h>
50 #include <vm/vm_map.h>
51
52 #include <machine/atomic.h>
53 #include <machine/bus.h>
54 #include <machine/cpufunc.h>
55
56 struct 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 bus_dma_lock_t *lockfunc;
71 void *lockfuncarg;
72 };
73
74 struct bus_dmamap {
75 bus_dma_tag_t dmat;
76 void *buf; /* unmapped buffer pointer */
77 bus_size_t buflen; /* unmapped buffer length */
78 bus_dmamap_callback_t *callback;
79 void *callback_arg;
80 };
81
82 /*
83 * Convenience function for manipulating driver locks from busdma (during
84 * busdma_swi, for example). Drivers that don't provide their own locks
85 * should specify &Giant to dmat->lockfuncarg. Drivers that use their own
86 * non-mutex locking scheme don't have to use this at all.
87 */
88 void
89 busdma_lock_mutex(void *arg, bus_dma_lock_op_t op)
90 {
91 struct mtx *dmtx;
92
93 dmtx = (struct mtx *)arg;
94 switch (op) {
95 case BUS_DMA_LOCK:
96 mtx_lock(dmtx);
97 break;
98 case BUS_DMA_UNLOCK:
99 mtx_unlock(dmtx);
100 break;
101 default:
102 panic("Unknown operation 0x%x for busdma_lock_mutex!", op);
103 }
104 }
105
106 /*
107 * dflt_lock should never get called. It gets put into the dma tag when
108 * lockfunc == NULL, which is only valid if the maps that are associated
109 * with the tag are meant to never be defered.
110 * XXX Should have a way to identify which driver is responsible here.
111 */
112 static void
113 dflt_lock(void *arg, bus_dma_lock_op_t op)
114 {
115 #ifdef INVARIANTS
116 panic("driver error: busdma dflt_lock called");
117 #else
118 printf("DRIVER_ERROR: busdma dflt_lock called\n");
119 #endif
120 }
121
122 /*
123 * Allocate a device specific dma_tag.
124 */
125 int
126 bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment,
127 bus_size_t boundary, bus_addr_t lowaddr,
128 bus_addr_t highaddr, bus_dma_filter_t *filter,
129 void *filterarg, bus_size_t maxsize, int nsegments,
130 bus_size_t maxsegsz, int flags, bus_dma_lock_t *lockfunc,
131 void *lockfuncarg, bus_dma_tag_t *dmat)
132 {
133 bus_dma_tag_t newtag;
134 int error = 0;
135
136 /* Return a NULL tag on failure */
137 *dmat = NULL;
138
139 newtag = (bus_dma_tag_t)malloc(sizeof(*newtag), M_DEVBUF, M_NOWAIT);
140 if (newtag == NULL)
141 return (ENOMEM);
142
143 newtag->parent = parent;
144 newtag->alignment = alignment;
145 newtag->boundary = boundary;
146 newtag->lowaddr = trunc_page((vm_offset_t)lowaddr) + (PAGE_SIZE - 1);
147 newtag->highaddr = trunc_page((vm_offset_t)highaddr) + (PAGE_SIZE - 1);
148 newtag->filter = filter;
149 newtag->filterarg = filterarg;
150 newtag->maxsize = maxsize;
151 newtag->nsegments = nsegments;
152 newtag->maxsegsz = maxsegsz;
153 newtag->flags = flags;
154 newtag->ref_count = 1; /* Count ourself */
155 newtag->map_count = 0;
156 if (lockfunc != NULL) {
157 newtag->lockfunc = lockfunc;
158 newtag->lockfuncarg = lockfuncarg;
159 } else {
160 newtag->lockfunc = dflt_lock;
161 newtag->lockfuncarg = NULL;
162 }
163
164 /*
165 * Take into account any restrictions imposed by our parent tag
166 */
167 if (parent != NULL) {
168 newtag->lowaddr = min(parent->lowaddr, newtag->lowaddr);
169 newtag->highaddr = max(parent->highaddr, newtag->highaddr);
170
171 /*
172 * XXX Not really correct??? Probably need to honor boundary
173 * all the way up the inheritence chain.
174 */
175 newtag->boundary = max(parent->boundary, newtag->boundary);
176 if (newtag->filter == NULL) {
177 /*
178 * Short circuit looking at our parent directly
179 * since we have encapsulated all of its information
180 */
181 newtag->filter = parent->filter;
182 newtag->filterarg = parent->filterarg;
183 newtag->parent = parent->parent;
184 }
185 if (newtag->parent != NULL)
186 atomic_add_int(&parent->ref_count, 1);
187 }
188
189 *dmat = newtag;
190 return (error);
191 }
192
193 int
194 bus_dma_tag_destroy(bus_dma_tag_t dmat)
195 {
196 if (dmat != NULL) {
197
198 if (dmat->map_count != 0)
199 return (EBUSY);
200
201 while (dmat != NULL) {
202 bus_dma_tag_t parent;
203
204 parent = dmat->parent;
205 atomic_subtract_int(&dmat->ref_count, 1);
206 if (dmat->ref_count == 0) {
207 free(dmat, M_DEVBUF);
208 /*
209 * Last reference count, so
210 * release our reference
211 * count on our parent.
212 */
213 dmat = parent;
214 } else
215 dmat = NULL;
216 }
217 }
218 return (0);
219 }
220
221 /*
222 * Allocate a handle for mapping from kva/uva/physical
223 * address space into bus device space.
224 */
225 int
226 bus_dmamap_create(bus_dma_tag_t dmat, int flags, bus_dmamap_t *mapp)
227 {
228 *mapp = NULL;
229 dmat->map_count++;
230
231 return (0);
232 }
233
234 /*
235 * Destroy a handle for mapping from kva/uva/physical
236 * address space into bus device space.
237 */
238 int
239 bus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map)
240 {
241 if (map != NULL) {
242 panic("dmamap_destroy: NULL?\n");
243 }
244 dmat->map_count--;
245 return (0);
246 }
247
248 /*
249 * Allocate a piece of memory that can be efficiently mapped into
250 * bus device space based on the constraints lited in the dma tag.
251 * A dmamap to for use with dmamap_load is also allocated.
252 */
253 int
254 bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags,
255 bus_dmamap_t *mapp)
256 {
257 int mflags;
258
259 if (flags & BUS_DMA_NOWAIT)
260 mflags = M_NOWAIT;
261 else
262 mflags = M_WAITOK;
263 if (flags & BUS_DMA_ZERO)
264 mflags |= M_ZERO;
265
266 *mapp = NULL;
267
268 if (dmat->maxsize <= PAGE_SIZE) {
269 *vaddr = malloc(dmat->maxsize, M_DEVBUF, mflags);
270 } else {
271 /*
272 * XXX Use Contigmalloc until it is merged into this facility
273 * and handles multi-seg allocations. Nobody is doing
274 * multi-seg allocations yet though.
275 */
276 *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF, mflags,
277 0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul,
278 dmat->boundary);
279 }
280
281 if (*vaddr == NULL)
282 return (ENOMEM);
283
284 return (0);
285 }
286
287 /*
288 * Free a piece of memory and it's allocated dmamap, that was allocated
289 * via bus_dmamem_alloc. Make the same choice for free/contigfree.
290 */
291 void
292 bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map)
293 {
294 if (map != NULL)
295 panic("bus_dmamem_free: Invalid map freed\n");
296 if (dmat->maxsize <= PAGE_SIZE)
297 free(vaddr, M_DEVBUF);
298 else {
299 mtx_lock(&Giant);
300 contigfree(vaddr, dmat->maxsize, M_DEVBUF);
301 mtx_unlock(&Giant);
302 }
303 }
304
305 /*
306 * Map the buffer buf into bus space using the dmamap map.
307 */
308 int
309 bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf,
310 bus_size_t buflen, bus_dmamap_callback_t *callback,
311 void *callback_arg, int flags)
312 {
313 vm_offset_t vaddr;
314 vm_offset_t paddr;
315 #ifdef __GNUC__
316 bus_dma_segment_t dm_segments[dmat->nsegments];
317 #else
318 bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
319 #endif
320 bus_dma_segment_t *sg;
321 int seg;
322 int error = 0;
323 vm_offset_t nextpaddr;
324
325 if (map != NULL)
326 panic("bus_dmamap_load: Invalid map\n");
327
328 vaddr = (vm_offset_t)buf;
329 sg = &dm_segments[0];
330 seg = 1;
331 sg->ds_len = 0;
332 nextpaddr = 0;
333
334 do {
335 bus_size_t size;
336
337 paddr = pmap_kextract(vaddr);
338 size = PAGE_SIZE - (paddr & PAGE_MASK);
339 if (size > buflen)
340 size = buflen;
341
342 if (sg->ds_len == 0) {
343 sg->ds_addr = paddr;
344 sg->ds_len = size;
345 } else if (paddr == nextpaddr) {
346 sg->ds_len += size;
347 } else {
348 /* Go to the next segment */
349 sg++;
350 seg++;
351 if (seg > dmat->nsegments)
352 break;
353 sg->ds_addr = paddr;
354 sg->ds_len = size;
355 }
356 vaddr += size;
357 nextpaddr = paddr + size;
358 buflen -= size;
359
360 } while (buflen > 0);
361
362 if (buflen != 0) {
363 printf("bus_dmamap_load: Too many segs! buf_len = 0x%lx\n",
364 (u_long)buflen);
365 error = EFBIG;
366 }
367
368 (*callback)(callback_arg, dm_segments, seg, error);
369
370 return (0);
371 }
372
373 /*
374 * Utility function to load a linear buffer. lastaddrp holds state
375 * between invocations (for multiple-buffer loads). segp contains
376 * the starting segment on entrance, and the ending segment on exit.
377 * first indicates if this is the first invocation of this function.
378 */
379 static int
380 bus_dmamap_load_buffer(bus_dma_tag_t dmat, bus_dma_segment_t segs[],
381 void *buf, bus_size_t buflen, struct thread *td,
382 int flags, vm_offset_t *lastaddrp, int *segp,
383 int first)
384 {
385 bus_size_t sgsize;
386 bus_addr_t curaddr, lastaddr, baddr, bmask;
387 vm_offset_t vaddr = (vm_offset_t)buf;
388 int seg;
389 pmap_t pmap;
390
391 if (td != NULL)
392 pmap = vmspace_pmap(td->td_proc->p_vmspace);
393 else
394 pmap = NULL;
395
396 lastaddr = *lastaddrp;
397 bmask = ~(dmat->boundary - 1);
398
399 for (seg = *segp; buflen > 0 ; ) {
400 /*
401 * Get the physical address for this segment.
402 */
403 if (pmap)
404 curaddr = pmap_extract(pmap, vaddr);
405 else
406 curaddr = pmap_kextract(vaddr);
407
408 /*
409 * Compute the segment size, and adjust counts.
410 */
411 sgsize = PAGE_SIZE - ((u_long)curaddr & PAGE_MASK);
412 if (buflen < sgsize)
413 sgsize = buflen;
414
415 /*
416 * Make sure we don't cross any boundaries.
417 */
418 if (dmat->boundary > 0) {
419 baddr = (curaddr + dmat->boundary) & bmask;
420 if (sgsize > (baddr - curaddr))
421 sgsize = (baddr - curaddr);
422 }
423
424 /*
425 * Insert chunk into a segment, coalescing with
426 * the previous segment if possible.
427 */
428 if (first) {
429 segs[seg].ds_addr = curaddr;
430 segs[seg].ds_len = sgsize;
431 first = 0;
432 } else {
433 if (curaddr == lastaddr &&
434 (segs[seg].ds_len + sgsize) <= dmat->maxsegsz &&
435 (dmat->boundary == 0 ||
436 (segs[seg].ds_addr & bmask) == (curaddr & bmask)))
437 segs[seg].ds_len += sgsize;
438 else {
439 if (++seg >= dmat->nsegments)
440 break;
441 segs[seg].ds_addr = curaddr;
442 segs[seg].ds_len = sgsize;
443 }
444 }
445
446 lastaddr = curaddr + sgsize;
447 vaddr += sgsize;
448 buflen -= sgsize;
449 }
450
451 *segp = seg;
452 *lastaddrp = lastaddr;
453
454 /*
455 * Did we fit?
456 */
457 return (buflen != 0 ? EFBIG : 0); /* XXX better return value here? */
458 }
459
460 /*
461 * Like bus_dmamap_load(), but for mbufs.
462 */
463 int
464 bus_dmamap_load_mbuf(bus_dma_tag_t dmat, bus_dmamap_t map, struct mbuf *m0,
465 bus_dmamap_callback2_t *callback, void *callback_arg,
466 int flags)
467 {
468 #ifdef __GNUC__
469 bus_dma_segment_t dm_segments[dmat->nsegments];
470 #else
471 bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
472 #endif
473 int nsegs = 0, error = 0;
474
475 M_ASSERTPKTHDR(m0);
476
477 if (m0->m_pkthdr.len <= dmat->maxsize) {
478 int first = 1;
479 vm_offset_t lastaddr = 0;
480 struct mbuf *m;
481
482 for (m = m0; m != NULL && error == 0; m = m->m_next) {
483 if (m->m_len > 0) {
484 error = bus_dmamap_load_buffer(dmat,
485 dm_segments, m->m_data, m->m_len, NULL,
486 flags, &lastaddr, &nsegs, first);
487 first = 0;
488 }
489 }
490 } else {
491 error = EINVAL;
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 m0->m_pkthdr.len, error);
502 }
503 return (error);
504 }
505
506 /*
507 * Like bus_dmamap_load(), but for uios.
508 */
509 int
510 bus_dmamap_load_uio(bus_dma_tag_t dmat, bus_dmamap_t map, struct uio *uio,
511 bus_dmamap_callback2_t *callback, void *callback_arg,
512 int flags)
513 {
514 vm_offset_t lastaddr;
515 #ifdef __GNUC__
516 bus_dma_segment_t dm_segments[dmat->nsegments];
517 #else
518 bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS];
519 #endif
520 int nsegs, i, error, first;
521 bus_size_t resid;
522 struct iovec *iov;
523 struct thread *td = NULL;
524
525 resid = uio->uio_resid;
526 iov = uio->uio_iov;
527
528 if (uio->uio_segflg == UIO_USERSPACE) {
529 td = uio->uio_td;
530 KASSERT(td != NULL,
531 ("bus_dmamap_load_uio: USERSPACE but no proc"));
532 }
533
534 first = 1;
535 nsegs = error = 0;
536 for (i = 0; i < uio->uio_iovcnt && resid != 0 && !error; i++) {
537 /*
538 * Now at the first iovec to load. Load each iovec
539 * until we have exhausted the residual count.
540 */
541 bus_size_t minlen =
542 resid < iov[i].iov_len ? resid : iov[i].iov_len;
543 caddr_t addr = (caddr_t) iov[i].iov_base;
544
545 if (minlen > 0) {
546 error = bus_dmamap_load_buffer(dmat, dm_segments, addr,
547 minlen, td, flags, &lastaddr, &nsegs, first);
548
549 first = 0;
550
551 resid -= minlen;
552 }
553 }
554
555 if (error) {
556 /*
557 * force "no valid mappings" on error in callback.
558 */
559 (*callback)(callback_arg, dm_segments, 0, 0, error);
560 } else {
561 (*callback)(callback_arg, dm_segments, nsegs+1,
562 uio->uio_resid, error);
563 }
564
565 return (error);
566 }
567
568 /*
569 * Release the mapping held by map. A no-op on PowerPC.
570 */
571 void
572 bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map)
573 {
574
575 return;
576 }
577
578 void
579 bus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, bus_dmasync_op_t op)
580 {
581
582 return;
583 }
Cache object: 1e028dac2f12d0de0a0008bc5fb3f4e9
|