/* $NetBSD: apple_intc.c,v 1.9 2022/06/28 10:42:22 jmcneill Exp $ */ /*- * Copyright (c) 2021 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_ddb.h" #include "opt_multiprocessor.h" #define _INTR_PRIVATE #include __KERNEL_RCSID(0, "$NetBSD: apple_intc.c,v 1.9 2022/06/28 10:42:22 jmcneill Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * AIC registers */ #define AIC_INFO 0x0004 #define AIC_INFO_NIRQ __BITS(15,0) #define AIC_WHOAMI 0x2000 #define AIC_EVENT 0x2004 #define AIC_EVENT_TYPE __BITS(31,16) #define AIC_EVENT_TYPE_NONE 0 #define AIC_EVENT_TYPE_IRQ 1 #define AIC_EVENT_TYPE_IPI 4 #define AIC_EVENT_DATA __BITS(15,0) #define AIC_EVENT_IPI_OTHER 1 #define AIC_IPI_SEND 0x2008 #define AIC_IPI_ACK 0x200c #define AIC_IPI_MASK_CLR 0x2028 #define AIC_IPI_OTHER __BIT(0) #define AIC_AFFINITY(irqno) (0x3000 + (irqno) * 4) #define AIC_SW_SET(irqno) (0x4000 + (irqno) / 32 * 4) #define AIC_SW_CLR(irqno) (0x4080 + (irqno) / 32 * 4) #define AIC_MASK_SET(irqno) (0x4100 + (irqno) / 32 * 4) #define AIC_MASK_CLR(irqno) (0x4180 + (irqno) / 32 * 4) #define AIC_MASK_BIT(irqno) __BIT((irqno) & 0x1f) static const struct device_compatible_entry compat_data[] = { { .compat = "apple,aic" }, DEVICE_COMPAT_EOL }; struct apple_intc_softc; struct apple_intc_percpu { struct apple_intc_softc *pc_sc; u_int pc_cpuid; u_int pc_ipimask; struct pic_softc pc_pic; }; #define LOCALPIC_SOURCE_TIMER 0 #define LOCALPIC_SOURCE_IPI 1 struct apple_intc_softc { device_t sc_dev; /* device handle */ bus_space_tag_t sc_bst; /* mmio tag */ bus_space_handle_t sc_bsh; /* mmio handle */ u_int sc_nirq; /* number of supported IRQs */ u_int *sc_cpuid; /* map of cpu index to AIC CPU ID */ struct apple_intc_percpu *sc_pc; /* per-CPU data for timer and IPIs */ struct pic_softc sc_pic; }; static struct apple_intc_softc *intc_softc; #define PICTOSOFTC(pic) container_of(pic, struct apple_intc_softc, sc_pic) #define PICTOPERCPU(pic) container_of(pic, struct apple_intc_percpu, pc_pic) #define AIC_READ(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define AIC_WRITE(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) static void apple_intc_unblock_irqs(struct pic_softc *pic, size_t irqbase, uint32_t mask) { struct apple_intc_softc * const sc = PICTOSOFTC(pic); AIC_WRITE(sc, AIC_SW_SET(irqbase), mask); AIC_WRITE(sc, AIC_MASK_CLR(irqbase), mask); } static void apple_intc_block_irqs(struct pic_softc *pic, size_t irqbase, uint32_t mask) { } static void apple_intc_establish_irq(struct pic_softc *pic, struct intrsource *is) { struct apple_intc_softc * const sc = PICTOSOFTC(pic); KASSERT(is->is_type == IST_LEVEL); /* Route to primary PE by default */ AIC_WRITE(sc, AIC_AFFINITY(is->is_irq), __BIT(0)); AIC_WRITE(sc, AIC_MASK_CLR(is->is_irq), AIC_MASK_BIT(is->is_irq)); } static void apple_intc_set_priority(struct pic_softc *pic, int ipl) { curcpu()->ci_cpl = ipl; } static void apple_intc_cpu_init(struct pic_softc *pic, struct cpu_info *ci) { struct apple_intc_softc * const sc = PICTOSOFTC(pic); const u_int cpuno = cpu_index(ci); sc->sc_cpuid[cpuno] = AIC_READ(sc, AIC_WHOAMI); } static const struct pic_ops apple_intc_picops = { .pic_unblock_irqs = apple_intc_unblock_irqs, .pic_block_irqs = apple_intc_block_irqs, .pic_establish_irq = apple_intc_establish_irq, .pic_set_priority = apple_intc_set_priority, #ifdef MULTIPROCESSOR .pic_cpu_init = apple_intc_cpu_init, #endif }; static void apple_intc_local_unblock_irqs(struct pic_softc *pic, size_t irqbase, uint32_t mask) { KASSERT(irqbase == 0); if ((mask & __BIT(LOCALPIC_SOURCE_TIMER)) != 0) { gtmr_cntv_ctl_write(gtmr_cntv_ctl_read() & ~CNTCTL_IMASK); isb(); } } static void apple_intc_local_block_irqs(struct pic_softc *pic, size_t irqbase, uint32_t mask) { KASSERT(irqbase == 0); if ((mask & __BIT(LOCALPIC_SOURCE_TIMER)) != 0) { gtmr_cntv_ctl_write(gtmr_cntv_ctl_read() | CNTCTL_IMASK); isb(); } } static void apple_intc_local_establish_irq(struct pic_softc *pic, struct intrsource *is) { } #ifdef MULTIPROCESSOR static void apple_intc_local_ipi_send(struct pic_softc *pic, const kcpuset_t *kcp, u_long ipi) { struct apple_intc_percpu * const pc = PICTOPERCPU(pic); struct apple_intc_softc * const sc = pc->pc_sc; const u_int target = sc->sc_cpuid[pc->pc_cpuid]; atomic_or_32(&pc->pc_ipimask, __BIT(ipi)); AIC_WRITE(sc, AIC_IPI_SEND, __BIT(target)); } #endif /* MULTIPROCESSOR */ static const struct pic_ops apple_intc_localpicops = { .pic_unblock_irqs = apple_intc_local_unblock_irqs, .pic_block_irqs = apple_intc_local_block_irqs, .pic_establish_irq = apple_intc_local_establish_irq, #ifdef MULTIPROCESSOR .pic_ipi_send = apple_intc_local_ipi_send, #endif }; static void * apple_intc_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, int (*func)(void *), void *arg, const char *xname) { struct apple_intc_softc * const sc = device_private(dev); /* 1st cell is the interrupt type (0=IRQ, 1=FIQ) */ const u_int type = be32toh(specifier[0]); /* 2nd cell is the interrupt number */ const u_int intno = be32toh(specifier[1]); /* 3rd cell is the interrupt flags */ const u_int mpsafe = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0; if (type == 0) return intr_establish_xname(intno, ipl, IST_LEVEL | mpsafe, func, arg, xname); /* interate over CPUs for LOCALPIC_SOURCE_TIMER */ CPU_INFO_ITERATOR cii; struct cpu_info *ci; void *ih = NULL; for (CPU_INFO_FOREACH(cii, ci)) { const cpuid_t cpuno = cpu_index(ci); struct apple_intc_percpu * const pc = &sc->sc_pc[cpuno]; struct pic_softc * const pic = &pc->pc_pic; const int irq = pic->pic_irqbase + LOCALPIC_SOURCE_TIMER; void *ihn = intr_establish_xname(irq, ipl, IST_LEVEL | mpsafe, func, arg, xname); if (cpuno == 0) ih = ihn; } return ih; } static void apple_intc_fdt_disestablish(device_t dev, void *ih) { intr_disestablish(ih); } static bool apple_intc_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) { if (!specifier) return false; /* 1st cell is the interrupt type (0=IRQ, 1=FIQ) */ const u_int type = be32toh(specifier[0]); /* 2nd cell is the interrupt number */ const u_int intno = be32toh(specifier[1]); snprintf(buf, buflen, "%s %u", type == 0 ? "irq" : "fiq", intno); return true; } static const struct fdtbus_interrupt_controller_func apple_intc_fdt_funcs = { .establish = apple_intc_fdt_establish, .disestablish = apple_intc_fdt_disestablish, .intrstr = apple_intc_fdt_intrstr, }; static void apple_intc_mark_pending(struct pic_softc *pic, u_int intno) { const int base = intno & ~0x1f; const uint32_t pending = __BIT(intno & 0x1f); pic_mark_pending_sources(pic, base, pending); } static void apple_intc_irq_handler(void *frame) { struct cpu_info * const ci = curcpu(); struct apple_intc_softc * const sc = intc_softc; struct pic_softc *pic; struct intrsource *is; const int oldipl = ci->ci_cpl; uint16_t evtype, evdata; bus_size_t clr_reg; uint32_t clr_val; ci->ci_data.cpu_nintr++; for (;;) { const uint32_t ev = AIC_READ(sc, AIC_EVENT); evtype = __SHIFTOUT(ev, AIC_EVENT_TYPE); evdata = __SHIFTOUT(ev, AIC_EVENT_DATA); dsb(sy); isb(); if (evtype == AIC_EVENT_TYPE_IRQ) { KASSERT(evdata < sc->sc_nirq); pic = &sc->sc_pic; is = pic->pic_sources[evdata]; KASSERT(is != NULL); AIC_WRITE(sc, AIC_SW_CLR(evdata), __BIT(evdata & 0x1f)); clr_reg = AIC_MASK_CLR(evdata); clr_val = AIC_MASK_BIT(evdata); } else if (evtype == AIC_EVENT_TYPE_IPI) { KASSERT(evdata == AIC_EVENT_IPI_OTHER); pic = &sc->sc_pc[cpu_index(ci)].pc_pic; is = pic->pic_sources[LOCALPIC_SOURCE_IPI]; KASSERT(is != NULL); AIC_WRITE(sc, AIC_IPI_ACK, AIC_IPI_OTHER); clr_reg = 0; clr_val = 0; } else { break; } if (ci->ci_cpl >= is->is_ipl) { apple_intc_mark_pending(pic, is->is_irq); } else { pic_set_priority(ci, is->is_ipl); ENABLE_INTERRUPT(); pic_dispatch(is, frame); DISABLE_INTERRUPT(); if (clr_val != 0) { AIC_WRITE(sc, clr_reg, clr_val); } } } if (oldipl != IPL_HIGH) { pic_do_pending_ints(DAIF_I|DAIF_F, oldipl, frame); } } static void apple_intc_fiq_handler(void *frame) { struct cpu_info * const ci = curcpu(); struct apple_intc_softc * const sc = intc_softc; struct pic_softc * const pic = &sc->sc_pc[cpu_index(ci)].pc_pic; const int oldipl = ci->ci_cpl; ci->ci_data.cpu_nintr++; struct intrsource * const is = pic->pic_sources[LOCALPIC_SOURCE_TIMER]; dsb(sy); isb(); if (oldipl >= is->is_ipl) { apple_intc_mark_pending(pic, LOCALPIC_SOURCE_TIMER); } else { pic_set_priority(ci, is->is_ipl); pic_dispatch(is, frame); } if (oldipl != IPL_HIGH) { pic_do_pending_ints(DAIF_I|DAIF_F, oldipl, frame); } } #ifdef MULTIPROCESSOR static int apple_intc_ipi_handler(void *priv) { struct apple_intc_percpu * const pc = priv; struct apple_intc_softc * const sc = pc->pc_sc; uint32_t ipimask, bit; AIC_WRITE(sc, AIC_IPI_MASK_CLR, AIC_IPI_OTHER); ipimask = atomic_swap_32(&pc->pc_ipimask, 0); while ((bit = ffs(ipimask)) > 0) { const u_int ipi = bit - 1; switch (ipi) { case IPI_AST: pic_ipi_ast(priv); break; case IPI_NOP: pic_ipi_nop(priv); break; #ifdef __HAVE_PREEMPTION case IPI_KPREEMPT: pic_ipi_kpreempt(priv); break; #endif case IPI_XCALL: pic_ipi_xcall(priv); break; case IPI_GENERIC: pic_ipi_generic(priv); break; case IPI_SHOOTDOWN: pic_ipi_shootdown(priv); break; #ifdef DDB case IPI_DDB: pic_ipi_ddb(priv); break; #endif } ipimask &= ~__BIT(ipi); } return 1; } #endif /* MULTIPROCESSOR */ static int apple_intc_match(device_t parent, cfdata_t cf, void *aux) { struct fdt_attach_args * const faa = aux; return of_compatible_match(faa->faa_phandle, compat_data); } static void apple_intc_attach(device_t parent, device_t self, void *aux) { struct apple_intc_softc * const sc = device_private(self); struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; bus_addr_t addr; bus_size_t size; int error; if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { aprint_error(": couldn't get registers\n"); return; } sc->sc_dev = self; sc->sc_bst = faa->faa_bst; if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { aprint_error(": couldn't map registers\n"); return; } sc->sc_nirq = AIC_READ(sc, AIC_INFO) & AIC_INFO_NIRQ; aprint_naive("\n"); aprint_normal(": Apple AIC (%u IRQs, 1 FIQ)\n", sc->sc_nirq); KASSERT(sc->sc_nirq % 32 == 0); sc->sc_pic.pic_ops = &apple_intc_picops; sc->sc_pic.pic_maxsources = sc->sc_nirq; snprintf(sc->sc_pic.pic_name, sizeof(sc->sc_pic.pic_name), "AIC"); pic_add(&sc->sc_pic, 0); error = fdtbus_register_interrupt_controller(self, phandle, &apple_intc_fdt_funcs); if (error) { aprint_error_dev(self, "couldn't register with fdtbus: %d\n", error); return; } KASSERT(intc_softc == NULL); intc_softc = sc; arm_fdt_irq_set_handler(apple_intc_irq_handler); arm_fdt_fiq_set_handler(apple_intc_fiq_handler); KASSERT(ncpu != 0); sc->sc_cpuid = kmem_zalloc(sizeof(*sc->sc_cpuid) * ncpu, KM_SLEEP); sc->sc_pc = kmem_zalloc(sizeof(*sc->sc_pc) * ncpu, KM_SLEEP); CPU_INFO_ITERATOR cii; struct cpu_info *ci; for (CPU_INFO_FOREACH(cii, ci)) { const cpuid_t cpuno = cpu_index(ci); struct apple_intc_percpu * const pc = &sc->sc_pc[cpuno]; struct pic_softc * const pic = &pc->pc_pic; pc->pc_sc = sc; pc->pc_cpuid = cpuno; #ifdef MULTIPROCESSOR pic->pic_cpus = ci->ci_kcpuset; #endif pic->pic_ops = &apple_intc_localpicops; pic->pic_maxsources = 2; snprintf(pic->pic_name, sizeof(pic->pic_name), "AIC/%lu", cpuno); pic_add(pic, PIC_IRQBASE_ALLOC); #ifdef MULTIPROCESSOR intr_establish_xname(pic->pic_irqbase + LOCALPIC_SOURCE_IPI, IPL_HIGH, IST_LEVEL | IST_MPSAFE, apple_intc_ipi_handler, pc, "ipi"); #endif } apple_intc_cpu_init(&sc->sc_pic, curcpu()); } CFATTACH_DECL_NEW(apple_intc, sizeof(struct apple_intc_softc), apple_intc_match, apple_intc_attach, NULL, NULL);