From 7bc868b9d1f8cf0576ef2aefb376a869aa85cef3 Mon Sep 17 00:00:00 2001 From: kettenis Date: Tue, 23 Feb 2021 17:01:17 +0000 Subject: [PATCH] Add aplintc(4), a driver for the interrupt controller found on Apple M1 SoCs. ok patrick@ --- sys/arch/arm64/conf/GENERIC | 3 +- sys/arch/arm64/conf/RAMDISK | 3 +- sys/arch/arm64/conf/files.arm64 | 6 +- sys/arch/arm64/dev/aplintc.c | 379 ++++++++++++++++++++++++++++++++ 4 files changed, 388 insertions(+), 3 deletions(-) create mode 100644 sys/arch/arm64/dev/aplintc.c diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index c2bd6d500b0..2450825a7b5 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.188 2021/02/22 21:51:48 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.189 2021/02/23 17:01:17 kettenis Exp $ # # GENERIC machine description file # @@ -128,6 +128,7 @@ wsdisplay* at amdgpu? # Apple apldog* at fdt? early 1 +aplintc* at fdt? early 1 exuart* at fdt? # iMX diff --git a/sys/arch/arm64/conf/RAMDISK b/sys/arch/arm64/conf/RAMDISK index 3ef7a48e710..a334663a5e9 100644 --- a/sys/arch/arm64/conf/RAMDISK +++ b/sys/arch/arm64/conf/RAMDISK @@ -1,4 +1,4 @@ -# $OpenBSD: RAMDISK,v 1.141 2021/02/22 21:51:48 kettenis Exp $ +# $OpenBSD: RAMDISK,v 1.142 2021/02/23 17:01:17 kettenis Exp $ # # GENERIC machine description file # @@ -112,6 +112,7 @@ wsdisplay* at simplefb? # Apple apldog* at fdt? early 1 +aplintc* at fdt? early 1 exuart* at fdt? # iMX diff --git a/sys/arch/arm64/conf/files.arm64 b/sys/arch/arm64/conf/files.arm64 index 00ac67666e5..27000f8b26f 100644 --- a/sys/arch/arm64/conf/files.arm64 +++ b/sys/arch/arm64/conf/files.arm64 @@ -1,4 +1,4 @@ -# $OpenBSD: files.arm64,v 1.34 2021/02/22 21:47:47 kettenis Exp $ +# $OpenBSD: files.arm64,v 1.35 2021/02/23 17:01:17 kettenis Exp $ maxpartitions 16 maxusers 2 8 128 @@ -140,6 +140,10 @@ device apldog attach apldog at fdt file arch/arm64/dev/apldog.c apldog +device aplintc +attach aplintc at fdt +file arch/arm64/dev/aplintc.c aplintc + device bcmintc attach bcmintc at fdt file arch/arm64/dev/bcm2836_intr.c bcmintc diff --git a/sys/arch/arm64/dev/aplintc.c b/sys/arch/arm64/dev/aplintc.c new file mode 100644 index 00000000000..4c418c2a25e --- /dev/null +++ b/sys/arch/arm64/dev/aplintc.c @@ -0,0 +1,379 @@ +/* $OpenBSD: aplintc.c,v 1.1 2021/02/23 17:01:17 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#define CNTV_CTL_IMASK (1 << 1) + +#define AIC_INFO 0x0004 +#define AIC_INFO_NIRQ(val) ((val) & 0xffff) +#define AIC_WHOAMI 0x2000 +#define AIC_EVENT 0x2004 +#define AIC_EVENT_TYPE(val) ((val) >> 16) +#define AIC_EVENT_TYPE_IRQ 1 +#define AIC_EVENT_IRQ(val) ((val) & 0xffff) +#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 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)) + +struct intrhand { + TAILQ_ENTRY(intrhand) ih_list; + int (*ih_func)(void *); + void *ih_arg; + int ih_ipl; + int ih_flags; + 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; + + struct interrupt_controller sc_ic; + + struct intrhand *sc_fiq_handler; + int sc_fiq_pending; + struct intrhand **sc_irq_handler; + int sc_nirq; + TAILQ_HEAD(, intrhand) sc_irq_list[NIPL]; +}; + +struct aplintc_softc *aplintc_sc; + +int aplintc_match(struct device *, void *, void *); +void aplintc_attach(struct device *, struct device *, void *); + +struct cfattach aplintc_ca = { + sizeof (struct aplintc_softc), aplintc_match, aplintc_attach +}; + +struct cfdriver aplintc_cd = { + NULL, "aplintc", DV_DULL +}; + +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_intr_establish(void *, int *, int, struct cpu_info *, + int (*)(void *), void *, char *); +void aplintc_intr_disestablish(void *); + +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"); +} + +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 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; + } + + info = HREAD4(sc, AIC_INFO); + sc->sc_nirq = AIC_INFO_NIRQ(info); + sc->sc_irq_handler = mallocarray(sc->sc_nirq, + sizeof(*sc->sc_irq_handler), M_DEVBUF, M_WAITOK | M_ZERO); + for (ipl = 0; ipl < NIPL; ipl++) + TAILQ_INIT(&sc->sc_irq_list[ipl]); + + printf(" nirq %d\n", sc->sc_nirq); + + arm_init_smask(); + + aplintc_sc = sc; + arm_set_intr_handler(aplintc_splraise, aplintc_spllower, aplintc_splx, + aplintc_setipl, aplintc_irq_handler, aplintc_fiq_handler); + + 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_barrier = aplintc_intr_barrier; + arm_intr_register_fdt(&sc->sc_ic); +} + +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 irq, type; + int handled; + int s; + + event = HREAD4(sc, AIC_EVENT); + irq = AIC_EVENT_IRQ(event); + type = AIC_EVENT_TYPE(event); + + if (type != AIC_EVENT_TYPE_IRQ) + panic("%s: unexpected event type %d\n", __func__, type); + + if (irq >= sc->sc_nirq) + panic("%s: unexpected irq %d\n", __func__, irq); + + if (sc->sc_irq_handler[irq] == NULL) + return; + + HWRITE4(sc, AIC_SW_CLR(irq), AIC_SW_BIT(irq)); + + ih = sc->sc_irq_handler[irq]; + + if (ci->ci_cpl >= ih->ih_ipl) { + /* Queue interrupt as pending. */ + TAILQ_INSERT_TAIL(&sc->sc_irq_list[ih->ih_ipl], ih, ih_list); + } else { + s = aplintc_splraise(ih->ih_ipl); + intr_enable(); + handled = ih->ih_func(ih->ih_arg); + intr_disable(); + if (handled) + ih->ih_count.ec_count++; + aplintc_splx(s); + + HWRITE4(sc, AIC_MASK_CLR(irq), AIC_MASK_BIT(irq)); + } +} + +void +aplintc_fiq_handler(void *frame) +{ + struct aplintc_softc *sc = aplintc_sc; + struct cpu_info *ci = curcpu(); + uint64_t reg; + int s; + + if (ci->ci_cpl >= IPL_CLOCK) { + /* Mask timer interrupt and mark as pending. */ + reg = READ_SPECIALREG(cntv_ctl_el0); + WRITE_SPECIALREG(cntv_ctl_el0, reg | CNTV_CTL_IMASK); + sc->sc_fiq_pending = 1; + return; + } + + 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(); + + /* Process pending FIQs. */ + if (sc->sc_fiq_pending && new < IPL_CLOCK) { + sc->sc_fiq_pending = 0; + reg = READ_SPECIALREG(cntv_ctl_el0); + WRITE_SPECIALREG(cntv_ctl_el0, reg & ~CNTV_CTL_IMASK); + } + + /* Process pending IRQs. */ + 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); + + HWRITE4(sc, AIC_SW_SET(ih->ih_irq), + AIC_SW_BIT(ih->ih_irq)); + HWRITE4(sc, AIC_MASK_CLR(ih->ih_irq), + AIC_MASK_BIT(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_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 irq = cell[1]; + + if (type == 0) { + KASSERT(level != (IPL_CLOCK | IPL_MPSAFE)); + if (irq >= sc->sc_nirq) { + panic("%s: bogus irq number %d\n", + 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\n", + 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_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[irq] = ih; + HWRITE4(sc, AIC_TARGET_CPU(irq), 1); + HWRITE4(sc, AIC_MASK_CLR(irq), AIC_MASK_BIT(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(); + + HWRITE4(sc, AIC_SW_CLR(ih->ih_irq), AIC_SW_BIT(ih->ih_irq)); + HWRITE4(sc, AIC_MASK_SET(ih->ih_irq), AIC_MASK_BIT(ih->ih_irq)); + + /* Remove ourselves from the list of pending IRQs. */ + 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_irq] = NULL; + if (ih->ih_name) + evcount_detach(&ih->ih_count); + + intr_restore(daif); + + free(ih, M_DEVBUF, sizeof(*ih)); +} -- 2.20.1