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