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