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 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 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 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 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 175 arm_doorbell_detach(device_t dev) 176 { 177 178 return (EBUSY); 179 } 180 181 struct arm_doorbell * 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 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 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 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