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 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28#include <sys/ioctl.h>
29#include <sys/mman.h>
30#include <assert.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <limits.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include "busdma.h"
40
41#include "../../sys/dev/proto/proto_dev.h"
42
43struct obj {
44	int	oid;
45	u_int	type;
46#define	OBJ_TYPE_NONE	0
47#define	OBJ_TYPE_TAG	1
48#define	OBJ_TYPE_MD	2
49#define	OBJ_TYPE_SEG	3
50	u_int	refcnt;
51	int	fd;
52	struct obj *parent;
53	u_long	key;
54	union {
55		struct {
56			unsigned long	align;
57			unsigned long	bndry;
58			unsigned long	maxaddr;
59			unsigned long	maxsz;
60			unsigned long	maxsegsz;
61			unsigned long	nsegs;
62			unsigned long	datarate;
63		} tag;
64		struct {
65			struct obj	*seg[3];
66			int		nsegs[3];
67#define	BUSDMA_MD_BUS	0
68#define	BUSDMA_MD_PHYS	1
69#define	BUSDMA_MD_VIRT	2
70		} md;
71		struct {
72			struct obj	*next;
73			unsigned long	address;
74			unsigned long	size;
75		} seg;
76	} u;
77};
78
79static struct obj **oidtbl = NULL;
80static int noids = 0;
81
82static struct obj *
83obj_alloc(u_int type)
84{
85	struct obj **newtbl, *obj;
86	int oid;
87
88	obj = calloc(1, sizeof(struct obj));
89	obj->type = type;
90
91	for (oid = 0; oid < noids; oid++) {
92		if (oidtbl[oid] == 0)
93			break;
94	}
95	if (oid == noids) {
96		newtbl = realloc(oidtbl, sizeof(struct obj *) * (noids + 1));
97		if (newtbl == NULL) {
98			free(obj);
99			return (NULL);
100		}
101		oidtbl = newtbl;
102		noids++;
103	}
104	oidtbl[oid] = obj;
105	obj->oid = oid;
106	return (obj);
107}
108
109static int
110obj_free(struct obj *obj)
111{
112
113	oidtbl[obj->oid] = NULL;
114	free(obj);
115	return (0);
116}
117
118static struct obj *
119obj_lookup(int oid, u_int type)
120{
121	struct obj *obj;
122
123	if (oid < 0 || oid >= noids) {
124		errno = EINVAL;
125		return (NULL);
126	}
127	obj = oidtbl[oid];
128	if (obj->refcnt == 0) {
129		errno = ENXIO;
130		return (NULL);
131	}
132	if (type != OBJ_TYPE_NONE && obj->type != type) {
133		errno = ENODEV;
134		return (NULL);
135	}
136	return (obj);
137}
138
139static struct obj *
140bd_tag_new(struct obj *ptag, int fd, u_long align, u_long bndry,
141    u_long maxaddr, u_long maxsz, u_int nsegs, u_long maxsegsz,
142    u_int datarate, u_int flags)
143{
144	struct proto_ioc_busdma ioc;
145	struct obj *tag;
146
147	tag = obj_alloc(OBJ_TYPE_TAG);
148	if (tag == NULL)
149		return (NULL);
150
151	memset(&ioc, 0, sizeof(ioc));
152	ioc.request = (ptag != NULL) ? PROTO_IOC_BUSDMA_TAG_DERIVE :
153	    PROTO_IOC_BUSDMA_TAG_CREATE;
154	ioc.key = (ptag != NULL) ? ptag->key : 0;
155	ioc.u.tag.align = align;
156	ioc.u.tag.bndry = bndry;
157	ioc.u.tag.maxaddr = maxaddr;
158	ioc.u.tag.maxsz = maxsz;
159	ioc.u.tag.nsegs = nsegs;
160	ioc.u.tag.maxsegsz = maxsegsz;
161	ioc.u.tag.datarate = datarate;
162	ioc.u.tag.flags = flags;
163	if (ioctl(fd, PROTO_IOC_BUSDMA, &ioc) == -1) {
164		obj_free(tag);
165		return (NULL);
166	}
167	tag->refcnt = 1;
168	tag->fd = fd;
169	tag->parent = ptag;
170	tag->key = ioc.result;
171	tag->u.tag.align = ioc.u.tag.align;
172	tag->u.tag.bndry = ioc.u.tag.bndry;
173	tag->u.tag.maxaddr = ioc.u.tag.maxaddr;
174	tag->u.tag.maxsz = ioc.u.tag.maxsz;
175	tag->u.tag.maxsegsz = ioc.u.tag.maxsegsz;
176	tag->u.tag.nsegs = ioc.u.tag.nsegs;
177	tag->u.tag.datarate = ioc.u.tag.datarate;
178	return (tag);
179}
180
181int
182bd_tag_create(const char *dev, u_long align, u_long bndry, u_long maxaddr,
183    u_long maxsz, u_int nsegs, u_long maxsegsz, u_int datarate, u_int flags)
184{
185	char path[PATH_MAX];
186	struct obj *tag;
187	int fd, len;
188
189	len = snprintf(path, PATH_MAX, "/dev/proto/%s/busdma", dev);
190	if (len >= PATH_MAX) {
191		errno = EINVAL;
192		return (-1);
193	}
194	fd = open(path, O_RDWR);
195	if (fd == -1)
196		return (-1);
197
198	tag = bd_tag_new(NULL, fd, align, bndry, maxaddr, maxsz, nsegs,
199	    maxsegsz, datarate, flags);
200	if (tag == NULL) {
201		close(fd);
202		return (-1);
203	}
204	return (tag->oid);
205}
206
207int
208bd_tag_derive(int ptid, u_long align, u_long bndry, u_long maxaddr,
209    u_long maxsz, u_int nsegs, u_long maxsegsz, u_int datarate, u_int flags)
210{
211	struct obj *ptag, *tag;
212
213	ptag = obj_lookup(ptid, OBJ_TYPE_TAG);
214	if (ptag == NULL)
215		return (-1);
216
217	tag = bd_tag_new(ptag, ptag->fd, align, bndry, maxaddr, maxsz, nsegs,
218	    maxsegsz, datarate, flags);
219	if (tag == NULL)
220		return (-1);
221	ptag->refcnt++;
222	return (tag->oid);
223}
224
225int
226bd_tag_destroy(int tid)
227{
228	struct proto_ioc_busdma ioc;
229	struct obj *ptag, *tag;
230
231	tag = obj_lookup(tid, OBJ_TYPE_TAG);
232	if (tag == NULL)
233		return (errno);
234	if (tag->refcnt > 1)
235		return (EBUSY);
236
237	memset(&ioc, 0, sizeof(ioc));
238	ioc.request = PROTO_IOC_BUSDMA_TAG_DESTROY;
239	ioc.key = tag->key;
240	if (ioctl(tag->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
241		return (errno);
242
243	if (tag->parent != NULL)
244		tag->parent->refcnt--;
245	else
246		close(tag->fd);
247	obj_free(tag);
248	return (0);
249}
250
251static int
252bd_md_add_seg(struct obj *md, int type, u_long addr, u_long size)
253{
254	struct obj *seg;
255
256	seg = obj_alloc(OBJ_TYPE_SEG);
257	if (seg == NULL)
258		return (errno);
259	seg->refcnt = 1;
260	seg->parent = md;
261	seg->u.seg.address = addr;
262	seg->u.seg.size = size;
263
264	md->u.md.seg[type] = seg;
265	md->u.md.nsegs[type] = 1;
266	return (0);
267}
268
269static int
270bd_md_del_segs(struct obj *md, int type, int unmap)
271{
272	struct obj *seg, *seg0;
273
274	for (seg = md->u.md.seg[type]; seg != NULL; seg = seg0) {
275		if (unmap)
276			munmap((void *)seg->u.seg.address, seg->u.seg.size);
277		seg0 = seg->u.seg.next;
278		obj_free(seg);
279	}
280	return (0);
281}
282
283int
284bd_md_create(int tid, u_int flags)
285{
286	struct proto_ioc_busdma ioc;
287	struct obj *md, *tag;
288
289	tag = obj_lookup(tid, OBJ_TYPE_TAG);
290	if (tag == NULL)
291		return (-1);
292
293	md = obj_alloc(OBJ_TYPE_MD);
294	if (md == NULL)
295		return (-1);
296
297	memset(&ioc, 0, sizeof(ioc));
298	ioc.request = PROTO_IOC_BUSDMA_MD_CREATE;
299	ioc.u.md.tag = tag->key;
300	ioc.u.md.flags = flags;
301	if (ioctl(tag->fd, PROTO_IOC_BUSDMA, &ioc) == -1) {
302		obj_free(md);
303		return (-1);
304	}
305
306	md->refcnt = 1;
307	md->fd = tag->fd;
308	md->parent = tag;
309	tag->refcnt++;
310	md->key = ioc.result;
311	return (md->oid);
312}
313
314int
315bd_md_destroy(int mdid)
316{
317	struct proto_ioc_busdma ioc;
318	struct obj *md;
319
320	md = obj_lookup(mdid, OBJ_TYPE_MD);
321	if (md == NULL)
322		return (errno);
323
324	memset(&ioc, 0, sizeof(ioc));
325	ioc.request = PROTO_IOC_BUSDMA_MD_DESTROY;
326	ioc.key = md->key;
327	if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
328		return (errno);
329
330	md->parent->refcnt--;
331	obj_free(md);
332	return (0);
333}
334
335int
336bd_md_load(int mdid, void *buf, u_long len, u_int flags)
337{
338	struct proto_ioc_busdma ioc;
339	struct obj *md;
340	int error;
341
342	md = obj_lookup(mdid, OBJ_TYPE_MD);
343	if (md == NULL)
344		return (errno);
345
346	memset(&ioc, 0, sizeof(ioc));
347	ioc.request = PROTO_IOC_BUSDMA_MD_LOAD;
348	ioc.key = md->key;
349	ioc.u.md.flags = flags;
350	ioc.u.md.virt_addr = (uintptr_t)buf;
351	ioc.u.md.virt_size = len;
352	if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
353		return (errno);
354
355	error = bd_md_add_seg(md, BUSDMA_MD_VIRT, ioc.u.md.virt_addr, len);
356	error = bd_md_add_seg(md, BUSDMA_MD_PHYS, ioc.u.md.phys_addr, len);
357	error = bd_md_add_seg(md, BUSDMA_MD_BUS, ioc.u.md.bus_addr, len);
358	return (error);
359}
360
361int
362bd_md_unload(int mdid)
363{
364	struct proto_ioc_busdma ioc;
365	struct obj *md;
366	int error;
367
368	md = obj_lookup(mdid, OBJ_TYPE_MD);
369	if (md == NULL)
370		return (errno);
371
372	memset(&ioc, 0, sizeof(ioc));
373	ioc.request = PROTO_IOC_BUSDMA_MD_UNLOAD;
374	ioc.key = md->key;
375	if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
376		return (errno);
377
378	bd_md_del_segs(md, BUSDMA_MD_VIRT, 0);
379	bd_md_del_segs(md, BUSDMA_MD_PHYS, 0);
380	bd_md_del_segs(md, BUSDMA_MD_BUS, 0);
381	return (0);
382}
383
384int
385bd_mem_alloc(int tid, u_int flags)
386{
387	struct proto_ioc_busdma ioc;
388	struct obj *md, *tag;
389	uintptr_t addr;
390	int error;
391
392	tag = obj_lookup(tid, OBJ_TYPE_TAG);
393	if (tag == NULL)
394		return (-1);
395
396	md = obj_alloc(OBJ_TYPE_MD);
397	if (md == NULL)
398		return (-1);
399
400	memset(&ioc, 0, sizeof(ioc));
401	ioc.request = PROTO_IOC_BUSDMA_MEM_ALLOC;
402	ioc.u.md.tag = tag->key;
403	ioc.u.md.flags = flags;
404	if (ioctl(tag->fd, PROTO_IOC_BUSDMA, &ioc) == -1) {
405		obj_free(md);
406		return (-1);
407	}
408
409	md->refcnt = 1;
410	md->fd = tag->fd;
411	md->parent = tag;
412	tag->refcnt++;
413	md->key = ioc.result;
414
415	/* XXX we need to support multiple segments */
416	assert(ioc.u.md.phys_nsegs == 1);
417	assert(ioc.u.md.bus_nsegs == 1);
418	error = bd_md_add_seg(md, BUSDMA_MD_PHYS, ioc.u.md.phys_addr,
419	    tag->u.tag.maxsz);
420	error = bd_md_add_seg(md, BUSDMA_MD_BUS, ioc.u.md.bus_addr,
421	    tag->u.tag.maxsz);
422
423	addr = (uintptr_t)mmap(NULL, tag->u.tag.maxsz, PROT_READ | PROT_WRITE,
424	    MAP_NOCORE | MAP_SHARED, md->fd, ioc.u.md.phys_addr);
425	if (addr == (uintptr_t)MAP_FAILED)
426		goto fail;
427	error = bd_md_add_seg(md, BUSDMA_MD_VIRT, addr, tag->u.tag.maxsz);
428
429	return (md->oid);
430
431 fail:
432	memset(&ioc, 0, sizeof(ioc));
433	ioc.request = PROTO_IOC_BUSDMA_MEM_FREE;
434	ioc.key = md->key;
435	ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc);
436	md->parent->refcnt--;
437	obj_free(md);
438	return (-1);
439}
440
441int
442bd_mem_free(int mdid)
443{
444	struct proto_ioc_busdma ioc;
445	struct obj *md;
446
447	md = obj_lookup(mdid, OBJ_TYPE_MD);
448	if (md == NULL)
449		return (errno);
450
451	memset(&ioc, 0, sizeof(ioc));
452	ioc.request = PROTO_IOC_BUSDMA_MEM_FREE;
453	ioc.key = md->key;
454	if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
455		return (errno);
456
457	bd_md_del_segs(md, BUSDMA_MD_VIRT, 1);
458	bd_md_del_segs(md, BUSDMA_MD_PHYS, 0);
459	bd_md_del_segs(md, BUSDMA_MD_BUS, 0);
460	md->parent->refcnt--;
461	obj_free(md);
462	return (0);
463}
464
465int
466bd_md_first_seg(int mdid, int space)
467{
468	struct obj *md, *seg;
469
470	md = obj_lookup(mdid, OBJ_TYPE_MD);
471	if (md == NULL)
472		return (-1);
473
474	if (space != BUSDMA_MD_BUS && space != BUSDMA_MD_PHYS &&
475	    space != BUSDMA_MD_VIRT) {
476		errno = EINVAL;
477		return (-1);
478	}
479	seg = md->u.md.seg[space];
480	if (seg == NULL) {
481		errno = ENXIO;
482		return (-1);
483	}
484	return (seg->oid);
485}
486
487int
488bd_md_next_seg(int mdid, int sid)
489{
490	struct obj *seg;
491
492	seg = obj_lookup(sid, OBJ_TYPE_SEG);
493	if (seg == NULL)
494		return (-1);
495
496	seg = seg->u.seg.next;
497	if (seg == NULL) {
498		errno = ENXIO;
499		return (-1);
500	}
501	return (seg->oid);
502}
503
504int
505bd_seg_get_addr(int sid, u_long *addr_p)
506{
507	struct obj *seg;
508
509	if (addr_p == NULL)
510		return (EINVAL);
511
512	seg = obj_lookup(sid, OBJ_TYPE_SEG);
513	if (seg == NULL)
514		return (errno);
515
516	*addr_p = seg->u.seg.address;
517	return (0);
518}
519
520int
521bd_seg_get_size(int sid, u_long *size_p)
522{
523	struct obj *seg;
524
525	if (size_p == NULL)
526		return (EINVAL);
527
528	seg = obj_lookup(sid, OBJ_TYPE_SEG);
529	if (seg == NULL)
530		return (errno);
531
532	*size_p = seg->u.seg.size;
533	return (0);
534}
535
536int
537bd_sync(int mdid, u_int op, u_long ofs, u_long len)
538{
539	struct proto_ioc_busdma ioc;
540	struct obj *md;
541
542	md = obj_lookup(mdid, OBJ_TYPE_MD);
543	if (md == NULL)
544		return (errno);
545
546	memset(&ioc, 0, sizeof(ioc));
547	ioc.request = PROTO_IOC_BUSDMA_SYNC;
548	ioc.key = md->key;
549	ioc.u.sync.op = op;
550	ioc.u.sync.base = ofs;
551	ioc.u.sync.size = len;
552	if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
553		return (errno);
554
555	return (0);
556}
557