xref: /freebsd/sys/dev/proto/proto_busdma.c (revision 3494f7c0)
1 /*-
2  * Copyright (c) 2015, 2019 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/param.h>
27 #include <sys/systm.h>
28 #include <machine/bus.h>
29 #include <machine/bus_dma.h>
30 #include <machine/resource.h>
31 #include <sys/bus.h>
32 #include <sys/conf.h>
33 #include <sys/kernel.h>
34 #include <sys/malloc.h>
35 #include <sys/module.h>
36 #include <sys/proc.h>
37 #include <sys/queue.h>
38 #include <sys/rman.h>
39 #include <sys/sbuf.h>
40 #include <sys/sx.h>
41 #include <sys/uio.h>
42 #include <vm/vm.h>
43 #include <vm/pmap.h>
44 #include <vm/vm_map.h>
45 
46 #include <dev/proto/proto.h>
47 #include <dev/proto/proto_dev.h>
48 #include <dev/proto/proto_busdma.h>
49 
50 MALLOC_DEFINE(M_PROTO_BUSDMA, "proto_busdma", "DMA management data");
51 
52 #define	BNDRY_MIN(a, b)		\
53 	(((a) == 0) ? (b) : (((b) == 0) ? (a) : MIN((a), (b))))
54 
55 struct proto_callback_bundle {
56 	struct proto_busdma *busdma;
57 	struct proto_md *md;
58 	struct proto_ioc_busdma *ioc;
59 };
60 
61 static int
62 proto_busdma_tag_create(struct proto_busdma *busdma, struct proto_tag *parent,
63     struct proto_ioc_busdma *ioc)
64 {
65 	struct proto_tag *tag;
66 
67 	/* Make sure that when a boundary is specified, it's a power of 2 */
68 	if (ioc->u.tag.bndry != 0 &&
69 	    (ioc->u.tag.bndry & (ioc->u.tag.bndry - 1)) != 0)
70 		return (EINVAL);
71 
72 	/*
73 	 * If nsegs is 1, ignore maxsegsz. What this means is that if we have
74 	 * just 1 segment, then maxsz should be equal to maxsegsz. To keep it
75 	 * simple for us, limit maxsegsz to maxsz in any case.
76 	 */
77 	if (ioc->u.tag.maxsegsz > ioc->u.tag.maxsz || ioc->u.tag.nsegs == 1)
78 		ioc->u.tag.maxsegsz = ioc->u.tag.maxsz;
79 
80 	tag = malloc(sizeof(*tag), M_PROTO_BUSDMA, M_WAITOK | M_ZERO);
81 	if (parent != NULL) {
82 		tag->parent = parent;
83 		LIST_INSERT_HEAD(&parent->children, tag, peers);
84 		tag->align = MAX(ioc->u.tag.align, parent->align);
85 		tag->bndry = BNDRY_MIN(ioc->u.tag.bndry, parent->bndry);
86 		tag->maxaddr = MIN(ioc->u.tag.maxaddr, parent->maxaddr);
87 		tag->maxsz = MIN(ioc->u.tag.maxsz, parent->maxsz);
88 		tag->maxsegsz = MIN(ioc->u.tag.maxsegsz, parent->maxsegsz);
89 		tag->nsegs = MIN(ioc->u.tag.nsegs, parent->nsegs);
90 		tag->datarate = MIN(ioc->u.tag.datarate, parent->datarate);
91 		/* Write constraints back */
92 		ioc->u.tag.align = tag->align;
93 		ioc->u.tag.bndry = tag->bndry;
94 		ioc->u.tag.maxaddr = tag->maxaddr;
95 		ioc->u.tag.maxsz = tag->maxsz;
96 		ioc->u.tag.maxsegsz = tag->maxsegsz;
97 		ioc->u.tag.nsegs = tag->nsegs;
98 		ioc->u.tag.datarate = tag->datarate;
99 	} else {
100 		tag->align = ioc->u.tag.align;
101 		tag->bndry = ioc->u.tag.bndry;
102 		tag->maxaddr = ioc->u.tag.maxaddr;
103 		tag->maxsz = ioc->u.tag.maxsz;
104 		tag->maxsegsz = ioc->u.tag.maxsegsz;
105 		tag->nsegs = ioc->u.tag.nsegs;
106 		tag->datarate = ioc->u.tag.datarate;
107 	}
108 	LIST_INSERT_HEAD(&busdma->tags, tag, tags);
109 	ioc->result = (uintptr_t)(void *)tag;
110 	return (0);
111 }
112 
113 static int
114 proto_busdma_tag_destroy(struct proto_busdma *busdma, struct proto_tag *tag)
115 {
116 
117 	if (!LIST_EMPTY(&tag->mds))
118 		return (EBUSY);
119 	if (!LIST_EMPTY(&tag->children))
120 		return (EBUSY);
121 
122 	if (tag->parent != NULL) {
123 		LIST_REMOVE(tag, peers);
124 		tag->parent = NULL;
125 	}
126 	LIST_REMOVE(tag, tags);
127 	free(tag, M_PROTO_BUSDMA);
128 	return (0);
129 }
130 
131 static struct proto_tag *
132 proto_busdma_tag_lookup(struct proto_busdma *busdma, u_long key)
133 {
134 	struct proto_tag *tag;
135 
136 	LIST_FOREACH(tag, &busdma->tags, tags) {
137 		if ((void *)tag == (void *)key)
138 			return (tag);
139 	}
140 	return (NULL);
141 }
142 
143 static int
144 proto_busdma_md_destroy_internal(struct proto_busdma *busdma,
145     struct proto_md *md)
146 {
147 
148 	LIST_REMOVE(md, mds);
149 	LIST_REMOVE(md, peers);
150 	if (md->physaddr)
151 		bus_dmamap_unload(md->bd_tag, md->bd_map);
152 	if (md->virtaddr != NULL)
153 		bus_dmamem_free(md->bd_tag, md->virtaddr, md->bd_map);
154 	else
155 		bus_dmamap_destroy(md->bd_tag, md->bd_map);
156 	bus_dma_tag_destroy(md->bd_tag);
157 	free(md, M_PROTO_BUSDMA);
158 	return (0);
159 }
160 
161 static void
162 proto_busdma_mem_alloc_callback(void *arg, bus_dma_segment_t *segs, int	nseg,
163     int error)
164 {
165 	struct proto_callback_bundle *pcb = arg;
166 
167 	pcb->ioc->u.md.bus_nsegs = nseg;
168 	pcb->ioc->u.md.bus_addr = segs[0].ds_addr;
169 }
170 
171 static int
172 proto_busdma_mem_alloc(struct proto_busdma *busdma, struct proto_tag *tag,
173     struct proto_ioc_busdma *ioc)
174 {
175 	struct proto_callback_bundle pcb;
176 	struct proto_md *md;
177 	int error;
178 
179 	md = malloc(sizeof(*md), M_PROTO_BUSDMA, M_WAITOK | M_ZERO);
180 	md->tag = tag;
181 
182 	error = bus_dma_tag_create(busdma->bd_roottag, tag->align, tag->bndry,
183 	    tag->maxaddr, BUS_SPACE_MAXADDR, NULL, NULL, tag->maxsz,
184 	    tag->nsegs, tag->maxsegsz, 0, NULL, NULL, &md->bd_tag);
185 	if (error) {
186 		free(md, M_PROTO_BUSDMA);
187 		return (error);
188 	}
189 	error = bus_dmamem_alloc(md->bd_tag, &md->virtaddr, 0, &md->bd_map);
190 	if (error) {
191 		bus_dma_tag_destroy(md->bd_tag);
192 		free(md, M_PROTO_BUSDMA);
193 		return (error);
194 	}
195 	md->physaddr = pmap_kextract((uintptr_t)(md->virtaddr));
196 	pcb.busdma = busdma;
197 	pcb.md = md;
198 	pcb.ioc = ioc;
199 	error = bus_dmamap_load(md->bd_tag, md->bd_map, md->virtaddr,
200 	    tag->maxsz, proto_busdma_mem_alloc_callback, &pcb, BUS_DMA_NOWAIT);
201 	if (error) {
202 		bus_dmamem_free(md->bd_tag, md->virtaddr, md->bd_map);
203 		bus_dma_tag_destroy(md->bd_tag);
204 		free(md, M_PROTO_BUSDMA);
205 		return (error);
206 	}
207 	LIST_INSERT_HEAD(&tag->mds, md, peers);
208 	LIST_INSERT_HEAD(&busdma->mds, md, mds);
209 	ioc->u.md.virt_addr = (uintptr_t)md->virtaddr;
210 	ioc->u.md.virt_size = tag->maxsz;
211 	ioc->u.md.phys_nsegs = 1;
212 	ioc->u.md.phys_addr = md->physaddr;
213 	ioc->result = (uintptr_t)(void *)md;
214 	return (0);
215 }
216 
217 static int
218 proto_busdma_mem_free(struct proto_busdma *busdma, struct proto_md *md)
219 {
220 
221 	if (md->virtaddr == NULL)
222 		return (ENXIO);
223 	return (proto_busdma_md_destroy_internal(busdma, md));
224 }
225 
226 static int
227 proto_busdma_md_create(struct proto_busdma *busdma, struct proto_tag *tag,
228     struct proto_ioc_busdma *ioc)
229 {
230 	struct proto_md *md;
231 	int error;
232 
233 	md = malloc(sizeof(*md), M_PROTO_BUSDMA, M_WAITOK | M_ZERO);
234 	md->tag = tag;
235 
236 	error = bus_dma_tag_create(busdma->bd_roottag, tag->align, tag->bndry,
237 	    tag->maxaddr, BUS_SPACE_MAXADDR, NULL, NULL, tag->maxsz,
238 	    tag->nsegs, tag->maxsegsz, 0, NULL, NULL, &md->bd_tag);
239 	if (error) {
240 		free(md, M_PROTO_BUSDMA);
241 		return (error);
242 	}
243 	error = bus_dmamap_create(md->bd_tag, 0, &md->bd_map);
244 	if (error) {
245 		bus_dma_tag_destroy(md->bd_tag);
246 		free(md, M_PROTO_BUSDMA);
247 		return (error);
248 	}
249 
250 	LIST_INSERT_HEAD(&tag->mds, md, peers);
251 	LIST_INSERT_HEAD(&busdma->mds, md, mds);
252 	ioc->result = (uintptr_t)(void *)md;
253 	return (0);
254 }
255 
256 static int
257 proto_busdma_md_destroy(struct proto_busdma *busdma, struct proto_md *md)
258 {
259 
260 	if (md->virtaddr != NULL)
261 		return (ENXIO);
262 	return (proto_busdma_md_destroy_internal(busdma, md));
263 }
264 
265 static void
266 proto_busdma_md_load_callback(void *arg, bus_dma_segment_t *segs, int nseg,
267     bus_size_t sz, int error)
268 {
269 	struct proto_callback_bundle *pcb = arg;
270 
271 	pcb->ioc->u.md.bus_nsegs = nseg;
272 	pcb->ioc->u.md.bus_addr = segs[0].ds_addr;
273 }
274 
275 static int
276 proto_busdma_md_load(struct proto_busdma *busdma, struct proto_md *md,
277     struct proto_ioc_busdma *ioc, struct thread *td)
278 {
279 	struct proto_callback_bundle pcb;
280 	struct iovec iov;
281 	struct uio uio;
282 	pmap_t pmap;
283 	int error;
284 
285 	iov.iov_base = (void *)(uintptr_t)ioc->u.md.virt_addr;
286 	iov.iov_len = ioc->u.md.virt_size;
287 	uio.uio_iov = &iov;
288 	uio.uio_iovcnt = 1;
289 	uio.uio_offset = 0;
290 	uio.uio_resid = iov.iov_len;
291 	uio.uio_segflg = UIO_USERSPACE;
292 	uio.uio_rw = UIO_READ;
293 	uio.uio_td = td;
294 
295 	pcb.busdma = busdma;
296 	pcb.md = md;
297 	pcb.ioc = ioc;
298 	error = bus_dmamap_load_uio(md->bd_tag, md->bd_map, &uio,
299 	    proto_busdma_md_load_callback, &pcb, BUS_DMA_NOWAIT);
300 	if (error)
301 		return (error);
302 
303 	/* XXX determine *all* physical memory segments */
304 	pmap = vmspace_pmap(td->td_proc->p_vmspace);
305 	md->physaddr = pmap_extract(pmap, ioc->u.md.virt_addr);
306 	ioc->u.md.phys_nsegs = 1;	/* XXX */
307 	ioc->u.md.phys_addr = md->physaddr;
308 	return (0);
309 }
310 
311 static int
312 proto_busdma_md_unload(struct proto_busdma *busdma, struct proto_md *md)
313 {
314 
315 	if (!md->physaddr)
316 		return (ENXIO);
317 	bus_dmamap_unload(md->bd_tag, md->bd_map);
318 	md->physaddr = 0;
319 	return (0);
320 }
321 
322 static int
323 proto_busdma_sync(struct proto_busdma *busdma, struct proto_md *md,
324     struct proto_ioc_busdma *ioc)
325 {
326 	u_int ops;
327 
328 	ops = BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE |
329 	    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE;
330 	if (ioc->u.sync.op & ~ops)
331 		return (EINVAL);
332 	if (!md->physaddr)
333 		return (ENXIO);
334 	bus_dmamap_sync(md->bd_tag, md->bd_map, ioc->u.sync.op);
335 	return (0);
336 }
337 
338 static struct proto_md *
339 proto_busdma_md_lookup(struct proto_busdma *busdma, u_long key)
340 {
341 	struct proto_md *md;
342 
343 	LIST_FOREACH(md, &busdma->mds, mds) {
344 		if ((void *)md == (void *)key)
345 			return (md);
346 	}
347 	return (NULL);
348 }
349 
350 struct proto_busdma *
351 proto_busdma_attach(struct proto_softc *sc)
352 {
353 	struct proto_busdma *busdma;
354 
355 	busdma = malloc(sizeof(*busdma), M_PROTO_BUSDMA, M_WAITOK | M_ZERO);
356 	sx_init(&busdma->sxlck, "proto-busdma");
357 	return (busdma);
358 }
359 
360 int
361 proto_busdma_detach(struct proto_softc *sc, struct proto_busdma *busdma)
362 {
363 
364 	proto_busdma_cleanup(sc, busdma);
365 	sx_destroy(&busdma->sxlck);
366 	free(busdma, M_PROTO_BUSDMA);
367 	return (0);
368 }
369 
370 int
371 proto_busdma_cleanup(struct proto_softc *sc, struct proto_busdma *busdma)
372 {
373 	struct proto_md *md, *md1;
374 	struct proto_tag *tag, *tag1;
375 
376 	sx_xlock(&busdma->sxlck);
377 	LIST_FOREACH_SAFE(md, &busdma->mds, mds, md1)
378 		proto_busdma_md_destroy_internal(busdma, md);
379 	LIST_FOREACH_SAFE(tag, &busdma->tags, tags, tag1)
380 		proto_busdma_tag_destroy(busdma, tag);
381 	sx_xunlock(&busdma->sxlck);
382 	return (0);
383 }
384 
385 int
386 proto_busdma_ioctl(struct proto_softc *sc, struct proto_busdma *busdma,
387     struct proto_ioc_busdma *ioc, struct thread *td)
388 {
389 	struct proto_tag *tag;
390 	struct proto_md *md;
391 	int error;
392 
393 	sx_xlock(&busdma->sxlck);
394 
395 	error = 0;
396 	switch (ioc->request) {
397 	case PROTO_IOC_BUSDMA_TAG_CREATE:
398 		busdma->bd_roottag = bus_get_dma_tag(sc->sc_dev);
399 		error = proto_busdma_tag_create(busdma, NULL, ioc);
400 		break;
401 	case PROTO_IOC_BUSDMA_TAG_DERIVE:
402 		tag = proto_busdma_tag_lookup(busdma, ioc->key);
403 		if (tag == NULL) {
404 			error = EINVAL;
405 			break;
406 		}
407 		error = proto_busdma_tag_create(busdma, tag, ioc);
408 		break;
409 	case PROTO_IOC_BUSDMA_TAG_DESTROY:
410 		tag = proto_busdma_tag_lookup(busdma, ioc->key);
411 		if (tag == NULL) {
412 			error = EINVAL;
413 			break;
414 		}
415 		error = proto_busdma_tag_destroy(busdma, tag);
416 		break;
417 	case PROTO_IOC_BUSDMA_MEM_ALLOC:
418 		tag = proto_busdma_tag_lookup(busdma, ioc->u.md.tag);
419 		if (tag == NULL) {
420 			error = EINVAL;
421 			break;
422 		}
423 		error = proto_busdma_mem_alloc(busdma, tag, ioc);
424 		break;
425 	case PROTO_IOC_BUSDMA_MEM_FREE:
426 		md = proto_busdma_md_lookup(busdma, ioc->key);
427 		if (md == NULL) {
428 			error = EINVAL;
429 			break;
430 		}
431 		error = proto_busdma_mem_free(busdma, md);
432 		break;
433 	case PROTO_IOC_BUSDMA_MD_CREATE:
434 		tag = proto_busdma_tag_lookup(busdma, ioc->u.md.tag);
435 		if (tag == NULL) {
436 			error = EINVAL;
437 			break;
438 		}
439 		error = proto_busdma_md_create(busdma, tag, ioc);
440 		break;
441 	case PROTO_IOC_BUSDMA_MD_DESTROY:
442 		md = proto_busdma_md_lookup(busdma, ioc->key);
443 		if (md == NULL) {
444 			error = EINVAL;
445 			break;
446 		}
447 		error = proto_busdma_md_destroy(busdma, md);
448 		break;
449 	case PROTO_IOC_BUSDMA_MD_LOAD:
450 		md = proto_busdma_md_lookup(busdma, ioc->key);
451 		if (md == NULL) {
452 			error = EINVAL;
453 			break;
454 		}
455 		error = proto_busdma_md_load(busdma, md, ioc, td);
456 		break;
457 	case PROTO_IOC_BUSDMA_MD_UNLOAD:
458 		md = proto_busdma_md_lookup(busdma, ioc->key);
459 		if (md == NULL) {
460 			error = EINVAL;
461 			break;
462 		}
463 		error = proto_busdma_md_unload(busdma, md);
464 		break;
465 	case PROTO_IOC_BUSDMA_SYNC:
466 		md = proto_busdma_md_lookup(busdma, ioc->key);
467 		if (md == NULL) {
468 			error = EINVAL;
469 			break;
470 		}
471 		error = proto_busdma_sync(busdma, md, ioc);
472 		break;
473 	default:
474 		error = EINVAL;
475 		break;
476 	}
477 
478 	sx_xunlock(&busdma->sxlck);
479 
480 	return (error);
481 }
482 
483 int
484 proto_busdma_mmap_allowed(struct proto_busdma *busdma, vm_paddr_t physaddr)
485 {
486 	struct proto_md *md;
487 	int result;
488 
489 	sx_xlock(&busdma->sxlck);
490 
491 	result = 0;
492 	LIST_FOREACH(md, &busdma->mds, mds) {
493 		if (physaddr >= trunc_page(md->physaddr) &&
494 		    physaddr <= trunc_page(md->physaddr + md->tag->maxsz)) {
495 			result = 1;
496 			break;
497 		}
498 	}
499 
500 	sx_xunlock(&busdma->sxlck);
501 
502 	return (result);
503 }
504