busdma_machdep.c revision 109623
11834Swollman/* 21834Swollman * Copyright (c) 2002 Peter Grehan 31834Swollman * Copyright (c) 1997, 1998 Justin T. Gibbs. 41834Swollman * All rights reserved. 51834Swollman * 61834Swollman * Redistribution and use in source and binary forms, with or without 71834Swollman * modification, are permitted provided that the following conditions 81834Swollman * are met: 91834Swollman * 1. Redistributions of source code must retain the above copyright 101834Swollman * notice, this list of conditions, and the following disclaimer, 111834Swollman * without modification, immediately at the beginning of the file. 121834Swollman * 2. The name of the author may not be used to endorse or promote products 131834Swollman * derived from this software without specific prior written permission. 141834Swollman * 151834Swollman * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 161834Swollman * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 171834Swollman * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 181834Swollman * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 191834Swollman * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 201834Swollman * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 211834Swollman * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 221834Swollman * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 231834Swollman * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 241834Swollman * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 251834Swollman * SUCH DAMAGE. 261834Swollman * 271834Swollman * From i386/busdma_machdep.c,v 1.26 2002/04/19 22:58:09 alfred 281834Swollman */ 291834Swollman 301834Swollman#ifndef lint 311834Swollmanstatic const char rcsid[] = 321834Swollman "$FreeBSD: head/sys/powerpc/powerpc/busdma_machdep.c 109623 2003-01-21 08:56:16Z alfred $"; 331834Swollman#endif /* not lint */ 341834Swollman 351834Swollman/* 361834Swollman * MacPPC bus dma support routines 371834Swollman */ 381834Swollman 391834Swollman#include <sys/param.h> 401834Swollman#include <sys/systm.h> 411834Swollman#include <sys/malloc.h> 421834Swollman#include <sys/bus.h> 431834Swollman#include <sys/interrupt.h> 441834Swollman#include <sys/lock.h> 451834Swollman#include <sys/proc.h> 461834Swollman#include <sys/mutex.h> 471834Swollman#include <sys/mbuf.h> 481834Swollman#include <sys/uio.h> 491834Swollman 501834Swollman#include <vm/vm.h> 511834Swollman#include <vm/vm_page.h> 521834Swollman#include <vm/vm_map.h> 531834Swollman 541834Swollman#include <machine/bus.h> 551834Swollman 561834Swollmanstruct bus_dma_tag { 571834Swollman bus_dma_tag_t parent; 581834Swollman bus_size_t alignment; 591834Swollman bus_size_t boundary; 601834Swollman bus_addr_t lowaddr; 611834Swollman bus_addr_t highaddr; 621834Swollman bus_dma_filter_t *filter; 631834Swollman void *filterarg; 641834Swollman bus_size_t maxsize; 651834Swollman u_int nsegments; 661834Swollman bus_size_t maxsegsz; 671834Swollman int flags; 681834Swollman int ref_count; 691834Swollman int map_count; 701834Swollman}; 711834Swollman 721834Swollmanstruct bus_dmamap { 731834Swollman bus_dma_tag_t dmat; 741834Swollman void *buf; /* unmapped buffer pointer */ 751834Swollman bus_size_t buflen; /* unmapped buffer length */ 761834Swollman bus_dmamap_callback_t *callback; 771834Swollman void *callback_arg; 781834Swollman}; 791834Swollman 801834Swollman/* 811834Swollman * Allocate a device specific dma_tag. 821834Swollman */ 831834Swollmanint 841834Swollmanbus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment, 851834Swollman bus_size_t boundary, bus_addr_t lowaddr, 861834Swollman bus_addr_t highaddr, bus_dma_filter_t *filter, 871834Swollman void *filterarg, bus_size_t maxsize, int nsegments, 881834Swollman bus_size_t maxsegsz, int flags, bus_dma_tag_t *dmat) 891834Swollman{ 901834Swollman bus_dma_tag_t newtag; 911834Swollman int error = 0; 921834Swollman 931834Swollman /* Return a NULL tag on failure */ 941834Swollman *dmat = NULL; 951834Swollman 961834Swollman newtag = (bus_dma_tag_t)malloc(sizeof(*newtag), M_DEVBUF, M_NOWAIT); 971834Swollman if (newtag == NULL) 981834Swollman return (ENOMEM); 991834Swollman 1001834Swollman newtag->parent = parent; 1011834Swollman newtag->alignment = alignment; 1021834Swollman newtag->boundary = boundary; 1031834Swollman newtag->lowaddr = trunc_page((vm_offset_t)lowaddr) + (PAGE_SIZE - 1); 1041834Swollman newtag->highaddr = trunc_page((vm_offset_t)highaddr) + (PAGE_SIZE - 1); 105 newtag->filter = filter; 106 newtag->filterarg = filterarg; 107 newtag->maxsize = maxsize; 108 newtag->nsegments = nsegments; 109 newtag->maxsegsz = maxsegsz; 110 newtag->flags = flags; 111 newtag->ref_count = 1; /* Count ourself */ 112 newtag->map_count = 0; 113 114 /* 115 * Take into account any restrictions imposed by our parent tag 116 */ 117 if (parent != NULL) { 118 newtag->lowaddr = min(parent->lowaddr, newtag->lowaddr); 119 newtag->highaddr = max(parent->highaddr, newtag->highaddr); 120 121 /* 122 * XXX Not really correct??? Probably need to honor boundary 123 * all the way up the inheritence chain. 124 */ 125 newtag->boundary = max(parent->boundary, newtag->boundary); 126 if (newtag->filter == NULL) { 127 /* 128 * Short circuit looking at our parent directly 129 * since we have encapsulated all of its information 130 */ 131 newtag->filter = parent->filter; 132 newtag->filterarg = parent->filterarg; 133 newtag->parent = parent->parent; 134 } 135 if (newtag->parent != NULL) { 136 parent->ref_count++; 137 } 138 } 139 140 *dmat = newtag; 141 return (error); 142} 143 144int 145bus_dma_tag_destroy(bus_dma_tag_t dmat) 146{ 147 if (dmat != NULL) { 148 149 if (dmat->map_count != 0) 150 return (EBUSY); 151 152 while (dmat != NULL) { 153 bus_dma_tag_t parent; 154 155 parent = dmat->parent; 156 dmat->ref_count--; 157 if (dmat->ref_count == 0) { 158 free(dmat, M_DEVBUF); 159 /* 160 * Last reference count, so 161 * release our reference 162 * count on our parent. 163 */ 164 dmat = parent; 165 } else 166 dmat = NULL; 167 } 168 } 169 return (0); 170} 171 172/* 173 * Allocate a handle for mapping from kva/uva/physical 174 * address space into bus device space. 175 */ 176int 177bus_dmamap_create(bus_dma_tag_t dmat, int flags, bus_dmamap_t *mapp) 178{ 179 *mapp = NULL; 180 dmat->map_count++; 181 182 return (0); 183} 184 185/* 186 * Destroy a handle for mapping from kva/uva/physical 187 * address space into bus device space. 188 */ 189int 190bus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map) 191{ 192 if (map != NULL) { 193 panic("dmamap_destroy: NULL?\n"); 194 } 195 dmat->map_count--; 196 return (0); 197} 198 199/* 200 * Allocate a piece of memory that can be efficiently mapped into 201 * bus device space based on the constraints lited in the dma tag. 202 * A dmamap to for use with dmamap_load is also allocated. 203 */ 204int 205bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags, 206 bus_dmamap_t *mapp) 207{ 208 *mapp = NULL; 209 210 if (dmat->maxsize <= PAGE_SIZE) { 211 *vaddr = malloc(dmat->maxsize, M_DEVBUF, 212 (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : 0); 213 } else { 214 /* 215 * XXX Use Contigmalloc until it is merged into this facility 216 * and handles multi-seg allocations. Nobody is doing 217 * multi-seg allocations yet though. 218 */ 219 *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF, 220 (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : 0, 221 0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul, 222 dmat->boundary); 223 } 224 225 if (*vaddr == NULL) 226 return (ENOMEM); 227 228 return (0); 229} 230 231/* 232 * Free a piece of memory and it's allocated dmamap, that was allocated 233 * via bus_dmamem_alloc. Make the same choice for free/contigfree. 234 */ 235void 236bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map) 237{ 238 if (map != NULL) 239 panic("bus_dmamem_free: Invalid map freed\n"); 240 if (dmat->maxsize <= PAGE_SIZE) 241 free(vaddr, M_DEVBUF); 242 else 243 contigfree(vaddr, dmat->maxsize, M_DEVBUF); 244} 245 246/* 247 * Map the buffer buf into bus space using the dmamap map. 248 */ 249int 250bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf, 251 bus_size_t buflen, bus_dmamap_callback_t *callback, 252 void *callback_arg, int flags) 253{ 254 vm_offset_t vaddr; 255 vm_offset_t paddr; 256#ifdef __GNUC__ 257 bus_dma_segment_t dm_segments[dmat->nsegments]; 258#else 259 bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS]; 260#endif 261 bus_dma_segment_t *sg; 262 int seg; 263 int error = 0; 264 vm_offset_t nextpaddr; 265 266 if (map != NULL) 267 panic("bus_dmamap_load: Invalid map\n"); 268 269 vaddr = (vm_offset_t)buf; 270 sg = &dm_segments[0]; 271 seg = 1; 272 sg->ds_len = 0; 273 nextpaddr = 0; 274 275 do { 276 bus_size_t size; 277 278 paddr = pmap_kextract(vaddr); 279 size = PAGE_SIZE - (paddr & PAGE_MASK); 280 if (size > buflen) 281 size = buflen; 282 283 if (sg->ds_len == 0) { 284 sg->ds_addr = paddr; 285 sg->ds_len = size; 286 } else if (paddr == nextpaddr) { 287 sg->ds_len += size; 288 } else { 289 /* Go to the next segment */ 290 sg++; 291 seg++; 292 if (seg > dmat->nsegments) 293 break; 294 sg->ds_addr = paddr; 295 sg->ds_len = size; 296 } 297 vaddr += size; 298 nextpaddr = paddr + size; 299 buflen -= size; 300 301 } while (buflen > 0); 302 303 if (buflen != 0) { 304 printf("bus_dmamap_load: Too many segs! buf_len = 0x%lx\n", 305 (u_long)buflen); 306 error = EFBIG; 307 } 308 309 (*callback)(callback_arg, dm_segments, seg, error); 310 311 return (0); 312} 313 314/* 315 * Utility function to load a linear buffer. lastaddrp holds state 316 * between invocations (for multiple-buffer loads). segp contains 317 * the starting segment on entrance, and the ending segment on exit. 318 * first indicates if this is the first invocation of this function. 319 */ 320static int 321bus_dmamap_load_buffer(bus_dma_tag_t dmat, bus_dma_segment_t segs[], 322 void *buf, bus_size_t buflen, struct thread *td, 323 int flags, vm_offset_t *lastaddrp, int *segp, 324 int first) 325{ 326 bus_size_t sgsize; 327 bus_addr_t curaddr, lastaddr, baddr, bmask; 328 vm_offset_t vaddr = (vm_offset_t)buf; 329 int seg; 330 pmap_t pmap; 331 332 if (td != NULL) 333 pmap = vmspace_pmap(td->td_proc->p_vmspace); 334 else 335 pmap = NULL; 336 337 lastaddr = *lastaddrp; 338 bmask = ~(dmat->boundary - 1); 339 340 for (seg = *segp; buflen > 0 ; ) { 341 /* 342 * Get the physical address for this segment. 343 */ 344 if (pmap) 345 curaddr = pmap_extract(pmap, vaddr); 346 else 347 curaddr = pmap_kextract(vaddr); 348 349 /* 350 * Compute the segment size, and adjust counts. 351 */ 352 sgsize = PAGE_SIZE - ((u_long)curaddr & PAGE_MASK); 353 if (buflen < sgsize) 354 sgsize = buflen; 355 356 /* 357 * Make sure we don't cross any boundaries. 358 */ 359 if (dmat->boundary > 0) { 360 baddr = (curaddr + dmat->boundary) & bmask; 361 if (sgsize > (baddr - curaddr)) 362 sgsize = (baddr - curaddr); 363 } 364 365 /* 366 * Insert chunk into a segment, coalescing with 367 * the previous segment if possible. 368 */ 369 if (first) { 370 segs[seg].ds_addr = curaddr; 371 segs[seg].ds_len = sgsize; 372 first = 0; 373 } else { 374 if (curaddr == lastaddr && 375 (segs[seg].ds_len + sgsize) <= dmat->maxsegsz && 376 (dmat->boundary == 0 || 377 (segs[seg].ds_addr & bmask) == (curaddr & bmask))) 378 segs[seg].ds_len += sgsize; 379 else { 380 if (++seg >= dmat->nsegments) 381 break; 382 segs[seg].ds_addr = curaddr; 383 segs[seg].ds_len = sgsize; 384 } 385 } 386 387 lastaddr = curaddr + sgsize; 388 vaddr += sgsize; 389 buflen -= sgsize; 390 } 391 392 *segp = seg; 393 *lastaddrp = lastaddr; 394 395 /* 396 * Did we fit? 397 */ 398 return (buflen != 0 ? EFBIG : 0); /* XXX better return value here? */ 399} 400 401/* 402 * Like bus_dmamap_load(), but for mbufs. 403 */ 404int 405bus_dmamap_load_mbuf(bus_dma_tag_t dmat, bus_dmamap_t map, struct mbuf *m0, 406 bus_dmamap_callback2_t *callback, void *callback_arg, 407 int flags) 408{ 409#ifdef __GNUC__ 410 bus_dma_segment_t dm_segments[dmat->nsegments]; 411#else 412 bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS]; 413#endif 414 int nsegs = 0, error = 0; 415 416 KASSERT(m0->m_flags & M_PKTHDR, 417 ("bus_dmamap_load_mbuf: no packet header")); 418 419 if (m0->m_pkthdr.len <= dmat->maxsize) { 420 int first = 1; 421 vm_offset_t lastaddr = 0; 422 struct mbuf *m; 423 424 for (m = m0; m != NULL && error == 0; m = m->m_next) { 425 error = bus_dmamap_load_buffer(dmat, dm_segments, 426 m->m_data, m->m_len, NULL, flags, 427 &lastaddr, &nsegs, first); 428 first = 0; 429 } 430 } else { 431 error = EINVAL; 432 } 433 434 if (error) { 435 /* 436 * force "no valid mappings" on error in callback. 437 */ 438 (*callback)(callback_arg, dm_segments, 0, 0, error); 439 } else { 440 (*callback)(callback_arg, dm_segments, nsegs+1, 441 m0->m_pkthdr.len, error); 442 } 443 return (error); 444} 445 446/* 447 * Like bus_dmamap_load(), but for uios. 448 */ 449int 450bus_dmamap_load_uio(bus_dma_tag_t dmat, bus_dmamap_t map, struct uio *uio, 451 bus_dmamap_callback2_t *callback, void *callback_arg, 452 int flags) 453{ 454 vm_offset_t lastaddr; 455#ifdef __GNUC__ 456 bus_dma_segment_t dm_segments[dmat->nsegments]; 457#else 458 bus_dma_segment_t dm_segments[BUS_DMAMAP_NSEGS]; 459#endif 460 int nsegs, i, error, first; 461 bus_size_t resid; 462 struct iovec *iov; 463 struct proc *td = NULL; 464 465 resid = uio->uio_resid; 466 iov = uio->uio_iov; 467 468 if (uio->uio_segflg == UIO_USERSPACE) { 469 td = uio->uio_td; 470 KASSERT(td != NULL, 471 ("bus_dmamap_load_uio: USERSPACE but no proc")); 472 } 473 474 first = 1; 475 nsegs = error = 0; 476 for (i = 0; i < uio->uio_iovcnt && resid != 0 && !error; i++) { 477 /* 478 * Now at the first iovec to load. Load each iovec 479 * until we have exhausted the residual count. 480 */ 481 bus_size_t minlen = 482 resid < iov[i].iov_len ? resid : iov[i].iov_len; 483 caddr_t addr = (caddr_t) iov[i].iov_base; 484 485 error = bus_dmamap_load_buffer(dmat, dm_segments, addr, 486 minlen, td, flags, &lastaddr, &nsegs, first); 487 488 first = 0; 489 490 resid -= minlen; 491 } 492 493 if (error) { 494 /* 495 * force "no valid mappings" on error in callback. 496 */ 497 (*callback)(callback_arg, dm_segments, 0, 0, error); 498 } else { 499 (*callback)(callback_arg, dm_segments, nsegs+1, 500 uio->uio_resid, error); 501 } 502 503 return (error); 504} 505 506/* 507 * Release the mapping held by map. A no-op on PowerPC. 508 */ 509void 510bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map) 511{} 512 513void 514bus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, bus_dmasync_op_t op) 515{} 516