#include "opt_multiprocessor.h"
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: intc_fdt.c,v 1.9 2026/01/09 16:11:10 skrll Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/evcnt.h>
#include <sys/kmem.h>
#include <sys/intr.h>
#include <dev/fdt/fdtvar.h>
#include <machine/frame.h>
#include <machine/machdep.h>
#include <machine/sysreg.h>
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] = {
"(reserved)",
"Supervisor software",
"Virtual Supervisor software",
"Machine software",
"(reserved)",
"Supervisor timer",
"Virtual Supervisor timer",
"Machine timer",
"(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);
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);
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%u (hart %lu)/%s #%u", cpu_index(sc->sc_ci),
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);