#include <sys/param.h>
#include <sys/systm.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/evcount.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/cpu.h>
#include <machine/sbi.h>
#include "riscv64/dev/riscv_cpu_intc.h"
#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>
#define PLIC_MAX_IRQS 1024
#define PLIC_PRIORITY_BASE 0x000000U
#define PLIC_ENABLE_BASE 0x002000U
#define PLIC_ENABLE_STRIDE 0x80U
#define IRQ_ENABLE 1
#define IRQ_DISABLE 0
#define PLIC_CONTEXT_BASE 0x200000U
#define PLIC_CONTEXT_STRIDE 0x1000U
#define PLIC_CONTEXT_THRESHOLD 0x0U
#define PLIC_CONTEXT_CLAIM 0x4U
#define PLIC_PRIORITY(n) (PLIC_PRIORITY_BASE + (n) * sizeof(uint32_t))
#define PLIC_ENABLE(sc, n, h) \
(sc->sc_contexts[h].enable_offset + ((n) / 32) * sizeof(uint32_t))
#define PLIC_THRESHOLD(sc, h) \
(sc->sc_contexts[h].context_offset + PLIC_CONTEXT_THRESHOLD)
#define PLIC_CLAIM(sc, h) \
(sc->sc_contexts[h].context_offset + PLIC_CONTEXT_CLAIM)
struct plic_intrhand {
TAILQ_ENTRY(plic_intrhand) ih_list;
int (*ih_func)(void *);
void *ih_arg;
int ih_ipl;
int ih_flags;
int ih_irq;
struct evcount ih_count;
char *ih_name;
struct cpu_info *ih_ci;
};
struct plic_irqsrc {
TAILQ_HEAD(, plic_intrhand) is_list;
int is_irq_max;
int is_irq_min;
};
struct plic_context {
bus_size_t enable_offset;
bus_size_t context_offset;
};
struct plic_softc {
struct device sc_dev;
int sc_node;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
struct plic_irqsrc *sc_isrcs;
struct plic_context sc_contexts[MAXCPUS];
int sc_ndev;
struct interrupt_controller sc_intc;
};
struct plic_softc *plic = NULL;
int plic_match(struct device *, void *, void *);
void plic_attach(struct device *, struct device *, void *);
int plic_irq_handler(void *);
int plic_irq_dispatch(uint32_t, void *);
void *plic_intr_establish(int, int, struct cpu_info *,
int (*)(void *), void *, char *);
void *plic_intr_establish_fdt(void *, int *, int, struct cpu_info *,
int (*)(void *), void *, char *);
void plic_intr_disestablish(void *);
void plic_intr_route(void *, int, struct cpu_info *);
void plic_intr_barrier(void *);
void plic_splx(int);
int plic_spllower(int);
int plic_splraise(int);
void plic_setipl(int);
void plic_calc_mask(void);
int plic_get_cpuid(int);
void plic_set_priority(int, uint32_t);
void plic_set_threshold(int, uint32_t);
void plic_intr_route_grid(int, int, int);
void plic_intr_enable_with_pri(int, uint32_t, int);
void plic_intr_disable(int, int);
const struct cfattach plic_ca = {
sizeof(struct plic_softc), plic_match, plic_attach,
};
struct cfdriver plic_cd = {
NULL, "plic", DV_DULL
};
int plic_attached = 0;
int
plic_match(struct device *parent, void *cfdata, void *aux)
{
struct fdt_attach_args *faa = aux;
if (plic_attached)
return 0;
return (OF_is_compatible(faa->fa_node, "riscv,plic0") ||
OF_is_compatible(faa->fa_node, "sifive,plic-1.0.0") ||
OF_is_compatible(faa->fa_node, "thead,c900-plic"));
}
void
plic_attach(struct device *parent, struct device *dev, void *aux)
{
struct plic_softc *sc;
struct fdt_attach_args *faa;
uint32_t *cells;
uint32_t irq;
int cpu;
int node;
int len;
int ncell;
int context;
int i;
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
if (plic_attached)
return;
plic = sc = (struct plic_softc *)dev;
faa = (struct fdt_attach_args *)aux;
if (faa->fa_nreg < 1)
return;
sc->sc_node = node = faa->fa_node;
sc->sc_iot = faa->fa_iot;
sc->sc_ndev = OF_getpropint(faa->fa_node, "riscv,ndev", -1);
if (sc->sc_ndev < 0) {
printf(": unable to resolve number of devices\n");
return;
}
if (sc->sc_ndev >= PLIC_MAX_IRQS) {
printf(": invalid ndev (%d)\n", sc->sc_ndev);
return;
}
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh))
panic("%s: bus_space_map failed!", __func__);
sc->sc_isrcs = mallocarray(PLIC_MAX_IRQS, sizeof(struct plic_irqsrc),
M_DEVBUF, M_ZERO | M_NOWAIT);
for (irq = 1; irq <= sc->sc_ndev; irq++) {
TAILQ_INIT(&sc->sc_isrcs[irq].is_list);
plic_set_priority(irq, 0);
}
len = OF_getproplen(node, "interrupts-extended");
if (len <= 0) {
printf(": could not find interrupts-extended\n");
return;
}
cells = malloc(len, M_TEMP, M_WAITOK);
ncell = len / sizeof(*cells);
if (OF_getpropintarray(node, "interrupts-extended", cells, len) < 0) {
printf(": failed to read interrupts-extended\n");
free(cells, M_TEMP, len);
return;
}
for (i = 0, context = 0; i < ncell; i += 2, context++) {
if (cells[i + 1] != IRQ_EXTERNAL_SUPERVISOR)
continue;
cpu = plic_get_cpuid(OF_getnodebyphandle(cells[i]));
if (cpu < 0)
continue;
sc->sc_contexts[cpu].enable_offset = PLIC_ENABLE_BASE +
context * PLIC_ENABLE_STRIDE;
sc->sc_contexts[cpu].context_offset = PLIC_CONTEXT_BASE +
context * PLIC_CONTEXT_STRIDE;
}
free(cells, M_TEMP, len);
CPU_INFO_FOREACH(cii, ci) {
plic_set_threshold(ci->ci_cpuid, 0);
}
plic_setipl(IPL_HIGH);
plic_calc_mask();
riscv_intc_intr_establish(IRQ_EXTERNAL_SUPERVISOR, 0,
plic_irq_handler, NULL, "plic0");
riscv_set_intr_func(plic_splraise, plic_spllower,
plic_splx, plic_setipl);
plic_attached = 1;
csr_set(sie, SIE_SEIE);
sc->sc_intc.ic_node = faa->fa_node;
sc->sc_intc.ic_cookie = sc;
sc->sc_intc.ic_establish = plic_intr_establish_fdt;
sc->sc_intc.ic_disestablish = plic_intr_disestablish;
sc->sc_intc.ic_route = plic_intr_route;
sc->sc_intc.ic_barrier = plic_intr_barrier;
riscv_intr_register_fdt(&sc->sc_intc);
printf("\n");
}
int
plic_irq_handler(void *frame)
{
struct plic_softc* sc;
uint32_t pending;
uint32_t cpu;
int handled = 0;
sc = plic;
cpu = cpu_number();
pending = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
PLIC_CLAIM(sc, cpu));
if (pending >= sc->sc_ndev) {
printf("plic0: pending %x\n", pending);
return 0;
}
if (pending) {
handled = plic_irq_dispatch(pending, frame);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
PLIC_CLAIM(sc, cpu), pending);
#ifdef DEBUG_INTC
if (handled == 0) {
printf("plic handled == 0 on pending %d\n", pending);
}
#endif
}
return handled;
}
int
plic_irq_dispatch(uint32_t irq, void *frame)
{
int pri, s;
int handled = 0;
struct plic_softc* sc;
struct plic_intrhand *ih;
void *arg;
#ifdef DEBUG_INTC
printf("plic irq %d fired\n", irq);
#endif
sc = plic;
pri = sc->sc_isrcs[irq].is_irq_max;
s = plic_splraise(pri);
TAILQ_FOREACH(ih, &sc->sc_isrcs[irq].is_list, ih_list) {
#ifdef MULTIPROCESSOR
int need_lock;
if (ih->ih_flags & IPL_MPSAFE)
need_lock = 0;
else
need_lock = s < IPL_SCHED;
if (need_lock)
KERNEL_LOCK();
#endif
if (ih->ih_arg)
arg = ih->ih_arg;
else
arg = frame;
intr_enable();
handled = ih->ih_func(arg);
intr_disable();
if (handled)
ih->ih_count.ec_count++;
#ifdef MULTIPROCESSOR
if (need_lock)
KERNEL_UNLOCK();
#endif
}
plic_splx(s);
return handled;
}
void *
plic_intr_establish(int irqno, int level, struct cpu_info *ci,
int (*func)(void *), void *arg, char *name)
{
struct plic_softc *sc = plic;
struct plic_intrhand *ih;
u_long sie;
if (irqno < 0 || irqno >= PLIC_MAX_IRQS)
panic("plic_intr_establish: bogus irqnumber %d: %s",
irqno, name);
if (ci == NULL)
ci = &cpu_info_primary;
ih = malloc(sizeof *ih, M_DEVBUF, M_WAITOK);
ih->ih_func = func;
ih->ih_arg = arg;
ih->ih_ipl = level & IPL_IRQMASK;
ih->ih_flags = level & IPL_FLAGMASK;
ih->ih_irq = irqno;
ih->ih_name = name;
ih->ih_ci = ci;
sie = intr_disable();
TAILQ_INSERT_TAIL(&sc->sc_isrcs[irqno].is_list, ih, ih_list);
if (name != NULL)
evcount_attach(&ih->ih_count, name, &ih->ih_irq);
#ifdef DEBUG_INTC
printf("%s irq %d level %d [%s]\n", __func__, irqno, level,
name);
#endif
plic_calc_mask();
intr_restore(sie);
return (ih);
}
void *
plic_intr_establish_fdt(void *cookie, int *cell, int level,
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
{
return plic_intr_establish(cell[0], level, ci, func, arg, name);
}
void
plic_intr_disestablish(void *cookie)
{
struct plic_softc *sc = plic;
struct plic_intrhand *ih = cookie;
int irqno = ih->ih_irq;
u_long sie;
sie = intr_disable();
TAILQ_REMOVE(&sc->sc_isrcs[irqno].is_list, ih, ih_list);
if (ih->ih_name != NULL)
evcount_detach(&ih->ih_count);
intr_restore(sie);
free(ih, M_DEVBUF, 0);
}
void
plic_intr_route(void *cookie, int enable, struct cpu_info *ci)
{
struct plic_softc *sc = plic;
struct plic_intrhand *ih = cookie;
int irq = ih->ih_irq;
int cpu = ci->ci_cpuid;
uint32_t min_pri = sc->sc_isrcs[irq].is_irq_min;
if (enable == IRQ_ENABLE) {
plic_intr_enable_with_pri(irq, min_pri, cpu);
} else {
plic_intr_route_grid(irq, IRQ_DISABLE, cpu);
}
}
void
plic_intr_barrier(void *cookie)
{
struct plic_intrhand *ih = cookie;
sched_barrier(ih->ih_ci);
}
void
plic_splx(int new)
{
struct cpu_info *ci = curcpu();
if (ci->ci_ipending & riscv_smask[new])
riscv_do_pending_intr(new);
plic_setipl(new);
}
int
plic_spllower(int new)
{
struct cpu_info *ci = curcpu();
int old = ci->ci_cpl;
plic_splx(new);
return (old);
}
int
plic_splraise(int new)
{
struct cpu_info *ci = curcpu();
int old;
old = ci->ci_cpl;
if (old > new)
new = old;
plic_setipl(new);
return (old);
}
void
plic_setipl(int new)
{
struct cpu_info *ci = curcpu();
u_long sie;
sie = intr_disable();
ci->ci_cpl = new;
plic_set_threshold(ci->ci_cpuid, new);
if (ci->ci_timer_deferred && new < IPL_CLOCK)
sbi_set_timer(0);
intr_restore(sie);
}
void
plic_calc_mask(void)
{
struct cpu_info *ci = curcpu();
struct plic_softc *sc = plic;
struct plic_intrhand *ih;
int irq;
for (irq = 1; irq <= sc->sc_ndev; irq++) {
int max = IPL_NONE;
int min = IPL_HIGH;
TAILQ_FOREACH(ih, &sc->sc_isrcs[irq].is_list, ih_list) {
if (ih->ih_ipl > max)
max = ih->ih_ipl;
if (ih->ih_ipl < min)
min = ih->ih_ipl;
}
if (max == IPL_NONE)
min = IPL_NONE;
if (sc->sc_isrcs[irq].is_irq_max == max &&
sc->sc_isrcs[irq].is_irq_min == min)
continue;
sc->sc_isrcs[irq].is_irq_max = max;
sc->sc_isrcs[irq].is_irq_min = min;
if (min != IPL_NONE) {
plic_intr_enable_with_pri(irq, min, ci->ci_cpuid);
} else {
plic_intr_disable(irq, ci->ci_cpuid);
}
}
plic_setipl(ci->ci_cpl);
}
int
plic_get_cpuid(int intc)
{
uint32_t hart;
int parent_node;
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
if (OF_getpropintarray(intc, "#interrupt-cells", &hart,
sizeof(hart)) < 0) {
printf(": could not find #interrupt-cells for phandle %u\n", intc);
return (-1);
}
parent_node = OF_parent(intc);
CPU_INFO_FOREACH(cii, ci) {
if (ci->ci_node == parent_node)
return ci->ci_cpuid;
}
return -1;
}
void
plic_set_priority(int irq, uint32_t pri)
{
struct plic_softc *sc = plic;
uint32_t prival;
if (pri <= 4 || pri >= 12)
prival = 0;
else
prival = pri - 4;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
PLIC_PRIORITY(irq), prival);
}
void
plic_set_threshold(int cpu, uint32_t threshold)
{
struct plic_softc *sc = plic;
uint32_t prival;
if (threshold < 4)
prival = 0;
else if (threshold >= 12)
prival = IPL_HIGH - 4;
else
prival = threshold - 4;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
PLIC_THRESHOLD(sc, cpu), prival);
}
void
plic_intr_route_grid(int irq, int enable, int cpu)
{
struct plic_softc *sc = plic;
uint32_t val, mask;
if (irq == 0)
return;
KASSERT(cpu < MAXCPUS);
mask = (1 << (irq % 32));
val = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
PLIC_ENABLE(sc, irq, cpu));
if (enable == IRQ_ENABLE)
val |= mask;
else
val &= ~mask;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
PLIC_ENABLE(sc, irq, cpu), val);
}
void
plic_intr_enable_with_pri(int irq, uint32_t min_pri, int cpu)
{
plic_set_priority(irq, min_pri);
plic_set_threshold(cpu, min_pri-1);
plic_intr_route_grid(irq, IRQ_ENABLE, cpu);
}
void
plic_intr_disable(int irq, int cpu)
{
plic_set_priority(irq, 0);
plic_set_threshold(cpu, IPL_HIGH);
plic_intr_route_grid(irq, IRQ_DISABLE, cpu);
}