xref: /freebsd/sys/dev/firmware/arm/scmi_virtio.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2023 Arm Ltd
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
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 AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/bus.h>
32 #include <sys/cpu.h>
33 #include <sys/kernel.h>
34 #include <sys/module.h>
35 
36 #include <dev/fdt/simplebus.h>
37 #include <dev/fdt/fdt_common.h>
38 #include <dev/ofw/ofw_bus_subr.h>
39 
40 #include <dev/virtio/scmi/virtio_scmi.h>
41 
42 #include "scmi.h"
43 #include "scmi_protocols.h"
44 
45 #define SCMI_VIRTIO_POLLING_INTERVAL_MS	2
46 
47 struct scmi_virtio_softc {
48 	struct scmi_softc	base;
49 	device_t		virtio_dev;
50 	int			cmdq_sz;
51 	int			evtq_sz;
52 	void			*p2a_pool;
53 };
54 
55 static void	scmi_virtio_callback(void *, unsigned int, void *);
56 static void	*scmi_virtio_p2a_pool_init(device_t, unsigned int);
57 static int	scmi_virtio_transport_init(device_t);
58 static void	scmi_virtio_transport_cleanup(device_t);
59 static int	scmi_virtio_xfer_msg(device_t, struct scmi_msg *);
60 static int	scmi_virtio_poll_msg(device_t, struct scmi_msg *, unsigned int);
61 static void	scmi_virtio_clear_channel(device_t, void *);
62 static int	scmi_virtio_probe(device_t);
63 static int	scmi_virtio_attach(device_t);
64 
65 static void
66 scmi_virtio_callback(void *msg, unsigned int len, void *priv)
67 {
68 	struct scmi_virtio_softc *sc;
69 	uint32_t hdr;
70 
71 	sc = priv;
72 
73 	if (msg == NULL || len < sizeof(hdr)) {
74 		device_printf(sc->virtio_dev, "Ignoring malformed message.\n");
75 		return;
76 	}
77 
78 	hdr = le32toh(*((uint32_t *)msg));
79 	scmi_rx_irq_callback(sc->base.dev, msg, hdr);
80 }
81 
82 static void *
83 scmi_virtio_p2a_pool_init(device_t dev, unsigned int max_msg)
84 {
85 	struct scmi_virtio_softc *sc;
86 	void *pool;
87 	uint8_t *buf;
88 	int i;
89 
90 	sc = device_get_softc(dev);
91 
92 	pool = mallocarray(max_msg, SCMI_MAX_MSG_SIZE, M_DEVBUF,
93 	    M_ZERO | M_WAITOK);
94 
95 	for (i = 0, buf = pool; i < max_msg; i++, buf += SCMI_MAX_MSG_SIZE) {
96 		/* Feed platform with pre-allocated P2A buffers */
97 		virtio_scmi_message_enqueue(sc->virtio_dev,
98 		    VIRTIO_SCMI_CHAN_P2A, buf, 0, SCMI_MAX_MSG_SIZE);
99 	}
100 
101 	device_printf(dev,
102 	    "Fed %d initial P2A buffers to platform.\n", max_msg);
103 
104 	return (pool);
105 }
106 
107 static void
108 scmi_virtio_clear_channel(device_t dev, void *msg)
109 {
110 	struct scmi_virtio_softc *sc;
111 
112 	sc = device_get_softc(dev);
113 	virtio_scmi_message_enqueue(sc->virtio_dev, VIRTIO_SCMI_CHAN_P2A,
114 	    msg, 0, SCMI_MAX_MSG_SIZE);
115 }
116 
117 static int
118 scmi_virtio_transport_init(device_t dev)
119 {
120 	struct scmi_virtio_softc *sc;
121 	int ret;
122 
123 	sc = device_get_softc(dev);
124 
125 	sc->cmdq_sz = virtio_scmi_channel_size_get(sc->virtio_dev,
126 	    VIRTIO_SCMI_CHAN_A2P);
127 	sc->evtq_sz = virtio_scmi_channel_size_get(sc->virtio_dev,
128 	    VIRTIO_SCMI_CHAN_P2A);
129 
130 	if (!sc->cmdq_sz) {
131 		device_printf(dev,
132 		    "VirtIO cmdq virtqueue not found. Aborting.\n");
133 		return (ENXIO);
134 	}
135 
136 	/*
137 	 * P2A buffers are owned by the platform initially; allocate a feed an
138 	 * appropriate number of buffers.
139 	 */
140 	if (sc->evtq_sz != 0) {
141 		sc->p2a_pool = scmi_virtio_p2a_pool_init(dev, sc->evtq_sz);
142 		if (sc->p2a_pool == NULL)
143 			return (ENOMEM);
144 	}
145 
146 	/* Note that setting a callback also enables that VQ interrupts */
147 	ret = virtio_scmi_channel_callback_set(sc->virtio_dev,
148 	    VIRTIO_SCMI_CHAN_A2P, scmi_virtio_callback, sc);
149 	if (ret) {
150 		device_printf(dev, "Failed to set VirtIO cmdq callback.\n");
151 		return (ENXIO);
152 	}
153 
154 	device_printf(dev,
155 	    "VirtIO cmdq virtqueue configured - cmdq_sz:%d\n", sc->cmdq_sz);
156 
157 	/* P2A channel is optional */
158 	if (sc->evtq_sz) {
159 		ret = virtio_scmi_channel_callback_set(sc->virtio_dev,
160 		    VIRTIO_SCMI_CHAN_P2A, scmi_virtio_callback, sc);
161 		if (ret == 0) {
162 			device_printf(dev,
163 			    "VirtIO evtq virtqueue configured - evtq_sz:%d\n",
164 			    sc->evtq_sz);
165 		} else {
166 			device_printf(dev,
167 			    "Failed to set VirtIO evtq callback.Skip.\n");
168 			sc->evtq_sz = 0;
169 		}
170 	}
171 
172 	sc->base.trs_desc.reply_timo_ms = 100;
173 
174 	return (0);
175 }
176 
177 static void
178 scmi_virtio_transport_cleanup(device_t dev)
179 {
180 	struct scmi_virtio_softc *sc;
181 
182 	sc = device_get_softc(dev);
183 
184 	if (sc->evtq_sz != 0) {
185 		virtio_scmi_channel_callback_set(sc->virtio_dev,
186 		    VIRTIO_SCMI_CHAN_P2A, NULL, NULL);
187 		free(sc->p2a_pool, M_DEVBUF);
188 	}
189 
190 	virtio_scmi_channel_callback_set(sc->virtio_dev,
191 	    VIRTIO_SCMI_CHAN_A2P, NULL, NULL);
192 }
193 
194 static int
195 scmi_virtio_xfer_msg(device_t dev, struct scmi_msg *msg)
196 {
197 	struct scmi_virtio_softc *sc;
198 
199 	sc = device_get_softc(dev);
200 
201 	return (virtio_scmi_message_enqueue(sc->virtio_dev,
202 	    VIRTIO_SCMI_CHAN_A2P, &msg->hdr, msg->tx_len, msg->rx_len));
203 }
204 
205 static int
206 scmi_virtio_poll_msg(device_t dev, struct scmi_msg *msg, unsigned int tmo_ms)
207 {
208 	struct scmi_virtio_softc *sc;
209 	device_t vdev;
210 	int tmo_loops;
211 
212 	sc = device_get_softc(dev);
213 	vdev = sc->virtio_dev;
214 
215 	tmo_loops = tmo_ms / SCMI_VIRTIO_POLLING_INTERVAL_MS;
216 	while (tmo_loops-- && atomic_load_acq_int(&msg->poll_done) == 0) {
217 		struct scmi_msg *rx_msg;
218 		void *rx_buf;
219 		uint32_t rx_len;
220 
221 		rx_buf = virtio_scmi_message_poll(vdev, &rx_len);
222 		if (rx_buf == NULL) {
223 			DELAY(SCMI_VIRTIO_POLLING_INTERVAL_MS * 1000);
224 			continue;
225 		}
226 
227 		rx_msg = hdr_to_msg(rx_buf);
228 		rx_msg->rx_len = rx_len;
229 		/* Complete the polling on any poll path */
230 		if (rx_msg->polling)
231 			atomic_store_rel_int(&rx_msg->poll_done, 1);
232 
233 		if (__predict_true(rx_msg == msg))
234 			break;
235 
236 		/*
237 		 * Polling returned an unexpected message: either a message
238 		 * polled by some other thread of execution or a message not
239 		 * supposed to be polled.
240 		 */
241 		device_printf(dev, "POLLED OoO HDR:|%08X| - polling:%d\n",
242 		    rx_msg->hdr, rx_msg->polling);
243 
244 		if (!rx_msg->polling)
245 			scmi_rx_irq_callback(sc->base.dev, rx_msg, rx_msg->hdr);
246 	}
247 
248 	return (tmo_loops > 0 ? 0 : ETIMEDOUT);
249 }
250 
251 static int
252 scmi_virtio_probe(device_t dev)
253 {
254 	if (!ofw_bus_is_compatible(dev, "arm,scmi-virtio"))
255 		return (ENXIO);
256 
257 	if (!ofw_bus_status_okay(dev))
258 		return (ENXIO);
259 
260 	device_set_desc(dev, "ARM SCMI VirtIO Transport driver");
261 
262 	return (BUS_PROBE_DEFAULT);
263 }
264 
265 static int
266 scmi_virtio_attach(device_t dev)
267 {
268 	struct scmi_virtio_softc *sc;
269 
270 	sc = device_get_softc(dev);
271 	sc->virtio_dev = virtio_scmi_transport_get();
272 	if (sc->virtio_dev == NULL)
273 		return (1);
274 
275 	/* When attach fails there is nothing to cleanup*/
276 	return (scmi_attach(dev));
277 }
278 
279 static device_method_t scmi_virtio_methods[] = {
280 	DEVMETHOD(device_probe,		scmi_virtio_probe),
281 	DEVMETHOD(device_attach,	scmi_virtio_attach),
282 
283 	/* SCMI interface */
284 	DEVMETHOD(scmi_transport_init,		scmi_virtio_transport_init),
285 	DEVMETHOD(scmi_transport_cleanup,	scmi_virtio_transport_cleanup),
286 	DEVMETHOD(scmi_xfer_msg,		scmi_virtio_xfer_msg),
287 	DEVMETHOD(scmi_poll_msg,		scmi_virtio_poll_msg),
288 	DEVMETHOD(scmi_clear_channel,		scmi_virtio_clear_channel),
289 
290 	DEVMETHOD_END
291 };
292 
293 DEFINE_CLASS_1(scmi_virtio, scmi_virtio_driver, scmi_virtio_methods,
294     sizeof(struct scmi_virtio_softc), scmi_driver);
295 
296 /* Needs to be after the mmio_sram driver */
297 DRIVER_MODULE(scmi_virtio, simplebus, scmi_virtio_driver, 0, 0);
298 MODULE_VERSION(scmi_virtio, 1);
299