#include "xcall.h"
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/device.h>
#include <sys/evcount.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <machine/armreg.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/intr.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>
#include <ddb/db_output.h>
#define APL_IRQ_CR_EL1 s3_4_c15_c10_4
#define APL_IRQ_CR_EL1_DISABLE (3 << 0)
#define APL_IPI_LOCAL_RR_EL1 s3_5_c15_c0_0
#define APL_IPI_GLOBAL_RR_EL1 s3_5_c15_c0_1
#define APL_IPI_SR_EL1 s3_5_c15_c1_1
#define APL_IPI_SR_EL1_PENDING (1 << 0)
#define AIC_INFO 0x0004
#define AIC_INFO_NDIE(val) (((val) >> 24) & 0xf)
#define AIC_INFO_NIRQ(val) ((val) & 0xffff)
#define AIC_WHOAMI 0x2000
#define AIC_EVENT 0x2004
#define AIC_EVENT_DIE(val) (((val) >> 24) & 0xff)
#define AIC_EVENT_TYPE(val) (((val) >> 16) & 0xff)
#define AIC_EVENT_TYPE_NONE 0
#define AIC_EVENT_TYPE_IRQ 1
#define AIC_EVENT_TYPE_IPI 4
#define AIC_EVENT_IRQ(val) ((val) & 0xffff)
#define AIC_EVENT_IPI_OTHER 1
#define AIC_EVENT_IPI_SELF 2
#define AIC_IPI_SEND 0x2008
#define AIC_IPI_ACK 0x200c
#define AIC_IPI_MASK_SET 0x2024
#define AIC_IPI_MASK_CLR 0x2028
#define AIC_IPI_OTHER (1U << 0)
#define AIC_IPI_SELF (1U << 31)
#define AIC_TARGET_CPU(irq) (0x3000 + ((irq) << 2))
#define AIC_SW_SET(irq) (0x4000 + (((irq) >> 5) << 2))
#define AIC_SW_CLR(irq) (0x4080 + (((irq) >> 5) << 2))
#define AIC_SW_BIT(irq) (1U << ((irq) & 0x1f))
#define AIC_MASK_SET(irq) (0x4100 + (((irq) >> 5) << 2))
#define AIC_MASK_CLR(irq) (0x4180 + (((irq) >> 5) << 2))
#define AIC_MASK_BIT(irq) (1U << ((irq) & 0x1f))
#define AIC2_CONFIG 0x0014
#define AIC2_CONFIG_ENABLE (1 << 0)
#define AIC2_SW_SET(die, irq) (0x6000 + (die) * 0x4a00 + (((irq) >> 5) << 2))
#define AIC2_SW_CLR(die, irq) (0x6200 + (die) * 0x4a00 + (((irq) >> 5) << 2))
#define AIC2_MASK_SET(die, irq) (0x6400 + (die) * 0x4a00 + (((irq) >> 5) << 2))
#define AIC2_MASK_CLR(die, irq) (0x6600 + (die) * 0x4a00 + (((irq) >> 5) << 2))
#define AIC2_EVENT 0xc000
#define AIC_MAXCPUS 32
#define AIC_MAXDIES 4
#define HREAD4(sc, reg) \
(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
#define HWRITE4(sc, reg, val) \
bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
#define HSET4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
#define HCLR4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
struct intrhand {
TAILQ_ENTRY(intrhand) ih_list;
int (*ih_func)(void *);
void *ih_arg;
int ih_ipl;
int ih_flags;
int ih_die;
int ih_irq;
struct evcount ih_count;
const char *ih_name;
struct cpu_info *ih_ci;
};
struct aplintc_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_space_handle_t sc_event_ioh;
int sc_version;
struct interrupt_controller sc_ic;
struct intrhand *sc_fiq_handler;
int sc_fiq_pending[AIC_MAXCPUS];
struct intrhand **sc_irq_handler[AIC_MAXDIES];
int sc_nirq;
int sc_ndie;
int sc_ncells;
TAILQ_HEAD(, intrhand) sc_irq_list[NIPL];
uint32_t sc_cpuremap[AIC_MAXCPUS];
u_int sc_ipi_reason[AIC_MAXCPUS];
struct evcount sc_ipi_count;
};
static inline void
aplintc_sw_clr(struct aplintc_softc *sc, int die, int irq)
{
if (sc->sc_version == 1)
HWRITE4(sc, AIC_SW_CLR(irq), AIC_SW_BIT(irq));
else
HWRITE4(sc, AIC2_SW_CLR(die, irq), AIC_SW_BIT(irq));
}
static inline void
aplintc_sw_set(struct aplintc_softc *sc, int die, int irq)
{
if (sc->sc_version == 1)
HWRITE4(sc, AIC_SW_SET(irq), AIC_SW_BIT(irq));
else
HWRITE4(sc, AIC2_SW_SET(die, irq), AIC_SW_BIT(irq));
}
static inline void
aplintc_mask_clr(struct aplintc_softc *sc, int die, int irq)
{
if (sc->sc_version == 1)
HWRITE4(sc, AIC_MASK_CLR(irq), AIC_MASK_BIT(irq));
else
HWRITE4(sc, AIC2_MASK_CLR(die, irq), AIC_MASK_BIT(irq));
}
static inline void
aplintc_mask_set(struct aplintc_softc *sc, int die, int irq)
{
if (sc->sc_version == 1)
HWRITE4(sc, AIC_MASK_SET(irq), AIC_MASK_BIT(irq));
else
HWRITE4(sc, AIC2_MASK_SET(die, irq), AIC_MASK_BIT(irq));
}
struct aplintc_softc *aplintc_sc;
int aplintc_match(struct device *, void *, void *);
void aplintc_attach(struct device *, struct device *, void *);
const struct cfattach aplintc_ca = {
sizeof (struct aplintc_softc), aplintc_match, aplintc_attach
};
struct cfdriver aplintc_cd = {
NULL, "aplintc", DV_DULL
};
void aplintc_cpuinit(void);
void aplintc_irq_handler(void *);
void aplintc_fiq_handler(void *);
void aplintc_intr_barrier(void *);
int aplintc_splraise(int);
int aplintc_spllower(int);
void aplintc_splx(int);
void aplintc_setipl(int);
void aplintc_enable_wakeup(void);
void aplintc_disable_wakeup(void);
void *aplintc_intr_establish(void *, int *, int, struct cpu_info *,
int (*)(void *), void *, char *);
void aplintc_intr_disestablish(void *);
void aplintc_intr_set_wakeup(void *);
void aplintc_send_ipi(struct cpu_info *, int);
void aplintc_handle_ipi(struct aplintc_softc *);
int
aplintc_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "apple,aic") ||
OF_is_compatible(faa->fa_node, "apple,aic2");
}
void
aplintc_attach(struct device *parent, struct device *self, void *aux)
{
struct aplintc_softc *sc = (struct aplintc_softc *)self;
struct fdt_attach_args *faa = aux;
uint32_t info;
int die, ipl;
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
if (OF_is_compatible(faa->fa_node, "apple,aic2"))
sc->sc_version = 2;
else
sc->sc_version = 1;
sc->sc_ncells = OF_getpropint(faa->fa_node, "#interrupt-cells", 3);
if (sc->sc_ncells < 3 || sc->sc_ncells > 4) {
printf(": invalid number of cells\n");
return;
}
if (faa->fa_nreg > 1) {
if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr,
faa->fa_reg[1].size, 0, &sc->sc_event_ioh)) {
printf(": can't map event register\n");
return;
}
} else {
if (sc->sc_version == 1) {
bus_space_subregion(sc->sc_iot, sc->sc_ioh,
AIC_EVENT, 4, &sc->sc_event_ioh);
} else {
bus_space_subregion(sc->sc_iot, sc->sc_ioh,
AIC2_EVENT, 4, &sc->sc_event_ioh);
}
}
info = HREAD4(sc, AIC_INFO);
sc->sc_nirq = AIC_INFO_NIRQ(info);
sc->sc_ndie = AIC_INFO_NDIE(info) + 1;
for (die = 0; die < sc->sc_ndie; die++) {
sc->sc_irq_handler[die] = mallocarray(sc->sc_nirq,
sizeof(struct intrhand), M_DEVBUF, M_WAITOK | M_ZERO);
}
for (ipl = 0; ipl < NIPL; ipl++)
TAILQ_INIT(&sc->sc_irq_list[ipl]);
printf(" nirq %d ndie %d\n", sc->sc_nirq, sc->sc_ndie);
arm_init_smask();
aplintc_sc = sc;
aplintc_cpuinit();
evcount_attach(&sc->sc_ipi_count, "ipi", NULL);
arm_set_intr_handler(aplintc_splraise, aplintc_spllower, aplintc_splx,
aplintc_setipl, aplintc_irq_handler, aplintc_fiq_handler,
aplintc_enable_wakeup, aplintc_disable_wakeup);
sc->sc_ic.ic_node = faa->fa_node;
sc->sc_ic.ic_cookie = self;
sc->sc_ic.ic_establish = aplintc_intr_establish;
sc->sc_ic.ic_disestablish = aplintc_intr_disestablish;
sc->sc_ic.ic_cpu_enable = aplintc_cpuinit;
sc->sc_ic.ic_barrier = aplintc_intr_barrier;
sc->sc_ic.ic_set_wakeup = aplintc_intr_set_wakeup;
arm_intr_register_fdt(&sc->sc_ic);
#ifdef MULTIPROCESSOR
intr_send_ipi_func = aplintc_send_ipi;
#endif
if (sc->sc_version == 2)
HSET4(sc, AIC2_CONFIG, AIC2_CONFIG_ENABLE);
}
void
aplintc_cpuinit(void)
{
struct aplintc_softc *sc = aplintc_sc;
struct cpu_info *ci = curcpu();
uint32_t hwid;
KASSERT(ci->ci_cpuid < AIC_MAXCPUS);
if (!CPU_IS_PRIMARY(ci))
WRITE_SPECIALREG(APL_IRQ_CR_EL1, APL_IRQ_CR_EL1_DISABLE);
if (sc->sc_version == 1) {
hwid = HREAD4(sc, AIC_WHOAMI);
KASSERT(hwid < AIC_MAXCPUS);
sc->sc_cpuremap[ci->ci_cpuid] = hwid;
}
}
void
aplintc_run_handler(struct intrhand *ih, void *frame, int s)
{
void *arg;
int handled;
#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;
handled = ih->ih_func(arg);
if (handled)
ih->ih_count.ec_count++;
#ifdef MULTIPROCESSOR
if (need_lock)
KERNEL_UNLOCK();
#endif
}
void
aplintc_irq_handler(void *frame)
{
struct aplintc_softc *sc = aplintc_sc;
struct cpu_info *ci = curcpu();
struct intrhand *ih;
uint32_t event;
uint32_t die, irq, type;
int s;
event = bus_space_read_4(sc->sc_iot, sc->sc_event_ioh, 0);
die = AIC_EVENT_DIE(event);
irq = AIC_EVENT_IRQ(event);
type = AIC_EVENT_TYPE(event);
if (type != AIC_EVENT_TYPE_IRQ) {
if (type != AIC_EVENT_TYPE_NONE) {
printf("%s: unexpected event type %d\n",
__func__, type);
}
return;
}
if (die >= sc->sc_ndie)
panic("%s: unexpected die %d", __func__, die);
if (irq >= sc->sc_nirq)
panic("%s: unexpected irq %d", __func__, irq);
if (sc->sc_irq_handler[die][irq] == NULL)
return;
aplintc_sw_clr(sc, die, irq);
ih = sc->sc_irq_handler[die][irq];
if (ci->ci_cpl >= ih->ih_ipl) {
TAILQ_INSERT_TAIL(&sc->sc_irq_list[ih->ih_ipl], ih, ih_list);
} else {
s = aplintc_splraise(ih->ih_ipl);
intr_enable();
aplintc_run_handler(ih, frame, s);
intr_disable();
aplintc_splx(s);
aplintc_mask_clr(sc, die, irq);
}
}
void
aplintc_fiq_handler(void *frame)
{
struct aplintc_softc *sc = aplintc_sc;
struct cpu_info *ci = curcpu();
uint64_t reg;
int s;
#ifdef MULTIPROCESSOR
reg = READ_SPECIALREG(APL_IPI_SR_EL1);
if (reg & APL_IPI_SR_EL1_PENDING) {
WRITE_SPECIALREG(APL_IPI_SR_EL1, APL_IPI_SR_EL1_PENDING);
aplintc_handle_ipi(sc);
}
#endif
reg = READ_SPECIALREG(cntv_ctl_el0);
if ((reg & (CNTV_CTL_ENABLE | CNTV_CTL_IMASK | CNTV_CTL_ISTATUS)) ==
(CNTV_CTL_ENABLE | CNTV_CTL_ISTATUS)) {
if (ci->ci_cpl >= IPL_CLOCK) {
WRITE_SPECIALREG(cntv_ctl_el0, reg | CNTV_CTL_IMASK);
sc->sc_fiq_pending[ci->ci_cpuid] = 1;
} else {
s = aplintc_splraise(IPL_CLOCK);
sc->sc_fiq_handler->ih_func(frame);
sc->sc_fiq_handler->ih_count.ec_count++;
aplintc_splx(s);
}
}
}
void
aplintc_intr_barrier(void *cookie)
{
struct intrhand *ih = cookie;
sched_barrier(ih->ih_ci);
}
int
aplintc_splraise(int new)
{
struct cpu_info *ci = curcpu();
int old = ci->ci_cpl;
if (old > new)
new = old;
aplintc_setipl(new);
return old;
}
int
aplintc_spllower(int new)
{
struct cpu_info *ci = curcpu();
int old = ci->ci_cpl;
aplintc_splx(new);
return old;
}
void
aplintc_splx(int new)
{
struct aplintc_softc *sc = aplintc_sc;
struct cpu_info *ci = curcpu();
struct intrhand *ih;
uint64_t reg;
u_long daif;
int ipl;
daif = intr_disable();
if (sc->sc_fiq_pending[ci->ci_cpuid] && new < IPL_CLOCK) {
sc->sc_fiq_pending[ci->ci_cpuid] = 0;
reg = READ_SPECIALREG(cntv_ctl_el0);
WRITE_SPECIALREG(cntv_ctl_el0, reg & ~CNTV_CTL_IMASK);
}
if (CPU_IS_PRIMARY(ci)) {
for (ipl = ci->ci_cpl; ipl > new; ipl--) {
while (!TAILQ_EMPTY(&sc->sc_irq_list[ipl])) {
ih = TAILQ_FIRST(&sc->sc_irq_list[ipl]);
TAILQ_REMOVE(&sc->sc_irq_list[ipl],
ih, ih_list);
aplintc_sw_set(sc, ih->ih_die, ih->ih_irq);
aplintc_mask_clr(sc, ih->ih_die, ih->ih_irq);
}
}
}
aplintc_setipl(new);
intr_restore(daif);
if (ci->ci_ipending & arm_smask[new])
arm_do_pending_intr(new);
}
void
aplintc_setipl(int ipl)
{
struct cpu_info *ci = curcpu();
ci->ci_cpl = ipl;
}
void
aplintc_enable_wakeup(void)
{
struct aplintc_softc *sc = aplintc_sc;
struct intrhand *ih;
int die, irq;
for (die = 0; die < sc->sc_ndie; die++) {
for (irq = 0; irq < sc->sc_nirq; irq++) {
ih = sc->sc_irq_handler[die][irq];
if (ih == NULL || (ih->ih_flags & IPL_WAKEUP))
continue;
aplintc_mask_set(sc, die, irq);
}
}
}
void
aplintc_disable_wakeup(void)
{
struct aplintc_softc *sc = aplintc_sc;
struct intrhand *ih;
int die, irq;
for (die = 0; die < sc->sc_ndie; die++) {
for (irq = 0; irq < sc->sc_nirq; irq++) {
ih = sc->sc_irq_handler[die][irq];
if (ih == NULL || (ih->ih_flags & IPL_WAKEUP))
continue;
aplintc_mask_clr(sc, die, irq);
}
}
}
void *
aplintc_intr_establish(void *cookie, int *cell, int level,
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
{
struct aplintc_softc *sc = cookie;
struct intrhand *ih;
uint32_t type = cell[0];
uint32_t die, irq;
if (sc->sc_ncells == 3) {
die = 0;
irq = cell[1];
} else {
die = cell[1];
irq = cell[2];
}
if (type == 0) {
KASSERT(level != (IPL_CLOCK | IPL_MPSAFE));
if (die >= sc->sc_ndie) {
panic("%s: bogus die number %d",
sc->sc_dev.dv_xname, die);
}
if (irq >= sc->sc_nirq) {
panic("%s: bogus irq number %d",
sc->sc_dev.dv_xname, irq);
}
} else if (type == 1) {
KASSERT(level == (IPL_CLOCK | IPL_MPSAFE));
if (irq >= 4)
panic("%s: bogus fiq number %d",
sc->sc_dev.dv_xname, irq);
} else {
panic("%s: bogus irq type %d",
sc->sc_dev.dv_xname, cell[0]);
}
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_die = die;
ih->ih_irq = irq;
ih->ih_name = name;
ih->ih_ci = ci;
if (name != NULL)
evcount_attach(&ih->ih_count, name, &ih->ih_irq);
if (type == 0) {
sc->sc_irq_handler[die][irq] = ih;
if (sc->sc_version == 1)
HWRITE4(sc, AIC_TARGET_CPU(irq), 1);
aplintc_mask_clr(sc, die, irq);
} else
sc->sc_fiq_handler = ih;
return ih;
}
void
aplintc_intr_disestablish(void *cookie)
{
struct aplintc_softc *sc = aplintc_sc;
struct intrhand *ih = cookie;
struct intrhand *tmp;
u_long daif;
KASSERT(ih->ih_ipl < IPL_CLOCK);
daif = intr_disable();
aplintc_sw_clr(sc, ih->ih_die, ih->ih_irq);
aplintc_mask_set(sc, ih->ih_die, ih->ih_irq);
TAILQ_FOREACH(tmp, &sc->sc_irq_list[ih->ih_ipl], ih_list) {
if (tmp == ih) {
TAILQ_REMOVE(&sc->sc_irq_list[ih->ih_ipl],
ih, ih_list);
break;
}
}
sc->sc_irq_handler[ih->ih_die][ih->ih_irq] = NULL;
if (ih->ih_name)
evcount_detach(&ih->ih_count);
intr_restore(daif);
free(ih, M_DEVBUF, sizeof(*ih));
}
void
aplintc_intr_set_wakeup(void *cookie)
{
struct intrhand *ih = cookie;
ih->ih_flags |= IPL_WAKEUP;
}
#ifdef MULTIPROCESSOR
void
aplintc_send_ipi(struct cpu_info *ci, int reason)
{
struct aplintc_softc *sc = aplintc_sc;
uint64_t sendmask;
if (reason == ARM_IPI_NOP) {
if (ci == curcpu())
return;
} else {
atomic_setbits_int(&sc->sc_ipi_reason[ci->ci_cpuid],
1 << reason);
}
sendmask = (ci->ci_mpidr & MPIDR_AFF0);
if ((curcpu()->ci_mpidr & MPIDR_AFF1) == (ci->ci_mpidr & MPIDR_AFF1)) {
WRITE_SPECIALREG(APL_IPI_LOCAL_RR_EL1, sendmask);
} else {
sendmask |= (ci->ci_mpidr & MPIDR_AFF1) << 8;
WRITE_SPECIALREG(APL_IPI_GLOBAL_RR_EL1, sendmask);
}
}
void
aplintc_handle_ipi(struct aplintc_softc *sc)
{
struct cpu_info *ci = curcpu();
u_int reasons;
reasons = sc->sc_ipi_reason[ci->ci_cpuid];
if (reasons) {
reasons = atomic_swap_uint(&sc->sc_ipi_reason[ci->ci_cpuid], 0);
#ifdef DDB
if (ISSET(reasons, 1 << ARM_IPI_DDB))
db_enter();
#endif
if (ISSET(reasons, 1 << ARM_IPI_HALT))
cpu_halt();
#if NXCALL > 0
if (ISSET(reasons, 1 << ARM_IPI_XCALL))
arm_cpu_xcall_dispatch();
#endif
}
sc->sc_ipi_count.ec_count++;
}
#endif