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