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