bus_dma.c revision 1.7
1/* $NetBSD: bus_dma.c,v 1.7 1998/01/09 06:37:04 thorpej Exp $ */ 2 3/*- 4 * Copyright (c) 1997 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 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the NetBSD 22 * Foundation, Inc. and its contributors. 23 * 4. Neither the name of The NetBSD Foundation nor the names of its 24 * contributors may be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40#include <sys/cdefs.h> /* RCS ID & Copyright macro defns */ 41 42__KERNEL_RCSID(0, "$NetBSD: bus_dma.c,v 1.7 1998/01/09 06:37:04 thorpej Exp $"); 43 44#include <sys/param.h> 45#include <sys/systm.h> 46#include <sys/kernel.h> 47#include <sys/device.h> 48#include <sys/malloc.h> 49#include <sys/proc.h> 50 51#include <vm/vm.h> 52#include <vm/vm_kern.h> 53 54#define _ALPHA_BUS_DMA_PRIVATE 55#include <machine/bus.h> 56#include <machine/intr.h> 57 58/* 59 * Common function for DMA map creation. May be called by bus-specific 60 * DMA map creation functions. 61 */ 62int 63_bus_dmamap_create(t, size, nsegments, maxsegsz, boundary, flags, dmamp) 64 bus_dma_tag_t t; 65 bus_size_t size; 66 int nsegments; 67 bus_size_t maxsegsz; 68 bus_size_t boundary; 69 int flags; 70 bus_dmamap_t *dmamp; 71{ 72 struct alpha_bus_dmamap *map; 73 void *mapstore; 74 size_t mapsize; 75 76 /* 77 * Allcoate and initialize the DMA map. The end of the map 78 * is a variable-sized array of segments, so we allocate enough 79 * room for them in one shot. 80 * 81 * Note we don't preserve the WAITOK or NOWAIT flags. Preservation 82 * of ALLOCNOW notifes others that we've reserved these resources, 83 * and they are not to be freed. 84 * 85 * The bus_dmamap_t includes one bus_dma_segment_t, hence 86 * the (nsegments - 1). 87 */ 88 mapsize = sizeof(struct alpha_bus_dmamap) + 89 (sizeof(bus_dma_segment_t) * (nsegments - 1)); 90 if ((mapstore = malloc(mapsize, M_DEVBUF, 91 (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK)) == NULL) 92 return (ENOMEM); 93 94 bzero(mapstore, mapsize); 95 map = (struct alpha_bus_dmamap *)mapstore; 96 map->_dm_size = size; 97 map->_dm_segcnt = nsegments; 98 map->_dm_maxsegsz = maxsegsz; 99 map->_dm_boundary = boundary; 100 map->_dm_flags = flags & ~(BUS_DMA_WAITOK|BUS_DMA_NOWAIT); 101 map->dm_nsegs = 0; /* no valid mappings */ 102 103 *dmamp = map; 104 return (0); 105} 106 107/* 108 * Common function for DMA map destruction. May be called by bus-specific 109 * DMA map destruction functions. 110 */ 111void 112_bus_dmamap_destroy(t, map) 113 bus_dma_tag_t t; 114 bus_dmamap_t map; 115{ 116 117 free(map, M_DEVBUF); 118} 119 120/* 121 * Common function for loading a direct-mapped DMA map with a linear 122 * buffer. Called by bus-specific DMA map load functions with the 123 * OR value appropriate for indicating "direct-mapped" for that 124 * chipset. 125 */ 126int 127_bus_dmamap_load_direct_common(t, map, buf, buflen, p, flags, wbase) 128 bus_dma_tag_t t; 129 bus_dmamap_t map; 130 void *buf; 131 bus_size_t buflen; 132 struct proc *p; 133 int flags; 134 bus_addr_t wbase; 135{ 136 bus_size_t sgsize; 137 vm_offset_t curaddr, lastaddr; 138 vm_offset_t vaddr = (vm_offset_t)buf; 139 int first, seg; 140 141 /* 142 * Make sure that on error condition we return "no valid mappings". 143 */ 144 map->dm_nsegs = 0; 145 146 if (buflen > map->_dm_size) 147 return (EINVAL); 148 149 /* 150 * XXX Need to implement "don't dma across this boundry". 151 */ 152 153 lastaddr = ~0; /* XXX gcc */ 154 for (first = 1, seg = 0; buflen > 0 && seg < map->_dm_segcnt; ) { 155 /* 156 * Get the physical address for this segment. 157 */ 158 if (p != NULL) 159 curaddr = pmap_extract(p->p_vmspace->vm_map.pmap, 160 vaddr); 161 else 162 curaddr = vtophys(vaddr); 163 164 curaddr |= wbase; 165 166 /* 167 * Compute the segment size, and adjust counts. 168 */ 169 sgsize = NBPG - ((u_long)vaddr & PGOFSET); 170 if (buflen < sgsize) 171 sgsize = buflen; 172 173 /* 174 * Insert chunk into a segment, coalescing with 175 * the previous segment if possible. 176 */ 177 if (first) { 178 map->dm_segs[seg].ds_addr = curaddr; 179 map->dm_segs[seg].ds_len = sgsize; 180 first = 0; 181 } else { 182 if (curaddr == lastaddr && 183 (map->dm_segs[seg].ds_len + sgsize) <= 184 map->_dm_maxsegsz) 185 map->dm_segs[seg].ds_len += sgsize; 186 else { 187 seg++; 188 map->dm_segs[seg].ds_addr = curaddr; 189 map->dm_segs[seg].ds_len = sgsize; 190 } 191 } 192 193 lastaddr = curaddr + sgsize; 194 vaddr += sgsize; 195 buflen -= sgsize; 196 } 197 198 /* 199 * Did we fit? 200 */ 201 if (buflen != 0) { 202 /* 203 * XXX Should fall back on SGMAPs. 204 */ 205 return (EFBIG); /* XXX better return value here? */ 206 } 207 208 map->dm_nsegs = seg + 1; 209 return (0); 210} 211 212/* 213 * Like _bus_dmamap_load_direct_common(), but for mbufs. 214 */ 215int 216_bus_dmamap_load_mbuf_direct_common(t, map, m, flags, wbase) 217 bus_dma_tag_t t; 218 bus_dmamap_t map; 219 struct mbuf *m; 220 int flags; 221 bus_addr_t wbase; 222{ 223 224 panic("_bus_dmamap_load_mbuf_direct_common: not implemented"); 225} 226 227/* 228 * Like _bus_dmamap_load_direct_common(), but for uios. 229 */ 230int 231_bus_dmamap_load_uio_direct_common(t, map, uio, flags, wbase) 232 bus_dma_tag_t t; 233 bus_dmamap_t map; 234 struct uio *uio; 235 int flags; 236 bus_addr_t wbase; 237{ 238 239 panic("_bus_dmamap_load_uio_direct_common: not implemented"); 240} 241 242/* 243 * Like _bus_dmamap_load_direct_common(), but for raw memory. 244 */ 245int 246_bus_dmamap_load_raw_direct_common(t, map, segs, nsegs, size, flags, wbase) 247 bus_dma_tag_t t; 248 bus_dmamap_t map; 249 bus_dma_segment_t *segs; 250 int nsegs; 251 bus_size_t size; 252 int flags; 253 bus_addr_t wbase; 254{ 255 256 panic("_bus_dmamap_load_raw_direct_common: not implemented"); 257} 258 259/* 260 * Common function for unloading a DMA map. May be called by 261 * chipset-specific DMA map unload functions. 262 */ 263void 264_bus_dmamap_unload(t, map) 265 bus_dma_tag_t t; 266 bus_dmamap_t map; 267{ 268 269 /* 270 * No resources to free; just mark the mappings as 271 * invalid. 272 */ 273 map->dm_nsegs = 0; 274} 275 276/* 277 * Common function for DMA map synchronization. May be called 278 * by chipset-specific DMA map synchronization functions. 279 */ 280void 281_bus_dmamap_sync(t, map, op) 282 bus_dma_tag_t t; 283 bus_dmamap_t map; 284 bus_dmasync_op_t op; 285{ 286 287 /* Nothing to do. */ 288} 289 290/* 291 * Common function for DMA-safe memory allocation. May be called 292 * by bus-specific DMA memory allocation functions. 293 */ 294int 295_bus_dmamem_alloc(t, size, alignment, boundary, segs, nsegs, rsegs, flags) 296 bus_dma_tag_t t; 297 bus_size_t size, alignment, boundary; 298 bus_dma_segment_t *segs; 299 int nsegs; 300 int *rsegs; 301 int flags; 302{ 303 extern vm_offset_t avail_start, avail_end; 304 vm_offset_t curaddr, lastaddr, high; 305 vm_page_t m; 306 struct pglist mlist; 307 int curseg, error; 308 309 /* Always round the size. */ 310 size = round_page(size); 311 312 high = avail_end - PAGE_SIZE; 313 314 /* 315 * Allocate pages from the VM system. 316 */ 317 TAILQ_INIT(&mlist); 318 error = vm_page_alloc_memory(size, avail_start, high, 319 alignment, boundary, &mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0); 320 if (error) 321 return (error); 322 323 /* 324 * Compute the location, size, and number of segments actually 325 * returned by the VM code. 326 */ 327 m = mlist.tqh_first; 328 curseg = 0; 329 lastaddr = segs[curseg].ds_addr = VM_PAGE_TO_PHYS(m); 330 segs[curseg].ds_len = PAGE_SIZE; 331 m = m->pageq.tqe_next; 332 333 for (; m != NULL; m = m->pageq.tqe_next) { 334 curaddr = VM_PAGE_TO_PHYS(m); 335#ifdef DIAGNOSTIC 336 if (curaddr < avail_start || curaddr >= high) { 337 printf("vm_page_alloc_memory returned non-sensical" 338 " address 0x%lx\n", curaddr); 339 panic("_bus_dmamem_alloc"); 340 } 341#endif 342 if (curaddr == (lastaddr + PAGE_SIZE)) 343 segs[curseg].ds_len += PAGE_SIZE; 344 else { 345 curseg++; 346 segs[curseg].ds_addr = curaddr; 347 segs[curseg].ds_len = PAGE_SIZE; 348 } 349 lastaddr = curaddr; 350 } 351 352 *rsegs = curseg + 1; 353 354 return (0); 355} 356 357/* 358 * Common function for freeing DMA-safe memory. May be called by 359 * bus-specific DMA memory free functions. 360 */ 361void 362_bus_dmamem_free(t, segs, nsegs) 363 bus_dma_tag_t t; 364 bus_dma_segment_t *segs; 365 int nsegs; 366{ 367 vm_page_t m; 368 bus_addr_t addr; 369 struct pglist mlist; 370 int curseg; 371 372 /* 373 * Build a list of pages to free back to the VM system. 374 */ 375 TAILQ_INIT(&mlist); 376 for (curseg = 0; curseg < nsegs; curseg++) { 377 for (addr = segs[curseg].ds_addr; 378 addr < (segs[curseg].ds_addr + segs[curseg].ds_len); 379 addr += PAGE_SIZE) { 380 m = PHYS_TO_VM_PAGE(addr); 381 TAILQ_INSERT_TAIL(&mlist, m, pageq); 382 } 383 } 384 385 vm_page_free_memory(&mlist); 386} 387 388/* 389 * Common function for mapping DMA-safe memory. May be called by 390 * bus-specific DMA memory map functions. 391 */ 392int 393_bus_dmamem_map(t, segs, nsegs, size, kvap, flags) 394 bus_dma_tag_t t; 395 bus_dma_segment_t *segs; 396 int nsegs; 397 size_t size; 398 caddr_t *kvap; 399 int flags; 400{ 401 vm_offset_t va; 402 bus_addr_t addr; 403 int curseg, s; 404 405 size = round_page(size); 406 407 s = splimp(); 408 va = kmem_alloc_pageable(kmem_map, size); 409 splx(s); 410 411 if (va == 0) 412 return (ENOMEM); 413 414 *kvap = (caddr_t)va; 415 416 for (curseg = 0; curseg < nsegs; curseg++) { 417 for (addr = segs[curseg].ds_addr; 418 addr < (segs[curseg].ds_addr + segs[curseg].ds_len); 419 addr += NBPG, va += NBPG, size -= NBPG) { 420 if (size == 0) 421 panic("_bus_dmamem_map: size botch"); 422 pmap_enter(pmap_kernel(), va, addr, 423 VM_PROT_READ | VM_PROT_WRITE, TRUE); 424#if 0 425 if (flags & BUS_DMAMEM_NOSYNC) 426 /* XXX make non-cacheable? */ ; 427#endif 428 } 429 } 430 431 return (0); 432} 433 434/* 435 * Common function for unmapping DMA-safe memory. May be called by 436 * bus-specific DMA memory unmapping functions. 437 */ 438void 439_bus_dmamem_unmap(t, kva, size) 440 bus_dma_tag_t t; 441 caddr_t kva; 442 size_t size; 443{ 444 int s; 445 446#ifdef DIAGNOSTIC 447 if ((u_long)kva & PGOFSET) 448 panic("_bus_dmamem_unmap"); 449#endif 450 451 size = round_page(size); 452 s = splimp(); 453 kmem_free(kmem_map, (vm_offset_t)kva, size); 454 splx(s); 455} 456 457/* 458 * Common functin for mmap(2)'ing DMA-safe memory. May be called by 459 * bus-specific DMA mmap(2)'ing functions. 460 */ 461int 462_bus_dmamem_mmap(t, segs, nsegs, off, prot, flags) 463 bus_dma_tag_t t; 464 bus_dma_segment_t *segs; 465 int nsegs, off, prot, flags; 466{ 467 int i; 468 469 for (i = 0; i < nsegs; i++) { 470#ifdef DIAGNOSTIC 471 if (off & PGOFSET) 472 panic("_bus_dmamem_mmap: offset unaligned"); 473 if (segs[i].ds_addr & PGOFSET) 474 panic("_bus_dmamem_mmap: segment unaligned"); 475 if (segs[i].ds_len & PGOFSET) 476 panic("_bus_dmamem_mmap: segment size not multiple" 477 " of page size"); 478#endif 479 if (off >= segs[i].ds_len) { 480 off -= segs[i].ds_len; 481 continue; 482 } 483 484 return (alpha_btop((caddr_t)segs[i].ds_addr + off)); 485 } 486 487 /* Page not found. */ 488 return (-1); 489} 490