/* $NetBSD: intc_fdt.c,v 1.6 2024/01/21 08:48:21 skrll Exp $ */ /*- * Copyright (c) 2023 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Nick Hudson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "opt_multiprocessor.h" #include __KERNEL_RCSID(0, "$NetBSD: intc_fdt.c,v 1.6 2024/01/21 08:48:21 skrll Exp $"); #include #include #include #include #include #include #include #include #include #include #include static const struct device_compatible_entry compat_data[] = { { .compat = "riscv,cpu-intc" }, DEVICE_COMPAT_EOL }; struct intc_irqhandler; struct intc_irq; typedef int (*intcih_t)(void *); struct intc_irqhandler { struct intc_irq *ih_irq; intcih_t ih_fn; void *ih_arg; TAILQ_ENTRY(intc_irqhandler) ih_next; }; struct intc_irq { struct intc_fdt_softc *intr_sc; void *intr_ih; void *intr_arg; int intr_refcnt; int intr_ipl; int intr_source; int intr_istflags; struct evcnt *pcpu_evs; TAILQ_HEAD(, intc_irqhandler) intr_handlers; }; struct intc_fdt_softc { device_t sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; struct intc_irq *sc_irq[IRQ_NSOURCES]; struct evcnt sc_evs[IRQ_NSOURCES]; struct cpu_info *sc_ci; cpuid_t sc_hartid; }; static const char * const intc_sources[IRQ_NSOURCES] = { /* Software interrupts */ "(reserved)", "Supervisor software", "Virtual Supervisor software", "Machine software", /* Timer interrupts */ "(reserved)", "Supervisor timer", "Virtual Supervisor timer", "Machine timer", /* External interrupts */ "(reserved)", "Supervisor external", "Virtual Supervisor external", "Machine external", "(reserved)", "Supervisor guest external.", "(reserved)", "(reserved)" }; #ifndef MULTIPROCESSOR struct intc_fdt_softc *intc_sc; #endif static inline struct intc_fdt_softc * intc_getsc(struct cpu_info *ci) { #ifdef MULTIPROCESSOR return ci->ci_intcsoftc; #else return intc_sc; #endif } static void * intc_intr_establish(struct intc_fdt_softc *sc, u_int source, u_int ipl, u_int istflags, int (*func)(void *), void *arg, const char *xname) { if (source > IRQ_NSOURCES) return NULL; const device_t dev = sc->sc_dev; struct intc_irq *irq = sc->sc_irq[source]; if (irq == NULL) { irq = kmem_alloc(sizeof(*irq), KM_SLEEP); irq->intr_sc = sc; irq->intr_refcnt = 0; irq->intr_arg = arg; irq->intr_ipl = ipl; irq->intr_istflags = istflags; irq->intr_source = source; TAILQ_INIT(&irq->intr_handlers); sc->sc_irq[source] = irq; evcnt_attach_dynamic(&sc->sc_evs[source], EVCNT_TYPE_INTR, NULL, device_xname(sc->sc_dev), intc_sources[source]); } else { if (irq->intr_arg == NULL || arg == NULL) { device_printf(dev, "cannot share irq with NULL-arg handler\n"); return NULL; } if (irq->intr_ipl != ipl) { device_printf(dev, "cannot share irq with different ipl\n"); return NULL; } if (irq->intr_istflags != istflags) { device_printf(dev, "cannot share irq between mpsafe/non-mpsafe\n"); return NULL; } } struct intc_irqhandler *irqh = kmem_alloc(sizeof(*irqh), KM_SLEEP); irqh->ih_irq = irq; irqh->ih_fn = func; irqh->ih_arg = arg; irq->intr_refcnt++; TAILQ_INSERT_TAIL(&irq->intr_handlers, irqh, ih_next); /* * XXX interrupt_distribute(9) assumes that any interrupt * handle can be used as an input to the MD interrupt_distribute * implementationm, so we are forced to return the handle * we got back from intr_establish(). Upshot is that the * input to bcm2835_icu_fdt_disestablish() is ambiguous for * shared IRQs, rendering them un-disestablishable. */ return irqh; } static void * intc_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, int (*func)(void *), void *arg, const char *xname) { struct intc_fdt_softc * const sc = device_private(dev); /* * 1st (and only) cell is the interrupt source, e.g. * 1 IRQ_SUPERVISOR_SOFTWARE * 5 IRQ_SUPERVISOR_TIMER * 9 IRQ_SUPERVISOR_EXTERNAL */ const u_int source = be32toh(specifier[0]); const u_int mpsafe = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0; return intc_intr_establish(sc, source, ipl, mpsafe, func, arg, xname); } static void intc_fdt_disestablish(device_t dev, void *ih) { #if 0 struct intc_fdt_softc * const sc = device_private(dev); #endif } static bool intc_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) { if (!specifier) return false; struct intc_fdt_softc * const sc = device_private(dev); if (sc->sc_ci == NULL) return false; const u_int source = be32toh(specifier[0]); snprintf(buf, buflen, "cpu%lu/%s #%u", sc->sc_hartid, intc_sources[source], source); return true; } struct fdtbus_interrupt_controller_func intc_fdt_funcs = { .establish = intc_fdt_establish, .disestablish = intc_fdt_disestablish, .intrstr = intc_fdt_intrstr }; static void intc_intr_handler(struct trapframe *tf, register_t epc, register_t status, register_t cause) { const int ppl = splhigh(); struct cpu_info * const ci = curcpu(); unsigned long pending; int ipl; KASSERT(CAUSE_INTERRUPT_P(cause)); struct intc_fdt_softc * const sc = intc_getsc(ci); ci->ci_intr_depth++; ci->ci_data.cpu_nintr++; while (ppl < (ipl = splintr(&pending))) { if (pending == 0) continue; splx(ipl); int source = ffs(pending) - 1; struct intc_irq *irq = sc->sc_irq[source]; sc->sc_evs[source].ev_count++; KASSERTMSG(irq != NULL, "source %d\n", source); if (irq) { struct intc_irqhandler *iih; bool mpsafe = source != IRQ_SUPERVISOR_EXTERNAL || (irq->intr_istflags & IST_MPSAFE) != 0; struct clockframe cf = { .cf_epc = epc, .cf_status = status, .cf_intr_depth = ci->ci_intr_depth }; if (!mpsafe) { KERNEL_LOCK(1, NULL); } TAILQ_FOREACH(iih, &irq->intr_handlers, ih_next) { int handled = iih->ih_fn(iih->ih_arg ? iih->ih_arg : &cf); if (handled) break; } if (!mpsafe) { KERNEL_UNLOCK_ONE(NULL); } } splhigh(); } ci->ci_intr_depth--; splx(ppl); } static int intc_match(device_t parent, cfdata_t cf, void *aux) { struct fdt_attach_args * const faa = aux; return of_compatible_match(faa->faa_phandle, compat_data); } static void intc_attach(device_t parent, device_t self, void *aux) { const struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; int error = fdtbus_register_interrupt_controller(self, phandle, &intc_fdt_funcs); if (error) { aprint_error(": couldn't register with fdtbus: %d\n", error); return; } struct cpu_info * const ci = device_private(parent); if (ci == NULL) { aprint_naive(": disabled\n"); aprint_normal(": disabled\n"); return; } aprint_naive("\n"); aprint_normal(": local interrupt controller\n"); struct intc_fdt_softc * const sc = device_private(self); riscv_intr_set_handler(intc_intr_handler); sc->sc_dev = self; sc->sc_ci = ci; sc->sc_hartid = ci->ci_cpuid; intc_intr_establish(sc, IRQ_SUPERVISOR_TIMER, IPL_SCHED, IST_MPSAFE, riscv_timer_intr, NULL, "clock"); #ifdef MULTIPROCESSOR ci->ci_intcsoftc = sc; intc_intr_establish(sc, IRQ_SUPERVISOR_SOFTWARE, IPL_HIGH, IST_MPSAFE, riscv_ipi_intr, NULL, "ipi"); #else intc_sc = sc; #endif } CFATTACH_DECL_NEW(intc_fdt, sizeof(struct intc_fdt_softc), intc_match, intc_attach, NULL, NULL);