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