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