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