1 /*- 2 * Copyright (c) 2015 Marcel Moolenaar 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <sys/param.h> 30 #include <sys/systm.h> 31 #include <machine/bus.h> 32 #include <machine/bus_dma.h> 33 #include <machine/resource.h> 34 #include <sys/bus.h> 35 #include <sys/conf.h> 36 #include <sys/kernel.h> 37 #include <sys/malloc.h> 38 #include <sys/module.h> 39 #include <sys/proc.h> 40 #include <sys/queue.h> 41 #include <sys/rman.h> 42 #include <sys/sbuf.h> 43 #include <sys/uio.h> 44 #include <vm/vm.h> 45 #include <vm/pmap.h> 46 #include <vm/vm_map.h> 47 48 #include <dev/proto/proto.h> 49 #include <dev/proto/proto_dev.h> 50 #include <dev/proto/proto_busdma.h> 51 52 MALLOC_DEFINE(M_PROTO_BUSDMA, "proto_busdma", "DMA management data"); 53 54 struct proto_callback_bundle { 55 struct proto_busdma *busdma; 56 struct proto_md *md; 57 struct proto_ioc_busdma *ioc; 58 }; 59 60 static int 61 proto_busdma_tag_create(struct proto_busdma *busdma, struct proto_tag *parent, 62 struct proto_ioc_busdma *ioc) 63 { 64 struct proto_tag *tag; 65 66 /* 67 * If nsegs is 1, ignore maxsegsz. What this means is that if we have 68 * just 1 segment, then maxsz should be equal to maxsegsz. To keep it 69 * simple for us, limit maxsegsz to maxsz in any case. 70 */ 71 if (ioc->u.tag.maxsegsz > ioc->u.tag.maxsz || ioc->u.tag.nsegs == 1) 72 ioc->u.tag.maxsegsz = ioc->u.tag.maxsz; 73 74 /* A bndry of 0 really means ~0, or no boundary. */ 75 if (ioc->u.tag.bndry == 0) 76 ioc->u.tag.bndry = ~0U; 77 78 tag = malloc(sizeof(*tag), M_PROTO_BUSDMA, M_WAITOK | M_ZERO); 79 if (parent != NULL) { 80 tag->parent = parent; 81 LIST_INSERT_HEAD(&parent->children, tag, peers); 82 tag->align = MAX(ioc->u.tag.align, parent->align); 83 tag->bndry = MIN(ioc->u.tag.bndry, parent->bndry); 84 tag->maxaddr = MIN(ioc->u.tag.maxaddr, parent->maxaddr); 85 tag->maxsz = MIN(ioc->u.tag.maxsz, parent->maxsz); 86 tag->maxsegsz = MIN(ioc->u.tag.maxsegsz, parent->maxsegsz); 87 tag->nsegs = MIN(ioc->u.tag.nsegs, parent->nsegs); 88 tag->datarate = MIN(ioc->u.tag.datarate, parent->datarate); 89 /* Write constraints back */ 90 ioc->u.tag.align = tag->align; 91 ioc->u.tag.bndry = tag->bndry; 92 ioc->u.tag.maxaddr = tag->maxaddr; 93 ioc->u.tag.maxsz = tag->maxsz; 94 ioc->u.tag.maxsegsz = tag->maxsegsz; 95 ioc->u.tag.nsegs = tag->nsegs; 96 ioc->u.tag.datarate = tag->datarate; 97 } else { 98 tag->align = ioc->u.tag.align; 99 tag->bndry = ioc->u.tag.bndry; 100 tag->maxaddr = ioc->u.tag.maxaddr; 101 tag->maxsz = ioc->u.tag.maxsz; 102 tag->maxsegsz = ioc->u.tag.maxsegsz; 103 tag->nsegs = ioc->u.tag.nsegs; 104 tag->datarate = ioc->u.tag.datarate; 105 } 106 LIST_INSERT_HEAD(&busdma->tags, tag, tags); 107 ioc->result = (uintptr_t)(void *)tag; 108 return (0); 109 } 110 111 static int 112 proto_busdma_tag_destroy(struct proto_busdma *busdma, struct proto_tag *tag) 113 { 114 115 if (!LIST_EMPTY(&tag->mds)) 116 return (EBUSY); 117 if (!LIST_EMPTY(&tag->children)) 118 return (EBUSY); 119 120 if (tag->parent != NULL) { 121 LIST_REMOVE(tag, peers); 122 tag->parent = NULL; 123 } 124 LIST_REMOVE(tag, tags); 125 free(tag, M_PROTO_BUSDMA); 126 return (0); 127 } 128 129 static struct proto_tag * 130 proto_busdma_tag_lookup(struct proto_busdma *busdma, u_long key) 131 { 132 struct proto_tag *tag; 133 134 LIST_FOREACH(tag, &busdma->tags, tags) { 135 if ((void *)tag == (void *)key) 136 return (tag); 137 } 138 return (NULL); 139 } 140 141 static int 142 proto_busdma_md_destroy_internal(struct proto_busdma *busdma, 143 struct proto_md *md) 144 { 145 146 LIST_REMOVE(md, mds); 147 LIST_REMOVE(md, peers); 148 if (md->physaddr) 149 bus_dmamap_unload(md->bd_tag, md->bd_map); 150 if (md->virtaddr != NULL) 151 bus_dmamem_free(md->bd_tag, md->virtaddr, md->bd_map); 152 else 153 bus_dmamap_destroy(md->bd_tag, md->bd_map); 154 bus_dma_tag_destroy(md->bd_tag); 155 free(md, M_PROTO_BUSDMA); 156 return (0); 157 } 158 159 static void 160 proto_busdma_mem_alloc_callback(void *arg, bus_dma_segment_t *segs, int nseg, 161 int error) 162 { 163 struct proto_callback_bundle *pcb = arg; 164 165 pcb->ioc->u.md.bus_nsegs = nseg; 166 pcb->ioc->u.md.bus_addr = segs[0].ds_addr; 167 } 168 169 static int 170 proto_busdma_mem_alloc(struct proto_busdma *busdma, struct proto_tag *tag, 171 struct proto_ioc_busdma *ioc) 172 { 173 struct proto_callback_bundle pcb; 174 struct proto_md *md; 175 int error; 176 177 md = malloc(sizeof(*md), M_PROTO_BUSDMA, M_WAITOK | M_ZERO); 178 md->tag = tag; 179 180 error = bus_dma_tag_create(busdma->bd_roottag, tag->align, tag->bndry, 181 tag->maxaddr, BUS_SPACE_MAXADDR, NULL, NULL, tag->maxsz, 182 tag->nsegs, tag->maxsegsz, 0, NULL, NULL, &md->bd_tag); 183 if (error) { 184 free(md, M_PROTO_BUSDMA); 185 return (error); 186 } 187 error = bus_dmamem_alloc(md->bd_tag, &md->virtaddr, 0, &md->bd_map); 188 if (error) { 189 bus_dma_tag_destroy(md->bd_tag); 190 free(md, M_PROTO_BUSDMA); 191 return (error); 192 } 193 md->physaddr = pmap_kextract((uintptr_t)(md->virtaddr)); 194 pcb.busdma = busdma; 195 pcb.md = md; 196 pcb.ioc = ioc; 197 error = bus_dmamap_load(md->bd_tag, md->bd_map, md->virtaddr, 198 tag->maxsz, proto_busdma_mem_alloc_callback, &pcb, BUS_DMA_NOWAIT); 199 if (error) { 200 bus_dmamem_free(md->bd_tag, md->virtaddr, md->bd_map); 201 bus_dma_tag_destroy(md->bd_tag); 202 free(md, M_PROTO_BUSDMA); 203 return (error); 204 } 205 LIST_INSERT_HEAD(&tag->mds, md, peers); 206 LIST_INSERT_HEAD(&busdma->mds, md, mds); 207 ioc->u.md.virt_addr = (uintptr_t)md->virtaddr; 208 ioc->u.md.virt_size = tag->maxsz; 209 ioc->u.md.phys_nsegs = 1; 210 ioc->u.md.phys_addr = md->physaddr; 211 ioc->result = (uintptr_t)(void *)md; 212 return (0); 213 } 214 215 static int 216 proto_busdma_mem_free(struct proto_busdma *busdma, struct proto_md *md) 217 { 218 219 if (md->virtaddr == NULL) 220 return (ENXIO); 221 return (proto_busdma_md_destroy_internal(busdma, md)); 222 } 223 224 static int 225 proto_busdma_md_create(struct proto_busdma *busdma, struct proto_tag *tag, 226 struct proto_ioc_busdma *ioc) 227 { 228 struct proto_md *md; 229 int error; 230 231 md = malloc(sizeof(*md), M_PROTO_BUSDMA, M_WAITOK | M_ZERO); 232 md->tag = tag; 233 234 error = bus_dma_tag_create(busdma->bd_roottag, tag->align, tag->bndry, 235 tag->maxaddr, BUS_SPACE_MAXADDR, NULL, NULL, tag->maxsz, 236 tag->nsegs, tag->maxsegsz, 0, NULL, NULL, &md->bd_tag); 237 if (error) { 238 free(md, M_PROTO_BUSDMA); 239 return (error); 240 } 241 error = bus_dmamap_create(md->bd_tag, 0, &md->bd_map); 242 if (error) { 243 bus_dma_tag_destroy(md->bd_tag); 244 free(md, M_PROTO_BUSDMA); 245 return (error); 246 } 247 248 LIST_INSERT_HEAD(&tag->mds, md, peers); 249 LIST_INSERT_HEAD(&busdma->mds, md, mds); 250 ioc->result = (uintptr_t)(void *)md; 251 return (0); 252 } 253 254 static int 255 proto_busdma_md_destroy(struct proto_busdma *busdma, struct proto_md *md) 256 { 257 258 if (md->virtaddr != NULL) 259 return (ENXIO); 260 return (proto_busdma_md_destroy_internal(busdma, md)); 261 } 262 263 static void 264 proto_busdma_md_load_callback(void *arg, bus_dma_segment_t *segs, int nseg, 265 bus_size_t sz, int error) 266 { 267 struct proto_callback_bundle *pcb = arg; 268 269 pcb->ioc->u.md.bus_nsegs = nseg; 270 pcb->ioc->u.md.bus_addr = segs[0].ds_addr; 271 } 272 273 static int 274 proto_busdma_md_load(struct proto_busdma *busdma, struct proto_md *md, 275 struct proto_ioc_busdma *ioc, struct thread *td) 276 { 277 struct proto_callback_bundle pcb; 278 struct iovec iov; 279 struct uio uio; 280 pmap_t pmap; 281 int error; 282 283 iov.iov_base = (void *)(uintptr_t)ioc->u.md.virt_addr; 284 iov.iov_len = ioc->u.md.virt_size; 285 uio.uio_iov = &iov; 286 uio.uio_iovcnt = 1; 287 uio.uio_offset = 0; 288 uio.uio_resid = iov.iov_len; 289 uio.uio_segflg = UIO_USERSPACE; 290 uio.uio_rw = UIO_READ; 291 uio.uio_td = td; 292 293 pcb.busdma = busdma; 294 pcb.md = md; 295 pcb.ioc = ioc; 296 error = bus_dmamap_load_uio(md->bd_tag, md->bd_map, &uio, 297 proto_busdma_md_load_callback, &pcb, BUS_DMA_NOWAIT); 298 if (error) 299 return (error); 300 301 /* XXX determine *all* physical memory segments */ 302 pmap = vmspace_pmap(td->td_proc->p_vmspace); 303 md->physaddr = pmap_extract(pmap, ioc->u.md.virt_addr); 304 ioc->u.md.phys_nsegs = 1; /* XXX */ 305 ioc->u.md.phys_addr = md->physaddr; 306 return (0); 307 } 308 309 static struct proto_md * 310 proto_busdma_md_lookup(struct proto_busdma *busdma, u_long key) 311 { 312 struct proto_md *md; 313 314 LIST_FOREACH(md, &busdma->mds, mds) { 315 if ((void *)md == (void *)key) 316 return (md); 317 } 318 return (NULL); 319 } 320 321 struct proto_busdma * 322 proto_busdma_attach(struct proto_softc *sc) 323 { 324 struct proto_busdma *busdma; 325 326 busdma = malloc(sizeof(*busdma), M_PROTO_BUSDMA, M_WAITOK | M_ZERO); 327 return (busdma); 328 } 329 330 int 331 proto_busdma_detach(struct proto_softc *sc, struct proto_busdma *busdma) 332 { 333 334 proto_busdma_cleanup(sc, busdma); 335 free(busdma, M_PROTO_BUSDMA); 336 return (0); 337 } 338 339 int 340 proto_busdma_cleanup(struct proto_softc *sc, struct proto_busdma *busdma) 341 { 342 struct proto_md *md, *md1; 343 struct proto_tag *tag, *tag1; 344 345 LIST_FOREACH_SAFE(md, &busdma->mds, mds, md1) 346 proto_busdma_md_destroy_internal(busdma, md); 347 LIST_FOREACH_SAFE(tag, &busdma->tags, tags, tag1) 348 proto_busdma_tag_destroy(busdma, tag); 349 return (0); 350 } 351 352 int 353 proto_busdma_ioctl(struct proto_softc *sc, struct proto_busdma *busdma, 354 struct proto_ioc_busdma *ioc, struct thread *td) 355 { 356 struct proto_tag *tag; 357 struct proto_md *md; 358 int error; 359 360 error = 0; 361 switch (ioc->request) { 362 case PROTO_IOC_BUSDMA_TAG_CREATE: 363 busdma->bd_roottag = bus_get_dma_tag(sc->sc_dev); 364 error = proto_busdma_tag_create(busdma, NULL, ioc); 365 break; 366 case PROTO_IOC_BUSDMA_TAG_DERIVE: 367 tag = proto_busdma_tag_lookup(busdma, ioc->key); 368 if (tag == NULL) { 369 error = EINVAL; 370 break; 371 } 372 error = proto_busdma_tag_create(busdma, tag, ioc); 373 break; 374 case PROTO_IOC_BUSDMA_TAG_DESTROY: 375 tag = proto_busdma_tag_lookup(busdma, ioc->key); 376 if (tag == NULL) { 377 error = EINVAL; 378 break; 379 } 380 error = proto_busdma_tag_destroy(busdma, tag); 381 break; 382 case PROTO_IOC_BUSDMA_MEM_ALLOC: 383 tag = proto_busdma_tag_lookup(busdma, ioc->u.md.tag); 384 if (tag == NULL) { 385 error = EINVAL; 386 break; 387 } 388 error = proto_busdma_mem_alloc(busdma, tag, ioc); 389 break; 390 case PROTO_IOC_BUSDMA_MEM_FREE: 391 md = proto_busdma_md_lookup(busdma, ioc->key); 392 if (md == NULL) { 393 error = EINVAL; 394 break; 395 } 396 error = proto_busdma_mem_free(busdma, md); 397 break; 398 case PROTO_IOC_BUSDMA_MD_CREATE: 399 tag = proto_busdma_tag_lookup(busdma, ioc->u.md.tag); 400 if (tag == NULL) { 401 error = EINVAL; 402 break; 403 } 404 error = proto_busdma_md_create(busdma, tag, ioc); 405 break; 406 case PROTO_IOC_BUSDMA_MD_DESTROY: 407 md = proto_busdma_md_lookup(busdma, ioc->key); 408 if (md == NULL) { 409 error = EINVAL; 410 break; 411 } 412 error = proto_busdma_md_destroy(busdma, md); 413 break; 414 case PROTO_IOC_BUSDMA_MD_LOAD: 415 md = proto_busdma_md_lookup(busdma, ioc->key); 416 if (md == NULL) { 417 error = EINVAL; 418 break; 419 } 420 error = proto_busdma_md_load(busdma, md, ioc, td); 421 break; 422 default: 423 error = EINVAL; 424 break; 425 } 426 return (error); 427 } 428 429 int 430 proto_busdma_mmap_allowed(struct proto_busdma *busdma, vm_paddr_t physaddr) 431 { 432 struct proto_md *md; 433 434 LIST_FOREACH(md, &busdma->mds, mds) { 435 if (physaddr >= trunc_page(md->physaddr) && 436 physaddr <= trunc_page(md->physaddr + md->tag->maxsz)) 437 return (1); 438 } 439 return (0); 440 } 441