xref: /freebsd/sys/dev/xdma/xdma.c (revision 0957b409)
1 /*-
2  * Copyright (c) 2016-2018 Ruslan Bukin <br@bsdpad.com>
3  * All rights reserved.
4  *
5  * This software was developed by SRI International and the University of
6  * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
7  * ("CTSRD"), as part of the DARPA CRASH research programme.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include "opt_platform.h"
35 #include <sys/param.h>
36 #include <sys/conf.h>
37 #include <sys/bus.h>
38 #include <sys/kernel.h>
39 #include <sys/queue.h>
40 #include <sys/kobj.h>
41 #include <sys/malloc.h>
42 #include <sys/limits.h>
43 #include <sys/lock.h>
44 #include <sys/sysctl.h>
45 #include <sys/systm.h>
46 #include <sys/sx.h>
47 
48 #include <machine/bus.h>
49 
50 #ifdef FDT
51 #include <dev/fdt/fdt_common.h>
52 #include <dev/ofw/ofw_bus.h>
53 #include <dev/ofw/ofw_bus_subr.h>
54 #endif
55 
56 #include <dev/xdma/xdma.h>
57 
58 #include <xdma_if.h>
59 
60 /*
61  * Multiple xDMA controllers may work with single DMA device,
62  * so we have global lock for physical channel management.
63  */
64 static struct sx xdma_sx;
65 
66 #define	XDMA_LOCK()			sx_xlock(&xdma_sx)
67 #define	XDMA_UNLOCK()			sx_xunlock(&xdma_sx)
68 #define	XDMA_ASSERT_LOCKED()		sx_xassert(&xdma_sx, MA_OWNED)
69 
70 /*
71  * Allocate virtual xDMA channel.
72  */
73 xdma_channel_t *
74 xdma_channel_alloc(xdma_controller_t *xdma, uint32_t caps)
75 {
76 	xdma_channel_t *xchan;
77 	int ret;
78 
79 	xchan = malloc(sizeof(xdma_channel_t), M_XDMA, M_WAITOK | M_ZERO);
80 	xchan->xdma = xdma;
81 	xchan->caps = caps;
82 
83 	XDMA_LOCK();
84 
85 	/* Request a real channel from hardware driver. */
86 	ret = XDMA_CHANNEL_ALLOC(xdma->dma_dev, xchan);
87 	if (ret != 0) {
88 		device_printf(xdma->dev,
89 		    "%s: Can't request hardware channel.\n", __func__);
90 		XDMA_UNLOCK();
91 		free(xchan, M_XDMA);
92 
93 		return (NULL);
94 	}
95 
96 	TAILQ_INIT(&xchan->ie_handlers);
97 
98 	sx_init(&xchan->sx_lock, "xDMA chan");
99 	sx_init(&xchan->sx_qin_lock, "xDMA qin");
100 	sx_init(&xchan->sx_qout_lock, "xDMA qout");
101 	sx_init(&xchan->sx_bank_lock, "xDMA bank");
102 	sx_init(&xchan->sx_proc_lock, "xDMA proc");
103 
104 	TAILQ_INIT(&xchan->bank);
105 	TAILQ_INIT(&xchan->queue_in);
106 	TAILQ_INIT(&xchan->queue_out);
107 	TAILQ_INIT(&xchan->processing);
108 
109 	TAILQ_INSERT_TAIL(&xdma->channels, xchan, xchan_next);
110 
111 	XDMA_UNLOCK();
112 
113 	return (xchan);
114 }
115 
116 int
117 xdma_channel_free(xdma_channel_t *xchan)
118 {
119 	xdma_controller_t *xdma;
120 	int err;
121 
122 	xdma = xchan->xdma;
123 	KASSERT(xdma != NULL, ("xdma is NULL"));
124 
125 	XDMA_LOCK();
126 
127 	/* Free the real DMA channel. */
128 	err = XDMA_CHANNEL_FREE(xdma->dma_dev, xchan);
129 	if (err != 0) {
130 		device_printf(xdma->dev,
131 		    "%s: Can't free real hw channel.\n", __func__);
132 		XDMA_UNLOCK();
133 		return (-1);
134 	}
135 
136 	if (xchan->flags & XCHAN_TYPE_SG)
137 		xdma_channel_free_sg(xchan);
138 
139 	xdma_teardown_all_intr(xchan);
140 
141 	sx_destroy(&xchan->sx_lock);
142 	sx_destroy(&xchan->sx_qin_lock);
143 	sx_destroy(&xchan->sx_qout_lock);
144 	sx_destroy(&xchan->sx_bank_lock);
145 	sx_destroy(&xchan->sx_proc_lock);
146 
147 	TAILQ_REMOVE(&xdma->channels, xchan, xchan_next);
148 
149 	free(xchan, M_XDMA);
150 
151 	XDMA_UNLOCK();
152 
153 	return (0);
154 }
155 
156 int
157 xdma_setup_intr(xdma_channel_t *xchan,
158     int (*cb)(void *, xdma_transfer_status_t *),
159     void *arg, void **ihandler)
160 {
161 	struct xdma_intr_handler *ih;
162 	xdma_controller_t *xdma;
163 
164 	xdma = xchan->xdma;
165 	KASSERT(xdma != NULL, ("xdma is NULL"));
166 
167 	/* Sanity check. */
168 	if (cb == NULL) {
169 		device_printf(xdma->dev,
170 		    "%s: Can't setup interrupt handler.\n",
171 		    __func__);
172 
173 		return (-1);
174 	}
175 
176 	ih = malloc(sizeof(struct xdma_intr_handler),
177 	    M_XDMA, M_WAITOK | M_ZERO);
178 	ih->cb = cb;
179 	ih->cb_user = arg;
180 
181 	XCHAN_LOCK(xchan);
182 	TAILQ_INSERT_TAIL(&xchan->ie_handlers, ih, ih_next);
183 	XCHAN_UNLOCK(xchan);
184 
185 	if (ihandler != NULL)
186 		*ihandler = ih;
187 
188 	return (0);
189 }
190 
191 int
192 xdma_teardown_intr(xdma_channel_t *xchan, struct xdma_intr_handler *ih)
193 {
194 	xdma_controller_t *xdma;
195 
196 	xdma = xchan->xdma;
197 	KASSERT(xdma != NULL, ("xdma is NULL"));
198 
199 	/* Sanity check. */
200 	if (ih == NULL) {
201 		device_printf(xdma->dev,
202 		    "%s: Can't teardown interrupt.\n", __func__);
203 		return (-1);
204 	}
205 
206 	TAILQ_REMOVE(&xchan->ie_handlers, ih, ih_next);
207 	free(ih, M_XDMA);
208 
209 	return (0);
210 }
211 
212 int
213 xdma_teardown_all_intr(xdma_channel_t *xchan)
214 {
215 	struct xdma_intr_handler *ih_tmp;
216 	struct xdma_intr_handler *ih;
217 	xdma_controller_t *xdma;
218 
219 	xdma = xchan->xdma;
220 	KASSERT(xdma != NULL, ("xdma is NULL"));
221 
222 	TAILQ_FOREACH_SAFE(ih, &xchan->ie_handlers, ih_next, ih_tmp) {
223 		TAILQ_REMOVE(&xchan->ie_handlers, ih, ih_next);
224 		free(ih, M_XDMA);
225 	}
226 
227 	return (0);
228 }
229 
230 int
231 xdma_request(xdma_channel_t *xchan, struct xdma_request *req)
232 {
233 	xdma_controller_t *xdma;
234 	int ret;
235 
236 	xdma = xchan->xdma;
237 
238 	KASSERT(xdma != NULL, ("xdma is NULL"));
239 
240 	XCHAN_LOCK(xchan);
241 	ret = XDMA_CHANNEL_REQUEST(xdma->dma_dev, xchan, req);
242 	if (ret != 0) {
243 		device_printf(xdma->dev,
244 		    "%s: Can't request a transfer.\n", __func__);
245 		XCHAN_UNLOCK(xchan);
246 
247 		return (-1);
248 	}
249 	XCHAN_UNLOCK(xchan);
250 
251 	return (0);
252 }
253 
254 int
255 xdma_control(xdma_channel_t *xchan, enum xdma_command cmd)
256 {
257 	xdma_controller_t *xdma;
258 	int ret;
259 
260 	xdma = xchan->xdma;
261 	KASSERT(xdma != NULL, ("xdma is NULL"));
262 
263 	ret = XDMA_CHANNEL_CONTROL(xdma->dma_dev, xchan, cmd);
264 	if (ret != 0) {
265 		device_printf(xdma->dev,
266 		    "%s: Can't process command.\n", __func__);
267 		return (-1);
268 	}
269 
270 	return (0);
271 }
272 
273 void
274 xdma_callback(xdma_channel_t *xchan, xdma_transfer_status_t *status)
275 {
276 	struct xdma_intr_handler *ih_tmp;
277 	struct xdma_intr_handler *ih;
278 	xdma_controller_t *xdma;
279 
280 	xdma = xchan->xdma;
281 	KASSERT(xdma != NULL, ("xdma is NULL"));
282 
283 	TAILQ_FOREACH_SAFE(ih, &xchan->ie_handlers, ih_next, ih_tmp)
284 		if (ih->cb != NULL)
285 			ih->cb(ih->cb_user, status);
286 
287 	if (xchan->flags & XCHAN_TYPE_SG)
288 		xdma_queue_submit(xchan);
289 }
290 
291 #ifdef FDT
292 /*
293  * Notify the DMA driver we have machine-dependent data in FDT.
294  */
295 static int
296 xdma_ofw_md_data(xdma_controller_t *xdma, pcell_t *cells, int ncells)
297 {
298 	uint32_t ret;
299 
300 	ret = XDMA_OFW_MD_DATA(xdma->dma_dev,
301 	    cells, ncells, (void **)&xdma->data);
302 
303 	return (ret);
304 }
305 
306 /*
307  * Allocate xdma controller.
308  */
309 xdma_controller_t *
310 xdma_ofw_get(device_t dev, const char *prop)
311 {
312 	phandle_t node, parent;
313 	xdma_controller_t *xdma;
314 	device_t dma_dev;
315 	pcell_t *cells;
316 	int ncells;
317 	int error;
318 	int ndmas;
319 	int idx;
320 
321 	node = ofw_bus_get_node(dev);
322 	if (node <= 0)
323 		device_printf(dev,
324 		    "%s called on not ofw based device.\n", __func__);
325 
326 	error = ofw_bus_parse_xref_list_get_length(node,
327 	    "dmas", "#dma-cells", &ndmas);
328 	if (error) {
329 		device_printf(dev,
330 		    "%s can't get dmas list.\n", __func__);
331 		return (NULL);
332 	}
333 
334 	if (ndmas == 0) {
335 		device_printf(dev,
336 		    "%s dmas list is empty.\n", __func__);
337 		return (NULL);
338 	}
339 
340 	error = ofw_bus_find_string_index(node, "dma-names", prop, &idx);
341 	if (error != 0) {
342 		device_printf(dev,
343 		    "%s can't find string index.\n", __func__);
344 		return (NULL);
345 	}
346 
347 	error = ofw_bus_parse_xref_list_alloc(node, "dmas", "#dma-cells",
348 	    idx, &parent, &ncells, &cells);
349 	if (error != 0) {
350 		device_printf(dev,
351 		    "%s can't get dma device xref.\n", __func__);
352 		return (NULL);
353 	}
354 
355 	dma_dev = OF_device_from_xref(parent);
356 	if (dma_dev == NULL) {
357 		device_printf(dev,
358 		    "%s can't get dma device.\n", __func__);
359 		return (NULL);
360 	}
361 
362 	xdma = malloc(sizeof(struct xdma_controller),
363 	    M_XDMA, M_WAITOK | M_ZERO);
364 	xdma->dev = dev;
365 	xdma->dma_dev = dma_dev;
366 
367 	TAILQ_INIT(&xdma->channels);
368 
369 	xdma_ofw_md_data(xdma, cells, ncells);
370 	free(cells, M_OFWPROP);
371 
372 	return (xdma);
373 }
374 #endif
375 
376 /*
377  * Free xDMA controller object.
378  */
379 int
380 xdma_put(xdma_controller_t *xdma)
381 {
382 
383 	XDMA_LOCK();
384 
385 	/* Ensure no channels allocated. */
386 	if (!TAILQ_EMPTY(&xdma->channels)) {
387 		device_printf(xdma->dev, "%s: Can't free xDMA\n", __func__);
388 		return (-1);
389 	}
390 
391 	free(xdma->data, M_DEVBUF);
392 	free(xdma, M_XDMA);
393 
394 	XDMA_UNLOCK();
395 
396 	return (0);
397 }
398 
399 static void
400 xdma_init(void)
401 {
402 
403 	sx_init(&xdma_sx, "xDMA");
404 }
405 
406 SYSINIT(xdma, SI_SUB_DRIVERS, SI_ORDER_FIRST, xdma_init, NULL);
407