xref: /freebsd/sys/dev/firmware/arm/scmi_virtio.c (revision 103ea03e6713a0f3bb874472fbd9fd81e2278805)
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
scmi_virtio_callback(void * msg,unsigned int len,void * priv)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, len);
80 }
81 
82 static void *
scmi_virtio_p2a_pool_init(device_t dev,unsigned int max_msg)83 scmi_virtio_p2a_pool_init(device_t dev, unsigned int max_msg)
84 {
85 	struct scmi_virtio_softc *sc;
86 	unsigned int max_msg_sz;
87 	void *pool;
88 	uint8_t *buf;
89 	int i;
90 
91 	sc = device_get_softc(dev);
92 	max_msg_sz = SCMI_MAX_MSG_SIZE(&sc->base);
93 	pool = mallocarray(max_msg, max_msg_sz, M_DEVBUF, M_ZERO | M_WAITOK);
94 
95 	for (i = 0, buf = pool; i < max_msg; i++, buf += max_msg_sz) {
96 		/* Feed platform with pre-allocated P2A buffers */
97 		virtio_scmi_message_enqueue(sc->virtio_dev,
98 		    VIRTIO_SCMI_CHAN_P2A, buf, 0, max_msg_sz);
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
scmi_virtio_clear_channel(device_t dev,void * msg)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(&sc->base));
115 }
116 
117 static int
scmi_virtio_transport_init(device_t dev)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
scmi_virtio_transport_cleanup(device_t dev)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
scmi_virtio_xfer_msg(device_t dev,struct scmi_msg * msg)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
scmi_virtio_poll_msg(device_t dev,struct scmi_msg * msg,unsigned int tmo_ms)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 		/* Complete the polling on any poll path */
229 		if (rx_msg->polling)
230 			atomic_store_rel_int(&rx_msg->poll_done, 1);
231 
232 		if (__predict_true(rx_msg == msg))
233 			break;
234 
235 		/*
236 		 * Polling returned an unexpected message: either a message
237 		 * polled by some other thread of execution or a message not
238 		 * supposed to be polled.
239 		 */
240 		device_printf(dev, "POLLED OoO HDR:|%08X| - polling:%d\n",
241 		    rx_msg->hdr, rx_msg->polling);
242 
243 		if (!rx_msg->polling)
244 			scmi_rx_irq_callback(sc->base.dev, rx_msg, rx_msg->hdr, rx_len);
245 	}
246 
247 	return (tmo_loops > 0 ? 0 : ETIMEDOUT);
248 }
249 
250 static int
scmi_virtio_probe(device_t dev)251 scmi_virtio_probe(device_t dev)
252 {
253 	if (!ofw_bus_is_compatible(dev, "arm,scmi-virtio"))
254 		return (ENXIO);
255 
256 	if (!ofw_bus_status_okay(dev))
257 		return (ENXIO);
258 
259 	device_set_desc(dev, "ARM SCMI VirtIO Transport driver");
260 
261 	return (BUS_PROBE_DEFAULT);
262 }
263 
264 static int
scmi_virtio_attach(device_t dev)265 scmi_virtio_attach(device_t dev)
266 {
267 	struct scmi_virtio_softc *sc;
268 
269 	sc = device_get_softc(dev);
270 	sc->virtio_dev = virtio_scmi_transport_get();
271 	if (sc->virtio_dev == NULL)
272 		return (1);
273 
274 	/* When attach fails there is nothing to cleanup*/
275 	return (scmi_attach(dev));
276 }
277 
278 static device_method_t scmi_virtio_methods[] = {
279 	DEVMETHOD(device_probe,		scmi_virtio_probe),
280 	DEVMETHOD(device_attach,	scmi_virtio_attach),
281 
282 	/* SCMI interface */
283 	DEVMETHOD(scmi_transport_init,		scmi_virtio_transport_init),
284 	DEVMETHOD(scmi_transport_cleanup,	scmi_virtio_transport_cleanup),
285 	DEVMETHOD(scmi_xfer_msg,		scmi_virtio_xfer_msg),
286 	DEVMETHOD(scmi_poll_msg,		scmi_virtio_poll_msg),
287 	DEVMETHOD(scmi_clear_channel,		scmi_virtio_clear_channel),
288 
289 	DEVMETHOD_END
290 };
291 
292 DEFINE_CLASS_1(scmi_virtio, scmi_virtio_driver, scmi_virtio_methods,
293     sizeof(struct scmi_virtio_softc), scmi_driver);
294 
295 /* Needs to be after the mmio_sram driver */
296 DRIVER_MODULE(scmi_virtio, simplebus, scmi_virtio_driver, 0, 0);
297 MODULE_VERSION(scmi_virtio, 1);
298