1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2022 Ruslan Bukin <br@bsdpad.com>
5 *
6 * This work was supported by Innovate UK project 105694, "Digital Security
7 * by Design (DSbD) Technology Platform Prototype".
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 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/rman.h>
35 #include <sys/kernel.h>
36 #include <sys/module.h>
37
38 #include <machine/bus.h>
39
40 #include <dev/fdt/simplebus.h>
41 #include <dev/fdt/fdt_common.h>
42 #include <dev/ofw/ofw_bus_subr.h>
43
44 #include "arm_doorbell.h"
45
46 #define MHU_CHAN_RX_LP 0x000 /* Low priority channel */
47 #define MHU_CHAN_RX_HP 0x020 /* High priority channel */
48 #define MHU_CHAN_RX_SEC 0x200 /* Secure channel */
49 #define MHU_INTR_STAT 0x00
50 #define MHU_INTR_SET 0x08
51 #define MHU_INTR_CLEAR 0x10
52
53 #define MHU_TX_REG_OFFSET 0x100
54
55 #define DOORBELL_N_CHANNELS 3
56 #define DOORBELL_N_DOORBELLS (DOORBELL_N_CHANNELS * 32)
57
58 struct arm_doorbell dbells[DOORBELL_N_DOORBELLS];
59
60 static struct resource_spec arm_doorbell_spec[] = {
61 { SYS_RES_MEMORY, 0, RF_ACTIVE },
62 { SYS_RES_IRQ, 0, RF_ACTIVE },
63 { SYS_RES_IRQ, 1, RF_ACTIVE },
64 { -1, 0 }
65 };
66
67 struct arm_doorbell_softc {
68 struct resource *res[3];
69 void *lp_intr_cookie;
70 void *hp_intr_cookie;
71 device_t dev;
72 };
73
74 static void
arm_doorbell_lp_intr(void * arg)75 arm_doorbell_lp_intr(void *arg)
76 {
77 struct arm_doorbell_softc *sc;
78 struct arm_doorbell *db;
79 uint32_t reg;
80 int i;
81
82 sc = arg;
83
84 reg = bus_read_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_STAT);
85 for (i = 0; i < 32; i++) {
86 if (reg & (1 << i)) {
87 db = &dbells[i];
88 bus_write_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_CLEAR,
89 (1 << i));
90 if (db->func != NULL)
91 db->func(db->arg);
92 }
93 }
94 }
95
96 static void
arm_doorbell_hp_intr(void * arg)97 arm_doorbell_hp_intr(void *arg)
98 {
99 struct arm_doorbell_softc *sc;
100 struct arm_doorbell *db;
101 uint32_t reg;
102 int i;
103
104 sc = arg;
105
106 reg = bus_read_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_STAT);
107 for (i = 0; i < 32; i++) {
108 if (reg & (1 << i)) {
109 db = &dbells[i];
110 bus_write_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_CLEAR,
111 (1 << i));
112 if (db->func != NULL)
113 db->func(db->arg);
114 }
115 }
116 }
117
118 static int
arm_doorbell_probe(device_t dev)119 arm_doorbell_probe(device_t dev)
120 {
121
122 if (!ofw_bus_is_compatible(dev, "arm,mhu-doorbell"))
123 return (ENXIO);
124
125 if (!ofw_bus_status_okay(dev))
126 return (ENXIO);
127
128 device_set_desc(dev, "ARM MHU Doorbell");
129
130 return (BUS_PROBE_DEFAULT);
131 }
132
133 static int
arm_doorbell_attach(device_t dev)134 arm_doorbell_attach(device_t dev)
135 {
136 struct arm_doorbell_softc *sc;
137 phandle_t node;
138 int error;
139
140 sc = device_get_softc(dev);
141 sc->dev = dev;
142
143 node = ofw_bus_get_node(dev);
144 if (node == -1)
145 return (ENXIO);
146
147 if (bus_alloc_resources(dev, arm_doorbell_spec, sc->res) != 0) {
148 device_printf(dev, "Can't allocate resources for device.\n");
149 return (ENXIO);
150 }
151
152 /* Setup interrupt handlers. */
153 error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE,
154 NULL, arm_doorbell_lp_intr, sc, &sc->lp_intr_cookie);
155 if (error != 0) {
156 device_printf(dev, "Can't setup LP interrupt handler.\n");
157 bus_release_resources(dev, arm_doorbell_spec, sc->res);
158 return (ENXIO);
159 }
160
161 error = bus_setup_intr(dev, sc->res[2], INTR_TYPE_MISC | INTR_MPSAFE,
162 NULL, arm_doorbell_hp_intr, sc, &sc->hp_intr_cookie);
163 if (error != 0) {
164 device_printf(dev, "Can't setup HP interrupt handler.\n");
165 bus_release_resources(dev, arm_doorbell_spec, sc->res);
166 return (ENXIO);
167 }
168
169 OF_device_register_xref(OF_xref_from_node(node), dev);
170
171 return (0);
172 }
173
174 static int
arm_doorbell_detach(device_t dev)175 arm_doorbell_detach(device_t dev)
176 {
177
178 return (EBUSY);
179 }
180
181 struct arm_doorbell *
arm_doorbell_ofw_get(device_t dev,const char * name)182 arm_doorbell_ofw_get(device_t dev, const char *name)
183 {
184 phandle_t node, parent;
185 struct arm_doorbell *db;
186 device_t db_dev;
187 pcell_t *cells;
188 int nmboxes;
189 int ncells;
190 int idx;
191 int db_id;
192 int error;
193 int chan;
194
195 node = ofw_bus_get_node(dev);
196
197 error = ofw_bus_parse_xref_list_get_length(node, "mboxes",
198 "#mbox-cells", &nmboxes);
199 if (error) {
200 device_printf(dev, "%s can't get mboxes list.\n", __func__);
201 return (NULL);
202 }
203
204 if (nmboxes == 0) {
205 device_printf(dev, "%s mbox list is empty.\n", __func__);
206 return (NULL);
207 }
208
209 error = ofw_bus_find_string_index(node, "mbox-names", name, &idx);
210 if (error != 0) {
211 device_printf(dev, "%s can't find string index.\n",
212 __func__);
213 return (NULL);
214 }
215
216 error = ofw_bus_parse_xref_list_alloc(node, "mboxes", "#mbox-cells",
217 idx, &parent, &ncells, &cells);
218 if (error != 0) {
219 device_printf(dev, "%s can't get mbox device xref\n",
220 __func__);
221 return (NULL);
222 }
223
224 if (ncells != 2) {
225 device_printf(dev, "Unexpected data size.\n");
226 OF_prop_free(cells);
227 return (NULL);
228 }
229
230 db_dev = OF_device_from_xref(parent);
231 if (db_dev == NULL) {
232 device_printf(dev, "%s: Can't get arm_doorbell device\n",
233 __func__);
234 OF_prop_free(cells);
235 return (NULL);
236 }
237
238 chan = cells[0];
239 if (chan >= DOORBELL_N_CHANNELS) {
240 device_printf(dev, "Unexpected channel number.\n");
241 OF_prop_free(cells);
242 return (NULL);
243 }
244
245 db_id = cells[1];
246 if (db_id >= 32) {
247 device_printf(dev, "Unexpected channel bit.\n");
248 OF_prop_free(cells);
249 return (NULL);
250 }
251
252 db = &dbells[chan * db_id];
253 db->dev = dev;
254 db->db_dev = db_dev;
255 db->chan = chan;
256 db->db = db_id;
257
258 OF_prop_free(cells);
259
260 return (db);
261 }
262
263 void
arm_doorbell_set(struct arm_doorbell * db)264 arm_doorbell_set(struct arm_doorbell *db)
265 {
266 struct arm_doorbell_softc *sc;
267 uint32_t offset;
268
269 sc = device_get_softc(db->db_dev);
270
271 switch (db->chan) {
272 case 0:
273 offset = MHU_CHAN_RX_LP;
274 break;
275 case 1:
276 offset = MHU_CHAN_RX_HP;
277 break;
278 case 2:
279 offset = MHU_CHAN_RX_SEC;
280 break;
281 default:
282 panic("not reached");
283 };
284
285 offset |= MHU_TX_REG_OFFSET;
286
287 bus_write_4(sc->res[0], offset + MHU_INTR_SET, (1 << db->db));
288 }
289
290 int
arm_doorbell_get(struct arm_doorbell * db)291 arm_doorbell_get(struct arm_doorbell *db)
292 {
293 struct arm_doorbell_softc *sc;
294 uint32_t offset;
295 uint32_t reg;
296
297 sc = device_get_softc(db->db_dev);
298
299 switch (db->chan) {
300 case 0:
301 offset = MHU_CHAN_RX_LP;
302 break;
303 case 1:
304 offset = MHU_CHAN_RX_HP;
305 break;
306 case 2:
307 offset = MHU_CHAN_RX_SEC;
308 break;
309 default:
310 panic("not reached");
311 };
312
313 reg = bus_read_4(sc->res[0], offset + MHU_INTR_STAT);
314 if (reg & (1 << db->db)) {
315 bus_write_4(sc->res[0], offset + MHU_INTR_CLEAR,
316 (1 << db->db));
317 return (1);
318 }
319
320 return (0);
321 }
322
323 void
arm_doorbell_set_handler(struct arm_doorbell * db,void (* func)(void *),void * arg)324 arm_doorbell_set_handler(struct arm_doorbell *db, void (*func)(void *),
325 void *arg)
326 {
327
328 db->func = func;
329 db->arg = arg;
330 }
331
332 static device_method_t arm_doorbell_methods[] = {
333 DEVMETHOD(device_probe, arm_doorbell_probe),
334 DEVMETHOD(device_attach, arm_doorbell_attach),
335 DEVMETHOD(device_detach, arm_doorbell_detach),
336 DEVMETHOD_END
337 };
338
339 DEFINE_CLASS_1(arm_doorbell, arm_doorbell_driver, arm_doorbell_methods,
340 sizeof(struct arm_doorbell_softc), simplebus_driver);
341
342 EARLY_DRIVER_MODULE(arm_doorbell, simplebus, arm_doorbell_driver, 0, 0,
343 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
344 MODULE_VERSION(arm_doorbell, 1);
345