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