1 /*-
2  * Copyright (c) 2014-2015 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 /*
32  * BERI interface for Virtio MMIO bus.
33  *
34  * This driver provides interrupt-engine for software-implemented
35  * Virtio MMIO backend.
36  */
37 
38 #include <sys/cdefs.h>
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/bus.h>
42 #include <sys/kernel.h>
43 #include <sys/module.h>
44 #include <sys/malloc.h>
45 #include <sys/rman.h>
46 #include <sys/timeet.h>
47 #include <sys/timetc.h>
48 #include <sys/watchdog.h>
49 
50 #include <machine/bus.h>
51 #include <machine/fdt.h>
52 #include <machine/cpu.h>
53 #include <machine/cache.h>
54 
55 #include <dev/fdt/fdt_common.h>
56 #include <dev/ofw/openfirm.h>
57 #include <dev/ofw/ofw_bus.h>
58 #include <dev/ofw/ofw_bus_subr.h>
59 
60 #include <dev/beri/virtio/virtio_mmio_platform.h>
61 #include <dev/virtio/mmio/virtio_mmio.h>
62 #include <dev/altera/pio/pio.h>
63 
64 #include "virtio_mmio_if.h"
65 #include "pio_if.h"
66 
67 static void platform_intr(void *arg);
68 
69 struct virtio_mmio_platform_softc {
70 	struct resource		*res[1];
71 	void			*ih;
72 	bus_space_tag_t		bst;
73 	bus_space_handle_t	bsh;
74 	device_t		dev;
75 	void			(*intr_handler)(void *);
76 	void			*ih_user;
77 	device_t		pio_recv;
78 	device_t		pio_send;
79 	int			use_pio;
80 };
81 
82 static int
83 setup_pio(struct virtio_mmio_platform_softc *sc, char *name, device_t *dev)
84 {
85 	phandle_t pio_node;
86 	struct fdt_ic *ic;
87 	phandle_t xref;
88 	phandle_t node;
89 
90 	if ((node = ofw_bus_get_node(sc->dev)) == -1)
91 		return (ENXIO);
92 
93 	if (OF_searchencprop(node, name, &xref,
94 		sizeof(xref)) == -1) {
95 		return (ENXIO);
96 	}
97 
98 	pio_node = OF_node_from_xref(xref);
99 	SLIST_FOREACH(ic, &fdt_ic_list_head, fdt_ics) {
100 		if (ic->iph == pio_node) {
101 			*dev = ic->dev;
102 			PIO_CONFIGURE(*dev, PIO_OUT_ALL,
103 					PIO_UNMASK_ALL);
104 			return (0);
105 		}
106 	}
107 
108 	return (ENXIO);
109 }
110 
111 static int
112 virtio_mmio_platform_probe(device_t dev)
113 {
114 
115 	if (!ofw_bus_status_okay(dev))
116 		return (ENXIO);
117 
118 	if (!ofw_bus_is_compatible(dev, "beri,virtio_mmio_platform"))
119 		return (ENXIO);
120 
121 	device_set_desc(dev, "Virtio MMIO platform");
122 	return (BUS_PROBE_DEFAULT);
123 }
124 
125 static int
126 virtio_mmio_platform_attach(device_t dev)
127 {
128 	struct virtio_mmio_platform_softc *sc;
129 	struct fdt_ic *fic;
130 	phandle_t node;
131 
132 	sc = device_get_softc(dev);
133 	sc->dev = dev;
134 	sc->use_pio = 1;
135 
136 	if ((setup_pio(sc, "pio-send", &sc->pio_send) != 0) ||
137 	    (setup_pio(sc, "pio-recv", &sc->pio_recv) != 0))
138 		sc->use_pio = 0;
139 
140 	if ((node = ofw_bus_get_node(sc->dev)) == -1)
141 		return (ENXIO);
142 
143 	fic = malloc(sizeof(*fic), M_DEVBUF, M_WAITOK|M_ZERO);
144 	fic->iph = node;
145 	fic->dev = dev;
146 	SLIST_INSERT_HEAD(&fdt_ic_list_head, fic, fdt_ics);
147 
148 	return (0);
149 }
150 
151 static int
152 platform_prewrite(device_t dev, size_t offset, int val)
153 {
154 	struct virtio_mmio_platform_softc *sc;
155 
156 	sc = device_get_softc(dev);
157 
158 	switch (offset) {
159 	case (VIRTIO_MMIO_QUEUE_NOTIFY):
160 		mips_dcache_wbinv_all();
161 		break;
162 	default:
163 		break;
164 	}
165 
166 	return (0);
167 }
168 
169 static int
170 platform_note(device_t dev, size_t offset, int val)
171 {
172 	struct virtio_mmio_platform_softc *sc;
173 	int note;
174 	int i;
175 
176 	sc = device_get_softc(dev);
177 
178 	switch (offset) {
179 	case (VIRTIO_MMIO_QUEUE_NOTIFY):
180 		if (val == 0)
181 			note = Q_NOTIFY;
182 		else if (val == 1)
183 			note = Q_NOTIFY1;
184 		else
185 			note = 0;
186 		break;
187 	case (VIRTIO_MMIO_QUEUE_PFN):
188 		note = Q_PFN;
189 		break;
190 	case (VIRTIO_MMIO_QUEUE_SEL):
191 		note = Q_SEL;
192 		break;
193 	default:
194 		note = 0;
195 	}
196 
197 	if (note) {
198 		mips_dcache_wbinv_all();
199 
200 		if (!sc->use_pio)
201 			return (0);
202 
203 		PIO_SET(sc->pio_send, note, 1);
204 
205 		/*
206 		 * Wait until host ack the request.
207 		 * Usually done within few cycles.
208 		 * TODO: bad
209 		 */
210 
211 		for (i = 100; i > 0; i--) {
212 			if (PIO_READ(sc->pio_send) == 0)
213 				break;
214 		}
215 
216 		if (i == 0)
217 			device_printf(sc->dev, "Warning: host busy\n");
218 	}
219 
220 	return (0);
221 }
222 
223 static void
224 platform_intr(void *arg)
225 {
226 	struct virtio_mmio_platform_softc *sc;
227 	int reg;
228 
229 	sc = arg;
230 
231 	if (sc->use_pio) {
232 		/* Read pending */
233 		reg = PIO_READ(sc->pio_recv);
234 
235 		/* Ack */
236 		PIO_SET(sc->pio_recv, reg, 0);
237 	}
238 
239 	/* Writeback, invalidate cache */
240 	mips_dcache_wbinv_all();
241 
242 	if (sc->intr_handler != NULL)
243 		sc->intr_handler(sc->ih_user);
244 }
245 
246 static int
247 platform_setup_intr(device_t dev, device_t mmio_dev,
248 			void *intr_handler, void *ih_user)
249 {
250 	struct virtio_mmio_platform_softc *sc;
251 	int rid;
252 
253 	sc = device_get_softc(dev);
254 
255 	sc->intr_handler = intr_handler;
256 	sc->ih_user = ih_user;
257 
258 	if (sc->use_pio) {
259 		PIO_SETUP_IRQ(sc->pio_recv, platform_intr, sc);
260 		return (0);
261 	}
262 
263 	rid = 0;
264 	sc->res[0] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
265 		RF_ACTIVE);
266 	if (!sc->res[0]) {
267 		device_printf(dev, "Can't allocate interrupt\n");
268 		return (ENXIO);
269 	}
270 
271 	if (bus_setup_intr(dev, sc->res[0], INTR_TYPE_MISC | INTR_MPSAFE,
272 		NULL, platform_intr, sc, &sc->ih)) {
273 		device_printf(dev, "Can't setup the interrupt\n");
274 		return (ENXIO);
275 	}
276 
277 	return (0);
278 }
279 
280 static int
281 platform_poll(device_t dev)
282 {
283 
284 	mips_dcache_wbinv_all();
285 
286 	return (0);
287 }
288 
289 static device_method_t virtio_mmio_platform_methods[] = {
290 	DEVMETHOD(device_probe,		virtio_mmio_platform_probe),
291 	DEVMETHOD(device_attach,	virtio_mmio_platform_attach),
292 
293 	/* virtio_mmio_if.h */
294 	DEVMETHOD(virtio_mmio_prewrite,		platform_prewrite),
295 	DEVMETHOD(virtio_mmio_note,		platform_note),
296 	DEVMETHOD(virtio_mmio_poll,		platform_poll),
297 	DEVMETHOD(virtio_mmio_setup_intr,	platform_setup_intr),
298 	DEVMETHOD_END
299 };
300 
301 static driver_t virtio_mmio_platform_driver = {
302 	"virtio_mmio_platform",
303 	virtio_mmio_platform_methods,
304 	sizeof(struct virtio_mmio_platform_softc),
305 };
306 
307 DRIVER_MODULE(virtio_mmio_platform, simplebus, virtio_mmio_platform_driver,
308     0, 0);
309