#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/cpuset.h>
#include <sys/interrupt.h>
#include <sys/smp.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/cpufunc.h>
#include <machine/frame.h>
#include <machine/intr.h>
#include <dev/fdt/simplebus.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include "pic_if.h"
#define INTC_NIRQS 16
struct intc_irqsrc {
struct intr_irqsrc isrc;
u_int irq;
};
struct intc_softc {
device_t dev;
struct intc_irqsrc isrcs[INTC_NIRQS];
};
static int intc_intr(void *arg);
static phandle_t
intc_ofw_find(device_t dev, uint32_t hartid)
{
phandle_t node;
pcell_t reg;
node = OF_finddevice("/cpus");
if (node == -1) {
device_printf(dev, "Can't find cpus node\n");
return ((phandle_t)-1);
}
for (node = OF_child(node); node != 0; node = OF_peer(node)) {
if (!ofw_bus_node_status_okay(node))
continue;
if (!ofw_bus_node_is_compatible(node, "riscv"))
continue;
if (OF_searchencprop(node, "reg", ®, sizeof(reg)) == -1)
continue;
if (reg == hartid)
break;
}
if (node == 0) {
device_printf(dev, "Can't find boot cpu node\n");
return ((phandle_t)-1);
}
for (node = OF_child(node); node != 0; node = OF_peer(node)) {
if (!ofw_bus_node_status_okay(node))
continue;
if (ofw_bus_node_is_compatible(node, "riscv,cpu-intc"))
break;
}
if (node == 0) {
device_printf(dev,
"Can't find boot cpu local interrupt controller\n");
return ((phandle_t)-1);
}
return (node);
}
static void
intc_identify(driver_t *driver, device_t parent)
{
device_t dev;
phandle_t node;
if (device_find_child(parent, "intc", DEVICE_UNIT_ANY) != NULL)
return;
node = intc_ofw_find(parent, PCPU_GET(hart));
if (node == -1)
return;
dev = simplebus_add_device(parent, node, 0, "intc", -1, NULL);
if (dev == NULL)
device_printf(parent, "Can't add intc child\n");
}
static int
intc_probe(device_t dev)
{
device_set_desc(dev, "RISC-V Local Interrupt Controller");
return (BUS_PROBE_NOWILDCARD);
}
static int
intc_attach(device_t dev)
{
struct intc_irqsrc *isrcs;
struct intc_softc *sc;
struct intr_pic *pic;
const char *name;
phandle_t xref;
u_int flags;
int i, error;
sc = device_get_softc(dev);
sc->dev = dev;
name = device_get_nameunit(dev);
xref = OF_xref_from_node(ofw_bus_get_node(dev));
isrcs = sc->isrcs;
for (i = 0; i < INTC_NIRQS; i++) {
isrcs[i].irq = i;
flags = i == IRQ_SOFTWARE_SUPERVISOR
? INTR_ISRCF_IPI : INTR_ISRCF_PPI;
error = intr_isrc_register(&isrcs[i].isrc, sc->dev, flags,
"%s,%u", name, i);
if (error != 0) {
device_printf(dev, "Can't register interrupt %d\n", i);
return (error);
}
}
pic = intr_pic_register(sc->dev, xref);
if (pic == NULL)
return (ENXIO);
return (intr_pic_claim_root(sc->dev, xref, intc_intr, sc, INTR_ROOT_IRQ));
}
static void
intc_disable_intr(device_t dev, struct intr_irqsrc *isrc)
{
u_int irq;
irq = ((struct intc_irqsrc *)isrc)->irq;
if (irq >= INTC_NIRQS)
panic("%s: Unsupported IRQ %u", __func__, irq);
csr_clear(sie, 1ul << irq);
}
static void
intc_enable_intr(device_t dev, struct intr_irqsrc *isrc)
{
u_int irq;
irq = ((struct intc_irqsrc *)isrc)->irq;
if (irq >= INTC_NIRQS)
panic("%s: Unsupported IRQ %u", __func__, irq);
csr_set(sie, 1ul << irq);
}
static int
intc_map_intr(device_t dev, struct intr_map_data *data,
struct intr_irqsrc **isrcp)
{
struct intr_map_data_fdt *daf;
struct intc_softc *sc;
sc = device_get_softc(dev);
if (data->type != INTR_MAP_DATA_FDT)
return (ENOTSUP);
daf = (struct intr_map_data_fdt *)data;
if (daf->ncells != 1 || daf->cells[0] >= INTC_NIRQS)
return (EINVAL);
*isrcp = &sc->isrcs[daf->cells[0]].isrc;
return (0);
}
static int
intc_setup_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
if (isrc->isrc_flags & INTR_ISRCF_PPI)
CPU_SET(PCPU_GET(cpuid), &isrc->isrc_cpu);
return (0);
}
#ifdef SMP
static void
intc_init_secondary(device_t dev, uint32_t rootnum)
{
struct intc_softc *sc;
struct intr_irqsrc *isrc;
u_int cpu, irq;
sc = device_get_softc(dev);
cpu = PCPU_GET(cpuid);
for (irq = 0; irq < INTC_NIRQS; irq++) {
isrc = &sc->isrcs[irq].isrc;
if (intr_isrc_init_on_cpu(isrc, cpu))
intc_enable_intr(dev, isrc);
}
}
#endif
static int
intc_intr(void *arg)
{
struct trapframe *frame;
struct intc_softc *sc;
uint64_t active_irq;
struct intc_irqsrc *src;
sc = arg;
frame = curthread->td_intr_frame;
KASSERT((frame->tf_scause & SCAUSE_INTR) != 0,
("%s: not an interrupt frame", __func__));
active_irq = frame->tf_scause & SCAUSE_CODE;
if (active_irq >= INTC_NIRQS)
return (FILTER_HANDLED);
src = &sc->isrcs[active_irq];
if (intr_isrc_dispatch(&src->isrc, frame) != 0) {
intc_disable_intr(sc->dev, &src->isrc);
device_printf(sc->dev, "Stray irq %lu disabled\n",
active_irq);
}
return (FILTER_HANDLED);
}
static device_method_t intc_methods[] = {
DEVMETHOD(device_identify, intc_identify),
DEVMETHOD(device_probe, intc_probe),
DEVMETHOD(device_attach, intc_attach),
DEVMETHOD(pic_disable_intr, intc_disable_intr),
DEVMETHOD(pic_enable_intr, intc_enable_intr),
DEVMETHOD(pic_map_intr, intc_map_intr),
DEVMETHOD(pic_setup_intr, intc_setup_intr),
#ifdef SMP
DEVMETHOD(pic_init_secondary, intc_init_secondary),
#endif
DEVMETHOD_END
};
DEFINE_CLASS_0(intc, intc_driver, intc_methods, sizeof(struct intc_softc));
EARLY_DRIVER_MODULE(intc, ofwbus, intc_driver, 0, 0,
BUS_PASS_INTERRUPT + BUS_PASS_ORDER_FIRST);