1 /* $NetBSD: plic_fdt.c,v 1.8 2024/08/11 08:29:12 skrll Exp $ */ 2 3 /*- 4 * Copyright (c) 2022 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Portions of this code is derived from software contributed to The NetBSD 8 * Foundation by Simon Burge and Nick Hudson. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: plic_fdt.c,v 1.8 2024/08/11 08:29:12 skrll Exp $"); 34 35 #include <sys/param.h> 36 37 #include <sys/bus.h> 38 #include <sys/cpu.h> 39 #include <sys/device.h> 40 #include <sys/intr.h> 41 42 #include <dev/fdt/fdtvar.h> 43 44 #include <riscv/sysreg.h> 45 #include <riscv/fdt/riscv_fdtvar.h> 46 #include <riscv/dev/plicreg.h> 47 #include <riscv/dev/plicvar.h> 48 49 static const struct device_compatible_entry compat_data[] = { 50 { .compat = "riscv,plic0" }, 51 { .compat = "sifive,plic-1.0.0" }, 52 { .compat = "thead,c900-plic" }, 53 DEVICE_COMPAT_EOL 54 }; 55 56 static void * 57 plic_fdt_intr_establish(device_t dev, u_int *specifier, int ipl, int flags, 58 int (*func)(void *), void *arg, const char *xname) 59 { 60 struct plic_softc * const sc = device_private(dev); 61 struct plic_intrhand *ih; 62 63 /* 1st cell is the interrupt number */ 64 const u_int irq = be32toh(specifier[0]); 65 if (irq > sc->sc_ndev) { 66 aprint_error_dev(dev, "irq %d greater than max irq %d\n", 67 irq, sc->sc_ndev); 68 return NULL; 69 } 70 ih = plic_intr_establish_xname(irq, ipl, 71 (flags & FDT_INTR_MPSAFE) != 0 ? IST_MPSAFE : 0, func, arg, xname); 72 73 return ih; 74 } 75 76 static void 77 plic_fdt_intr_disestablish(device_t dev, void *cookie) 78 { 79 80 plic_intr_disestablish(cookie); 81 } 82 83 84 static bool 85 plic_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) 86 { 87 /* 1st cell is the interrupt number */ 88 const int irq = be32toh(specifier[0]); 89 90 snprintf(buf, buflen, "%s irq %d", device_xname(dev), irq); 91 92 return true; 93 } 94 95 static struct fdtbus_interrupt_controller_func plic_funcs = { 96 .establish = plic_fdt_intr_establish, 97 .disestablish = plic_fdt_intr_disestablish, 98 .intrstr = plic_intrstr, 99 }; 100 101 static int 102 plic_fdt_match(device_t parent, cfdata_t cf, void *aux) 103 { 104 struct fdt_attach_args * const faa = aux; 105 106 return of_compatible_match(faa->faa_phandle, compat_data); 107 } 108 109 110 static void 111 plic_fdt_attach_source(device_t self, int phandle, int context, int xref, 112 int intr_source) 113 { 114 struct plic_softc * const sc = device_private(self); 115 static const struct device_compatible_entry clint_compat_data[] = { 116 { .compat = "riscv,cpu-intc" }, 117 DEVICE_COMPAT_EOL 118 }; 119 120 if (!of_compatible_match(xref, clint_compat_data)) { 121 aprint_error_dev(self, "incompatible CLINT " 122 "for PLIC for context %d\n", context); 123 return; 124 } 125 126 const int cpu_ref = OF_parent(xref); 127 if (!riscv_fdt_cpu_okay(cpu_ref)) { 128 aprint_verbose_dev(self, "inactive HART " 129 "for PLIC for context %d\n", context); 130 return; 131 } 132 133 /* What do we want to pass as arg to plic_intr */ 134 void *ih = fdtbus_intr_establish_xname(phandle, 135 context, IPL_VM, FDT_INTR_MPSAFE, 136 plic_intr, sc, device_xname(self)); 137 if (ih == NULL) { 138 aprint_error_dev(self, "couldn't install " 139 "interrupt handler\n"); 140 } else { 141 char intrstr[128]; 142 bool ok = fdtbus_intr_str(phandle, context, 143 intrstr, sizeof(intrstr)); 144 aprint_verbose_dev(self, "interrupt %s handler " 145 "installed\n", ok ? intrstr : "(unk)"); 146 } 147 148 if (intr_source == IRQ_SUPERVISOR_EXTERNAL) { 149 bus_addr_t hartid; 150 /* get cpuid for the parent node */ 151 fdtbus_get_reg(cpu_ref, 0, &hartid, NULL); 152 153 KASSERT(context <= PLIC_MAX_CONTEXT); 154 sc->sc_context[hartid] = context; 155 aprint_verbose_dev(self, "hart %"PRId64" context %d\n", 156 hartid, context); 157 } 158 } 159 160 161 static void 162 plic_fdt_attach(device_t parent, device_t self, void *aux) 163 { 164 struct plic_softc * const sc = device_private(self); 165 struct fdt_attach_args * const faa = aux; 166 const int phandle = faa->faa_phandle; 167 bus_addr_t addr; 168 bus_size_t size; 169 const uint32_t *data; 170 int error, context, len; 171 172 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 173 aprint_error(": couldn't get registers\n"); 174 return; 175 } 176 177 sc->sc_dev = self; 178 sc->sc_bst = faa->faa_bst; 179 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 180 aprint_error(": couldn't get registers\n"); 181 return; 182 } 183 184 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 185 aprint_error(": couldn't map registers\n"); 186 return; 187 } 188 189 error = of_getprop_uint32(phandle, "riscv,ndev", &sc->sc_ndev); 190 if (error) { 191 aprint_error("couldn't get supported number of external " 192 "interrupts\n"); 193 return; 194 } 195 if (sc->sc_ndev > PLIC_MAX_IRQ) { 196 aprint_error(": invalid number of external interrupts (%u)\n", 197 sc->sc_ndev); 198 return; 199 } 200 aprint_verbose("\n"); 201 202 /* 203 * PLIC context device mappings is documented at 204 * https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/sifive%2Cplic-1.0.0.yaml 205 * We need to walk the "interrupts-extended" property of 206 * and register handlers for the defined contexts. 207 * 208 * XXX 209 * This is usually an abstraction violation to inspect 210 * the current node's properties directly. We do it 211 * in this case because the current binding spec defines 212 * this case. We do a bit of error checking to make 213 * sure all the documented assumptions are correct. 214 */ 215 216 data = fdtbus_get_prop(phandle, "interrupts-extended", &len); 217 if (data == NULL) { 218 aprint_error_dev(self, "couldn't get context data\n"); 219 return; 220 } 221 context = 0; 222 while (len > 0) { 223 const int pphandle = be32toh(data[0]); 224 const int xref = fdtbus_get_phandle_from_native(pphandle); 225 const int intr_source = be32toh(data[1]); 226 uint32_t intr_cells; 227 228 error = of_getprop_uint32(xref, "#interrupt-cells", &intr_cells); 229 if (error) { 230 aprint_error_dev(self, "couldn't get cell length " 231 "for parent CPU for context %d", context); 232 return; 233 } 234 235 if (intr_source != -1) { 236 plic_fdt_attach_source(self, phandle, context, xref, 237 intr_source); 238 } 239 len -= (intr_cells + 1) * 4; 240 data += (intr_cells + 1); 241 context++; 242 } 243 244 aprint_verbose_dev(self, "attaching"); 245 error = plic_attach_common(sc, addr, size); 246 if (error != 0) { 247 return; 248 } 249 250 /* Setup complete, register this FDT bus. */ 251 error = fdtbus_register_interrupt_controller(self, phandle, 252 &plic_funcs); 253 if (error != 0) { 254 aprint_error(": couldn't register with fdtbus: %d\n", error); 255 } 256 } 257 258 CFATTACH_DECL_NEW(plic_fdt, sizeof(struct plic_softc), 259 plic_fdt_match, plic_fdt_attach, NULL, NULL); 260