1 /* $NetBSD: intc_fdt.c,v 1.2 2023/06/12 19:04:13 skrll Exp $ */ 2 3 /*- 4 * Copyright (c) 2023 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by 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 "opt_multiprocessor.h" 33 34 #include <sys/cdefs.h> 35 __KERNEL_RCSID(0, "$NetBSD: intc_fdt.c,v 1.2 2023/06/12 19:04:13 skrll Exp $"); 36 37 #include <sys/param.h> 38 39 #include <sys/bus.h> 40 #include <sys/cpu.h> 41 #include <sys/device.h> 42 #include <sys/evcnt.h> 43 #include <sys/kmem.h> 44 #include <sys/intr.h> 45 46 #include <dev/fdt/fdtvar.h> 47 48 #include <machine/frame.h> 49 #include <machine/machdep.h> 50 #include <machine/sysreg.h> 51 52 static const struct device_compatible_entry compat_data[] = { 53 { .compat = "riscv,cpu-intc" }, 54 DEVICE_COMPAT_EOL 55 }; 56 57 58 struct intc_irqhandler; 59 struct intc_irq; 60 struct intc_softc; 61 62 typedef int (*intcih_t)(void *); 63 64 struct intc_irqhandler { 65 struct intc_irq *ih_irq; 66 intcih_t ih_fn; 67 void *ih_arg; 68 TAILQ_ENTRY(intc_irqhandler) 69 ih_next; 70 }; 71 72 struct intc_irq { 73 struct intc_fdt_softc *intr_sc; 74 void *intr_ih; 75 void *intr_arg; 76 int intr_refcnt; 77 int intr_ipl; 78 int intr_source; 79 int intr_istflags; 80 struct evcnt *pcpu_evs; 81 TAILQ_HEAD(, intc_irqhandler) 82 intr_handlers; 83 }; 84 85 86 struct intc_fdt_softc { 87 device_t sc_dev; 88 bus_space_tag_t sc_bst; 89 bus_space_handle_t sc_bsh; 90 91 struct intc_irq *sc_irq[IRQ_NSOURCES]; 92 93 struct evcnt *sc_evs[IRQ_NSOURCES]; 94 95 struct cpu_info *sc_ci; 96 cpuid_t sc_hartid; 97 }; 98 99 static struct intc_fdt_softc *intc_sc; 100 101 102 static void * 103 intc_intr_establish(struct intc_fdt_softc *sc, u_int source, u_int ipl, 104 u_int istflags, int (*func)(void *), void *arg, const char *xname) 105 { 106 if (source > IRQ_NSOURCES) 107 return NULL; 108 109 const device_t dev = sc->sc_dev; 110 struct intc_irq *irq = sc->sc_irq[source]; 111 if (irq == NULL) { 112 irq = kmem_alloc(sizeof(*irq), KM_SLEEP); 113 irq->intr_sc = sc; 114 irq->intr_refcnt = 0; 115 irq->intr_arg = arg; 116 irq->intr_ipl = ipl; 117 irq->intr_istflags = istflags; 118 irq->intr_source = source; 119 TAILQ_INIT(&irq->intr_handlers); 120 sc->sc_irq[source] = irq; 121 } else { 122 if (irq->intr_arg == NULL || arg == NULL) { 123 device_printf(dev, 124 "cannot share irq with NULL-arg handler\n"); 125 return NULL; 126 } 127 if (irq->intr_ipl != ipl) { 128 device_printf(dev, 129 "cannot share irq with different ipl\n"); 130 return NULL; 131 } 132 if (irq->intr_istflags != istflags) { 133 device_printf(dev, 134 "cannot share irq between mpsafe/non-mpsafe\n"); 135 return NULL; 136 } 137 } 138 139 struct intc_irqhandler *irqh = kmem_alloc(sizeof(*irqh), KM_SLEEP); 140 irqh->ih_irq = irq; 141 irqh->ih_fn = func; 142 irqh->ih_arg = arg; 143 144 irq->intr_refcnt++; 145 TAILQ_INSERT_TAIL(&irq->intr_handlers, irqh, ih_next); 146 147 /* 148 * XXX interrupt_distribute(9) assumes that any interrupt 149 * handle can be used as an input to the MD interrupt_distribute 150 * implementationm, so we are forced to return the handle 151 * we got back from intr_establish(). Upshot is that the 152 * input to bcm2835_icu_fdt_disestablish() is ambiguous for 153 * shared IRQs, rendering them un-disestablishable. 154 */ 155 156 return irqh; 157 } 158 159 160 static void * 161 intc_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, 162 int (*func)(void *), void *arg, const char *xname) 163 { 164 struct intc_fdt_softc * const sc = device_private(dev); 165 166 /* 167 * 1st (and only) cell is the interrupt source, e.g. 168 * 1 IRQ_SUPERVISOR_SOFTWARE 169 * 5 IRQ_SUPERVISOR_TIMER 170 * 9 IRQ_SUPERVISOR_EXTERNAL 171 */ 172 173 const u_int source = be32toh(specifier[0]); 174 const u_int mpsafe = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0; 175 176 return intc_intr_establish(sc, source, ipl, mpsafe, func, arg, xname); 177 } 178 179 static void 180 intc_fdt_disestablish(device_t dev, void *ih) 181 { 182 #if 0 183 struct intc_fdt_softc * const sc = device_private(dev); 184 #endif 185 186 } 187 188 static const char * const intc_sources[IRQ_NSOURCES] = { 189 /* Software interrupts */ 190 "(reserved)", 191 "Supervisor software", 192 "Virtual Supervisor software", 193 "Machine software", 194 195 /* Timer interrupts */ 196 "(reserved)", 197 "Supervisor timer", 198 "Virtual Supervisor timer", 199 "Machine timer", 200 201 /* External interrupts */ 202 "(reserved)", 203 "Supervisor external", 204 "Virtual Supervisor external", 205 "Machine external", 206 207 "(reserved)", 208 "Supervisor guest external.", 209 "(reserved)", 210 "(reserved)" 211 }; 212 213 static bool 214 intc_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) 215 { 216 if (!specifier) 217 return false; 218 219 struct intc_fdt_softc * const sc = device_private(dev); 220 if (sc->sc_ci == NULL) 221 return false; 222 223 const u_int source = be32toh(specifier[0]); 224 snprintf(buf, buflen, "cpu%lu/%s #%u", sc->sc_hartid, 225 intc_sources[source], source); 226 227 return true; 228 } 229 230 231 struct fdtbus_interrupt_controller_func intc_fdt_funcs = { 232 .establish = intc_fdt_establish, 233 .disestablish = intc_fdt_disestablish, 234 .intrstr = intc_fdt_intrstr 235 }; 236 237 238 static void 239 intc_intr_handler(struct trapframe *tf, register_t epc, register_t status, 240 register_t cause) 241 { 242 const int ppl = splhigh(); 243 struct cpu_info * const ci = curcpu(); 244 unsigned long pending; 245 int ipl; 246 247 KASSERT(CAUSE_INTERRUPT_P(cause)); 248 249 struct intc_fdt_softc * const sc = intc_sc; 250 251 ci->ci_intr_depth++; 252 ci->ci_data.cpu_nintr++; 253 254 while (ppl < (ipl = splintr(&pending))) { 255 if (pending == 0) 256 continue; 257 258 splx(ipl); 259 260 int source = ffs(pending) - 1; 261 struct intc_irq *irq = sc->sc_irq[source]; 262 KASSERTMSG(irq != NULL, "source %d\n", source); 263 264 if (irq) { 265 struct intc_irqhandler *iih; 266 267 bool mpsafe = 268 source != IRQ_SUPERVISOR_EXTERNAL || 269 (irq->intr_istflags & IST_MPSAFE) != 0; 270 struct clockframe cf = { 271 .cf_epc = epc, 272 .cf_status = status, 273 .cf_intr_depth = ci->ci_intr_depth 274 }; 275 276 if (!mpsafe) { 277 KERNEL_LOCK(1, NULL); 278 } 279 280 TAILQ_FOREACH(iih, &irq->intr_handlers, ih_next) { 281 int handled = 282 iih->ih_fn(iih->ih_arg ? iih->ih_arg : &cf); 283 if (handled) 284 break; 285 } 286 287 if (!mpsafe) { 288 KERNEL_UNLOCK_ONE(NULL); 289 } 290 } 291 splhigh(); 292 } 293 ci->ci_intr_depth--; 294 splx(ppl); 295 } 296 297 298 299 static int 300 intc_match(device_t parent, cfdata_t cf, void *aux) 301 { 302 struct fdt_attach_args * const faa = aux; 303 return of_compatible_match(faa->faa_phandle, compat_data); 304 } 305 306 static void 307 intc_attach(device_t parent, device_t self, void *aux) 308 { 309 const struct fdt_attach_args * const faa = aux; 310 const int phandle = faa->faa_phandle; 311 312 int error = fdtbus_register_interrupt_controller(self, phandle, 313 &intc_fdt_funcs); 314 if (error) { 315 aprint_error(": couldn't register with fdtbus: %d\n", error); 316 return; 317 } 318 319 struct cpu_info * const ci = device_private(parent); 320 if (ci == NULL) { 321 aprint_naive(": disabled\n"); 322 aprint_normal(": disabled\n"); 323 return; 324 } 325 aprint_naive("\n"); 326 aprint_normal(": local interrupt controller\n"); 327 328 struct intc_fdt_softc * const sc = device_private(self); 329 intc_sc = sc; 330 331 riscv_intr_set_handler(intc_intr_handler); 332 333 sc->sc_dev = self; 334 sc->sc_ci = ci; 335 sc->sc_hartid = ci->ci_cpuid; 336 337 intc_intr_establish(sc, IRQ_SUPERVISOR_TIMER, IPL_SCHED, IST_MPSAFE, 338 riscv_timer_intr, NULL, "clock"); 339 #ifdef MULTIPROCESSOR 340 intc_intr_establish(sc, IRQ_SUPERVISOR_SOFTWARE, IPL_HIGH, IST_MPSAFE, 341 riscv_ipi_intr, NULL, "ipi"); 342 #endif 343 } 344 345 CFATTACH_DECL_NEW(intc_fdt, sizeof(struct intc_fdt_softc), 346 intc_match, intc_attach, NULL, NULL); 347