isadma_bounce.c revision 1.14 1 /* $NetBSD: isadma_bounce.c,v 1.14 2021/12/17 06:28:20 skrll Exp $ */
2
3 /*-
4 * Copyright (c) 1996, 1997, 1998, 2000, 2001 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9 * NASA Ames Research Center.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: isadma_bounce.c,v 1.14 2021/12/17 06:28:20 skrll Exp $");
35
36 #define _MIPS_BUS_DMA_PRIVATE
37
38 #include <sys/param.h>
39 #include <sys/bus.h>
40 #include <sys/device.h>
41 #include <sys/malloc.h>
42 #include <sys/mbuf.h>
43 #include <sys/proc.h>
44 #include <sys/systm.h>
45
46 #include <mips/cache.h>
47 #include <mips/locore.h>
48
49 #include <dev/isa/isareg.h>
50 #include <dev/isa/isavar.h>
51
52 #include <uvm/uvm_extern.h>
53
54 int isadma_bounce_alloc_bouncebuf(bus_dma_tag_t, bus_dmamap_t,
55 bus_size_t, int);
56 void isadma_bounce_free_bouncebuf(bus_dma_tag_t, bus_dmamap_t);
57
58 /*
59 * Create an ISA DMA map.
60 */
61 int
62 isadma_bounce_dmamap_create(bus_dma_tag_t t, bus_size_t size, int nsegments,
63 bus_size_t maxsegsz, bus_size_t boundary, int flags, bus_dmamap_t *dmamp)
64 {
65 struct mips_bus_dma_cookie *cookie;
66 bus_dmamap_t map;
67 int error, cookieflags;
68 void *cookiestore;
69 size_t cookiesize;
70
71 /* Call common function to create the basic map. */
72 error = _bus_dmamap_create(t, size, nsegments, maxsegsz, boundary,
73 flags, dmamp);
74 if (error)
75 return (error);
76
77 map = *dmamp;
78 map->_dm_cookie = NULL;
79
80 cookiesize = sizeof(*cookie);
81
82 /*
83 * ISA only has 24-bits of address space. This means
84 * we can't DMA to pages over 16M. In order to DMA to
85 * arbitrary buffers, we use "bounce buffers" - pages
86 * in memory below the 16M boundary. On DMA reads,
87 * DMA happens to the bounce buffers, and is copied into
88 * the caller's buffer. On writes, data is copied into
89 * the bounce buffer, and the DMA happens from those
90 * pages. To software using the DMA mapping interface,
91 * this looks simply like a data cache.
92 *
93 * If we have more than 16M of RAM in the system, we may
94 * need bounce buffers. We check and remember that here.
95 *
96 * ...or, there is an opposite case. The most segments
97 * a transfer will require is (maxxfer / PAGE_SIZE) + 1. If
98 * the caller can't handle that many segments (e.g. the
99 * ISA DMA controller), we may have to bounce it as well.
100 */
101 cookieflags = 0;
102 if (_BUS_AVAIL_END > (t->_wbase + t->_bounce_alloc_hi - t->_bounce_alloc_lo)
103 || ((map->_dm_size / PAGE_SIZE) + 1) > map->_dm_segcnt) {
104 cookieflags |= _BUS_DMA_MIGHT_NEED_BOUNCE;
105 cookiesize += (sizeof(bus_dma_segment_t) *
106 (map->_dm_segcnt - 1));
107 }
108
109 /*
110 * Allocate our cookie.
111 */
112 if ((cookiestore = malloc(cookiesize, M_DMAMAP,
113 (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK)) == NULL) {
114 error = ENOMEM;
115 goto out;
116 }
117 memset(cookiestore, 0, cookiesize);
118 cookie = (struct mips_bus_dma_cookie *)cookiestore;
119 cookie->id_flags = cookieflags;
120 map->_dm_cookie = cookie;
121
122 if (cookieflags & _BUS_DMA_MIGHT_NEED_BOUNCE) {
123 /*
124 * Allocate the bounce pages now if the caller
125 * wishes us to do so.
126 */
127 if ((flags & BUS_DMA_ALLOCNOW) == 0)
128 goto out;
129
130 error = isadma_bounce_alloc_bouncebuf(t, map, size, flags);
131 }
132
133 out:
134 if (error) {
135 if (map->_dm_cookie != NULL)
136 free(map->_dm_cookie, M_DMAMAP);
137 _bus_dmamap_destroy(t, map);
138 }
139 return (error);
140 }
141
142 /*
143 * Destroy an ISA DMA map.
144 */
145 void
146 isadma_bounce_dmamap_destroy(bus_dma_tag_t t, bus_dmamap_t map)
147 {
148 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
149
150 /*
151 * Free any bounce pages this map might hold.
152 */
153 if (cookie->id_flags & _BUS_DMA_HAS_BOUNCE)
154 isadma_bounce_free_bouncebuf(t, map);
155
156 free(cookie, M_DMAMAP);
157 _bus_dmamap_destroy(t, map);
158 }
159
160 /*
161 * Load an ISA DMA map with a linear buffer.
162 */
163 int
164 isadma_bounce_dmamap_load(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
165 bus_size_t buflen, struct proc *p, int flags)
166 {
167 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
168 int error;
169
170 /*
171 * Make sure that on error condition we return "no valid mappings."
172 */
173 map->dm_mapsize = 0;
174 map->dm_nsegs = 0;
175
176 /*
177 * Try to load the map the normal way. If this errors out,
178 * and we can bounce, we will.
179 */
180 error = _bus_dmamap_load(t, map, buf, buflen, p, flags);
181 if (error == 0 || (cookie->id_flags & _BUS_DMA_MIGHT_NEED_BOUNCE) == 0)
182 return (error);
183
184 /*
185 * First attempt failed; bounce it.
186 */
187
188 /*
189 * Allocate bounce pages, if necessary.
190 */
191 if ((cookie->id_flags & _BUS_DMA_HAS_BOUNCE) == 0) {
192 error = isadma_bounce_alloc_bouncebuf(t, map, buflen, flags);
193 if (error)
194 return (error);
195 }
196
197 /*
198 * Cache a pointer to the caller's buffer and load the DMA map
199 * with the bounce buffer.
200 */
201 cookie->id_origbuf = buf;
202 cookie->id_origbuflen = buflen;
203 cookie->id_buftype = _BUS_DMA_BUFTYPE_LINEAR;
204 error = _bus_dmamap_load(t, map, cookie->id_bouncebuf, buflen,
205 p, flags);
206 if (error) {
207 /*
208 * Free the bounce pages, unless our resources
209 * are reserved for our exclusive use.
210 */
211 if ((map->_dm_flags & BUS_DMA_ALLOCNOW) == 0)
212 isadma_bounce_free_bouncebuf(t, map);
213 return (error);
214 }
215
216 /* ...so isadma_bounce_dmamap_sync() knows we're bouncing */
217 cookie->id_flags |= _BUS_DMA_IS_BOUNCING;
218 return (0);
219 }
220
221 /*
222 * Like isadma_bounce_dmamap_load(), but for mbufs.
223 */
224 int
225 isadma_bounce_dmamap_load_mbuf(bus_dma_tag_t t, bus_dmamap_t map,
226 struct mbuf *m0, int flags)
227 {
228 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
229 int error;
230
231 /*
232 * Make sure on error condition we return "no valid mappings."
233 */
234 map->dm_mapsize = 0;
235 map->dm_nsegs = 0;
236
237 #ifdef DIAGNOSTIC
238 if ((m0->m_flags & M_PKTHDR) == 0)
239 panic("isadma_bounce_dmamap_load_mbuf: no packet header");
240 #endif
241
242 if (m0->m_pkthdr.len > map->_dm_size)
243 return (EINVAL);
244
245 /*
246 * Try to load the map the normal way. If this errors out,
247 * and we can bounce, we will.
248 */
249 error = _bus_dmamap_load_mbuf(t, map, m0, flags);
250 if (error == 0 || (cookie->id_flags & _BUS_DMA_MIGHT_NEED_BOUNCE) == 0)
251 return (error);
252
253 /*
254 * First attempt failed; bounce it.
255 */
256
257 /*
258 * Allocate bounce pages, if necessary.
259 */
260 if ((cookie->id_flags & _BUS_DMA_HAS_BOUNCE) == 0) {
261 error = isadma_bounce_alloc_bouncebuf(t, map, m0->m_pkthdr.len,
262 flags);
263 if (error)
264 return (error);
265 }
266
267 /*
268 * Cache a pointer to the caller's buffer and load the DMA map
269 * with the bounce buffer.
270 */
271 cookie->id_origbuf = m0;
272 cookie->id_origbuflen = m0->m_pkthdr.len; /* not really used */
273 cookie->id_buftype = _BUS_DMA_BUFTYPE_MBUF;
274 error = _bus_dmamap_load(t, map, cookie->id_bouncebuf,
275 m0->m_pkthdr.len, NULL, flags);
276 if (error) {
277 /*
278 * Free the bounce pages, unless our resources
279 * are reserved for our exclusive use.
280 */
281 if ((map->_dm_flags & BUS_DMA_ALLOCNOW) == 0)
282 isadma_bounce_free_bouncebuf(t, map);
283 return (error);
284 }
285
286 /* ...so isadma_bounce_dmamap_sync() knows we're bouncing */
287 cookie->id_flags |= _BUS_DMA_IS_BOUNCING;
288 return (0);
289 }
290
291 /*
292 * Like isadma_bounce_dmamap_load(), but for uios.
293 */
294 int
295 isadma_bounce_dmamap_load_uio(bus_dma_tag_t t, bus_dmamap_t map,
296 struct uio *uio, int flags)
297 {
298
299 panic("isadma_bounce_dmamap_load_uio: not implemented");
300 }
301
302 /*
303 * Like isadma_bounce_dmamap_load(), but for raw memory allocated with
304 * bus_dmamem_alloc().
305 */
306 int
307 isadma_bounce_dmamap_load_raw(bus_dma_tag_t t, bus_dmamap_t map,
308 bus_dma_segment_t *segs, int nsegs, bus_size_t size, int flags)
309 {
310
311 panic("isadma_bounce_dmamap_load_raw: not implemented");
312 }
313
314 /*
315 * Unload an ISA DMA map.
316 */
317 void
318 isadma_bounce_dmamap_unload(bus_dma_tag_t t, bus_dmamap_t map)
319 {
320 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
321
322 /*
323 * If we have bounce pages, free them, unless they're
324 * reserved for our exclusive use.
325 */
326 if ((cookie->id_flags & _BUS_DMA_HAS_BOUNCE) &&
327 (map->_dm_flags & BUS_DMA_ALLOCNOW) == 0)
328 isadma_bounce_free_bouncebuf(t, map);
329
330 cookie->id_flags &= ~_BUS_DMA_IS_BOUNCING;
331 cookie->id_buftype = _BUS_DMA_BUFTYPE_INVALID;
332
333 /*
334 * Do the generic bits of the unload.
335 */
336 _bus_dmamap_unload(t, map);
337 }
338
339 /*
340 * Synchronize an ISA DMA map.
341 */
342 void
343 isadma_bounce_dmamap_sync(bus_dma_tag_t t, bus_dmamap_t map, bus_addr_t offset,
344 bus_size_t len, int ops)
345 {
346 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
347
348 /*
349 * Mixing PRE and POST operations is not allowed.
350 */
351 if ((ops & (BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE)) != 0 &&
352 (ops & (BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE)) != 0)
353 panic("isadma_bounce_dmamap_sync: mix PRE and POST");
354
355 #ifdef DIAGNOSTIC
356 if ((ops & (BUS_DMASYNC_PREWRITE|BUS_DMASYNC_POSTREAD)) != 0) {
357 if (offset >= map->dm_mapsize)
358 panic("isadma_bounce_dmamap_sync: bad offset");
359 if (len == 0 || (offset + len) > map->dm_mapsize)
360 panic("isadma_bounce_dmamap_sync: bad length");
361 }
362 #endif
363
364 /*
365 * If we're not bouncing, just do the normal sync operation
366 * and return.
367 */
368 if ((cookie->id_flags & _BUS_DMA_IS_BOUNCING) == 0) {
369 _bus_dmamap_sync(t, map, offset, len, ops);
370 return;
371 }
372
373 /*
374 * Flush data cache for PREREAD. This has the side-effect
375 * of invalidating the cache. Done at PREREAD since it
376 * causes the cache line(s) to be written back to memory.
377 *
378 * Copy the original buffer to the bounce buffer and flush
379 * the data cache for PREWRITE, so that the contents
380 * of the data buffer in memory reflect reality.
381 *
382 * Copy the bounce buffer to the original buffer in POSTREAD.
383 */
384
385 switch (cookie->id_buftype) {
386 case _BUS_DMA_BUFTYPE_LINEAR:
387 /*
388 * Nothing to do for pre-read.
389 */
390
391 if (ops & BUS_DMASYNC_PREWRITE) {
392 /*
393 * Copy the caller's buffer to the bounce buffer.
394 */
395 memcpy((char *)cookie->id_bouncebuf + offset,
396 (char *)cookie->id_origbuf + offset, len);
397 wbflush();
398 }
399
400 if (ops & BUS_DMASYNC_POSTREAD) {
401 /*
402 * Copy the bounce buffer to the caller's buffer.
403 */
404 memcpy((char *)cookie->id_origbuf + offset,
405 (char *)cookie->id_bouncebuf + offset, len);
406 }
407
408 /*
409 * Nothing to do for post-write.
410 */
411 break;
412
413 case _BUS_DMA_BUFTYPE_MBUF:
414 {
415 struct mbuf *m, *m0 = cookie->id_origbuf;
416 bus_size_t minlen, moff;
417
418 /*
419 * Nothing to do for pre-read.
420 */
421
422 if (ops & BUS_DMASYNC_PREWRITE) {
423 /*
424 * Copy the caller's buffer to the bounce buffer.
425 */
426 m_copydata(m0, offset, len,
427 (char *)cookie->id_bouncebuf + offset);
428 }
429
430 if (ops & BUS_DMASYNC_POSTREAD) {
431 /*
432 * Copy the bounce buffer to the caller's buffer.
433 */
434 for (moff = offset, m = m0; m != NULL && len != 0;
435 m = m->m_next) {
436 /* Find the beginning mbuf. */
437 if (moff >= m->m_len) {
438 moff -= m->m_len;
439 continue;
440 }
441
442 /*
443 * Now at the first mbuf to sync; nail
444 * each one until we have exhausted the
445 * length.
446 */
447 minlen = len < m->m_len - moff ?
448 len : m->m_len - moff;
449
450 memcpy(mtod(m, char *) + moff,
451 (char *)cookie->id_bouncebuf + offset,
452 minlen);
453
454 moff = 0;
455 len -= minlen;
456 offset += minlen;
457 }
458 }
459
460 /*
461 * Nothing to do for post-write.
462 */
463 break;
464 }
465
466 case _BUS_DMA_BUFTYPE_UIO:
467 panic("isadma_bounce_dmamap_sync: _BUS_DMA_BUFTYPE_UIO");
468 break;
469
470 case _BUS_DMA_BUFTYPE_RAW:
471 panic("isadma_bounce_dmamap_sync: _BUS_DMA_BUFTYPE_RAW");
472 break;
473
474 case _BUS_DMA_BUFTYPE_INVALID:
475 panic("isadma_bounce_dmamap_sync: _BUS_DMA_BUFTYPE_INVALID");
476 break;
477
478 default:
479 printf("unknown buffer type %d\n", cookie->id_buftype);
480 panic("isadma_bounce_dmamap_sync");
481 }
482
483 /* Drain the write buffer. */
484 wbflush();
485
486 /* XXXJRT */
487 if (ops & (BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE))
488 mips_dcache_wbinv_range((vaddr_t)cookie->id_bouncebuf + offset,
489 len);
490 }
491
492 /*
493 * Allocate memory safe for ISA DMA.
494 */
495 int
496 isadma_bounce_dmamem_alloc(bus_dma_tag_t t, bus_size_t size,
497 bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
498 int nsegs, int *rsegs, int flags)
499 {
500 paddr_t high;
501
502 if (_BUS_AVAIL_END > ISA_DMA_BOUNCE_THRESHOLD)
503 high = trunc_page(ISA_DMA_BOUNCE_THRESHOLD);
504 else
505 high = trunc_page(_BUS_AVAIL_END);
506
507 return (_bus_dmamem_alloc_range(t, size, alignment, boundary,
508 segs, nsegs, rsegs, flags, 0, high));
509 }
510
511 /**********************************************************************
512 * ISA DMA utility functions
513 **********************************************************************/
514
515 int
516 isadma_bounce_alloc_bouncebuf(bus_dma_tag_t t, bus_dmamap_t map,
517 bus_size_t size, int flags)
518 {
519 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
520 int error = 0;
521
522 cookie->id_bouncebuflen = round_page(size);
523 error = isadma_bounce_dmamem_alloc(t, cookie->id_bouncebuflen,
524 PAGE_SIZE, map->_dm_boundary, cookie->id_bouncesegs,
525 map->_dm_segcnt, &cookie->id_nbouncesegs, flags);
526 if (error)
527 goto out;
528 error = _bus_dmamem_map(t, cookie->id_bouncesegs,
529 cookie->id_nbouncesegs, cookie->id_bouncebuflen,
530 (void **)&cookie->id_bouncebuf, flags);
531
532 out:
533 if (error) {
534 _bus_dmamem_free(t, cookie->id_bouncesegs,
535 cookie->id_nbouncesegs);
536 cookie->id_bouncebuflen = 0;
537 cookie->id_nbouncesegs = 0;
538 } else
539 cookie->id_flags |= _BUS_DMA_HAS_BOUNCE;
540
541 return (error);
542 }
543
544 void
545 isadma_bounce_free_bouncebuf(bus_dma_tag_t t, bus_dmamap_t map)
546 {
547 struct mips_bus_dma_cookie *cookie = map->_dm_cookie;
548
549 _bus_dmamem_unmap(t, cookie->id_bouncebuf,
550 cookie->id_bouncebuflen);
551 _bus_dmamem_free(t, cookie->id_bouncesegs,
552 cookie->id_nbouncesegs);
553 cookie->id_bouncebuflen = 0;
554 cookie->id_nbouncesegs = 0;
555 cookie->id_flags &= ~_BUS_DMA_HAS_BOUNCE;
556 }
557