Add aplintc(4), a driver for the interrupt controller found on
authorkettenis <kettenis@openbsd.org>
Tue, 23 Feb 2021 17:01:17 +0000 (17:01 +0000)
committerkettenis <kettenis@openbsd.org>
Tue, 23 Feb 2021 17:01:17 +0000 (17:01 +0000)
Apple M1 SoCs.

ok patrick@

sys/arch/arm64/conf/GENERIC
sys/arch/arm64/conf/RAMDISK
sys/arch/arm64/conf/files.arm64
sys/arch/arm64/dev/aplintc.c [new file with mode: 0644]

index c2bd6d5..2450825 100644 (file)
@@ -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
index 3ef7a48..a334663 100644 (file)
@@ -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
index 00ac676..27000f8 100644 (file)
@@ -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 (file)
index 0000000..4c418c2
--- /dev/null
@@ -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 <sys/param.h>
+#include <sys/device.h>
+#include <sys/evcount.h>
+#include <sys/malloc.h>
+#include <sys/systm.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 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));
+}