1/*-
2 * Copyright (c) 2016 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/* xDMA memcpy test driver. */
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/conf.h>
36#include <sys/bus.h>
37#include <sys/kernel.h>
38#include <sys/kthread.h>
39#include <sys/module.h>
40#include <sys/lock.h>
41#include <sys/mutex.h>
42#include <sys/resource.h>
43#include <sys/rman.h>
44
45#include <machine/bus.h>
46
47#include <dev/xdma/xdma.h>
48
49#include <dev/fdt/fdt_common.h>
50#include <dev/ofw/ofw_bus.h>
51#include <dev/ofw/ofw_bus_subr.h>
52
53/*
54 * To use this test add a compatible node to your dts, e.g.
55 *
56 * 	xdma_test {
57 *		compatible = "freebsd,xdma-test";
58 *
59 * 		dmas = <&dma 0 0 0xffffffff>;
60 * 		dma-names = "test";
61 *	};
62 */
63
64struct xdmatest_softc {
65	device_t		dev;
66	xdma_controller_t	*xdma;
67	xdma_channel_t		*xchan;
68	void			*ih;
69	struct intr_config_hook config_intrhook;
70	char			*src;
71	char			*dst;
72	uint32_t		len;
73	uintptr_t		src_phys;
74	uintptr_t		dst_phys;
75	bus_dma_tag_t		src_dma_tag;
76	bus_dmamap_t		src_dma_map;
77	bus_dma_tag_t		dst_dma_tag;
78	bus_dmamap_t		dst_dma_map;
79	struct mtx		mtx;
80	int			done;
81	struct proc		*newp;
82	struct xdma_request	req;
83};
84
85static int xdmatest_probe(device_t dev);
86static int xdmatest_attach(device_t dev);
87static int xdmatest_detach(device_t dev);
88
89static int
90xdmatest_intr(void *arg)
91{
92	struct xdmatest_softc *sc;
93
94	sc = arg;
95
96	sc->done = 1;
97
98	mtx_lock(&sc->mtx);
99	wakeup(sc);
100	mtx_unlock(&sc->mtx);
101
102	return (0);
103}
104
105static void
106xdmatest_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
107{
108	bus_addr_t *addr;
109
110	if (err)
111		return;
112
113	addr = (bus_addr_t*)arg;
114	*addr = segs[0].ds_addr;
115}
116
117static int
118xdmatest_alloc_test_memory(struct xdmatest_softc *sc)
119{
120	int err;
121
122	sc->len = (0x1000000 - 8); /* 16mb */
123	sc->len = 8;
124
125	/* Source memory. */
126
127	err = bus_dma_tag_create(
128	    bus_get_dma_tag(sc->dev),
129	    1024, 0,			/* alignment, boundary */
130	    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
131	    BUS_SPACE_MAXADDR,		/* highaddr */
132	    NULL, NULL,			/* filter, filterarg */
133	    sc->len, 1,			/* maxsize, nsegments*/
134	    sc->len, 0,			/* maxsegsize, flags */
135	    NULL, NULL,			/* lockfunc, lockarg */
136	    &sc->src_dma_tag);
137	if (err) {
138		device_printf(sc->dev,
139		    "%s: Can't create bus_dma tag.\n", __func__);
140		return (-1);
141	}
142
143	err = bus_dmamem_alloc(sc->src_dma_tag, (void **)&sc->src,
144	    BUS_DMA_WAITOK | BUS_DMA_COHERENT, &sc->src_dma_map);
145	if (err) {
146		device_printf(sc->dev,
147		    "%s: Can't allocate memory.\n", __func__);
148		return (-1);
149	}
150
151	err = bus_dmamap_load(sc->src_dma_tag, sc->src_dma_map, sc->src,
152	    sc->len, xdmatest_dmamap_cb, &sc->src_phys, BUS_DMA_WAITOK);
153	if (err) {
154		device_printf(sc->dev,
155		    "%s: Can't load DMA map.\n", __func__);
156		return (-1);
157	}
158
159	/* Destination memory. */
160
161	err = bus_dma_tag_create(
162	    bus_get_dma_tag(sc->dev),
163	    1024, 0,			/* alignment, boundary */
164	    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
165	    BUS_SPACE_MAXADDR,		/* highaddr */
166	    NULL, NULL,			/* filter, filterarg */
167	    sc->len, 1,			/* maxsize, nsegments*/
168	    sc->len, 0,			/* maxsegsize, flags */
169	    NULL, NULL,			/* lockfunc, lockarg */
170	    &sc->dst_dma_tag);
171	if (err) {
172		device_printf(sc->dev,
173		    "%s: Can't create bus_dma tag.\n", __func__);
174		return (-1);
175	}
176
177	err = bus_dmamem_alloc(sc->dst_dma_tag, (void **)&sc->dst,
178	    BUS_DMA_WAITOK | BUS_DMA_COHERENT, &sc->dst_dma_map);
179	if (err) {
180		device_printf(sc->dev,
181		    "%s: Can't allocate memory.\n", __func__);
182		return (-1);
183	}
184
185	err = bus_dmamap_load(sc->dst_dma_tag, sc->dst_dma_map, sc->dst,
186	    sc->len, xdmatest_dmamap_cb, &sc->dst_phys, BUS_DMA_WAITOK);
187	if (err) {
188		device_printf(sc->dev,
189		    "%s: Can't load DMA map.\n", __func__);
190		return (-1);
191	}
192
193	return (0);
194}
195
196static int
197xdmatest_test(struct xdmatest_softc *sc)
198{
199	int err;
200	int i;
201
202	/* Get xDMA controller. */
203	sc->xdma = xdma_ofw_get(sc->dev, "test");
204	if (sc->xdma == NULL) {
205		device_printf(sc->dev, "Can't find xDMA controller.\n");
206		return (-1);
207	}
208
209	/* Alloc xDMA virtual channel. */
210	sc->xchan = xdma_channel_alloc(sc->xdma);
211	if (sc->xchan == NULL) {
212		device_printf(sc->dev, "Can't alloc virtual DMA channel.\n");
213		return (-1);
214	}
215
216	/* Setup callback. */
217	err = xdma_setup_intr(sc->xchan, 0, xdmatest_intr, sc, &sc->ih);
218	if (err) {
219		device_printf(sc->dev, "Can't setup xDMA interrupt handler.\n");
220		return (-1);
221	}
222
223	/* We are going to fill memory. */
224	bus_dmamap_sync(sc->src_dma_tag, sc->src_dma_map, BUS_DMASYNC_PREWRITE);
225	bus_dmamap_sync(sc->dst_dma_tag, sc->dst_dma_map, BUS_DMASYNC_PREWRITE);
226
227	/* Fill memory. */
228	for (i = 0; i < sc->len; i++) {
229		sc->src[i] = (i & 0xff);
230		sc->dst[i] = 0;
231	}
232
233	sc->req.type = XR_TYPE_PHYS_ADDR;
234	sc->req.direction = XDMA_MEM_TO_MEM;
235	sc->req.src_addr = sc->src_phys;
236	sc->req.dst_addr = sc->dst_phys;
237	sc->req.src_width = 4;
238	sc->req.dst_width = 4;
239	sc->req.block_len = sc->len;
240	sc->req.block_num = 1;
241
242	err = xdma_request(sc->xchan, sc->src_phys, sc->dst_phys, sc->len);
243	if (err != 0) {
244		device_printf(sc->dev, "Can't configure virtual channel.\n");
245		return (-1);
246	}
247
248	/* Start operation. */
249	xdma_begin(sc->xchan);
250
251	return (0);
252}
253
254static int
255xdmatest_verify(struct xdmatest_softc *sc)
256{
257	int err;
258	int i;
259
260	/* We have memory updated by DMA controller. */
261	bus_dmamap_sync(sc->src_dma_tag, sc->src_dma_map, BUS_DMASYNC_POSTREAD);
262	bus_dmamap_sync(sc->dst_dma_tag, sc->dst_dma_map, BUS_DMASYNC_POSTWRITE);
263
264	for (i = 0; i < sc->len; i++) {
265		if (sc->dst[i] != sc->src[i]) {
266			device_printf(sc->dev,
267			    "%s: Test failed: iter %d\n", __func__, i);
268			return (-1);
269		}
270	}
271
272	err = xdma_channel_free(sc->xchan);
273	if (err != 0) {
274		device_printf(sc->dev,
275		    "%s: Test failed: can't deallocate channel.\n", __func__);
276		return (-1);
277	}
278
279	err = xdma_put(sc->xdma);
280	if (err != 0) {
281		device_printf(sc->dev,
282		    "%s: Test failed: can't deallocate xDMA.\n", __func__);
283		return (-1);
284	}
285
286	return (0);
287}
288
289static void
290xdmatest_worker(void *arg)
291{
292	struct xdmatest_softc *sc;
293	int timeout;
294	int err;
295
296	sc = arg;
297
298	device_printf(sc->dev, "Worker %d started.\n",
299	    device_get_unit(sc->dev));
300
301	while (1) {
302		sc->done = 0;
303
304		mtx_lock(&sc->mtx);
305
306		if (xdmatest_test(sc) != 0) {
307			mtx_unlock(&sc->mtx);
308			device_printf(sc->dev,
309			    "%s: Test failed.\n", __func__);
310			break;
311		}
312
313		timeout = 100;
314
315		do {
316			mtx_sleep(sc, &sc->mtx, 0, "xdmatest_wait", hz);
317		} while (timeout-- && sc->done == 0);
318
319		if (timeout != 0) {
320			err = xdmatest_verify(sc);
321			if (err == 0) {
322				/* Test succeeded. */
323				mtx_unlock(&sc->mtx);
324				continue;
325			}
326		}
327
328		mtx_unlock(&sc->mtx);
329		device_printf(sc->dev,
330		    "%s: Test failed.\n", __func__);
331		break;
332	}
333}
334
335static void
336xdmatest_delayed_attach(void *arg)
337{
338	struct xdmatest_softc *sc;
339
340	sc = arg;
341
342	if (kproc_create(xdmatest_worker, (void *)sc, &sc->newp, 0, 0,
343            "xdmatest_worker") != 0) {
344		device_printf(sc->dev,
345		    "%s: Failed to create worker thread.\n", __func__);
346	}
347
348	config_intrhook_disestablish(&sc->config_intrhook);
349}
350
351static int
352xdmatest_probe(device_t dev)
353{
354
355	if (!ofw_bus_status_okay(dev))
356		return (ENXIO);
357
358	if (!ofw_bus_is_compatible(dev, "freebsd,xdma-test"))
359		return (ENXIO);
360
361	device_set_desc(dev, "xDMA test driver");
362
363	return (BUS_PROBE_DEFAULT);
364}
365
366static int
367xdmatest_attach(device_t dev)
368{
369	struct xdmatest_softc *sc;
370	int err;
371
372	sc = device_get_softc(dev);
373	sc->dev = dev;
374
375	mtx_init(&sc->mtx, device_get_nameunit(dev), "xdmatest", MTX_DEF);
376
377	/* Allocate test memory */
378	err = xdmatest_alloc_test_memory(sc);
379	if (err != 0) {
380		device_printf(sc->dev, "Can't allocate test memory.\n");
381		return (-1);
382	}
383
384	/* We'll run test later, but before / mount. */
385	sc->config_intrhook.ich_func = xdmatest_delayed_attach;
386	sc->config_intrhook.ich_arg = sc;
387	if (config_intrhook_establish(&sc->config_intrhook) != 0)
388		device_printf(dev, "config_intrhook_establish failed\n");
389
390	return (0);
391}
392
393static int
394xdmatest_detach(device_t dev)
395{
396	struct xdmatest_softc *sc;
397
398	sc = device_get_softc(dev);
399
400	bus_dmamap_unload(sc->src_dma_tag, sc->src_dma_map);
401	bus_dmamem_free(sc->src_dma_tag, sc->src, sc->src_dma_map);
402	bus_dma_tag_destroy(sc->src_dma_tag);
403
404	bus_dmamap_unload(sc->dst_dma_tag, sc->dst_dma_map);
405	bus_dmamem_free(sc->dst_dma_tag, sc->dst, sc->dst_dma_map);
406	bus_dma_tag_destroy(sc->dst_dma_tag);
407
408	return (0);
409}
410
411static device_method_t xdmatest_methods[] = {
412	/* Device interface */
413	DEVMETHOD(device_probe,			xdmatest_probe),
414	DEVMETHOD(device_attach,		xdmatest_attach),
415	DEVMETHOD(device_detach,		xdmatest_detach),
416
417	DEVMETHOD_END
418};
419
420static driver_t xdmatest_driver = {
421	"xdmatest",
422	xdmatest_methods,
423	sizeof(struct xdmatest_softc),
424};
425
426DRIVER_MODULE(xdmatest, simplebus, xdmatest_driver, 0, 0);
427