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