/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Andrew Turner * Copyright (c) 2022 Michael J. Karels * Copyright (c) 2022 Kyle Evans * * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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_platform.h" #include #include #include #include #include #include #include #include #include #include #ifdef SMP #include #include #endif #include #include #include #include #include #include "pic_if.h" #define AIC_INFO 0x0004 #define AIC_INFO_NDIE(val) (((val) >> 24) & 0xf) #define AIC_INFO_NIRQS(val) ((val) & 0x0000ffff) #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 0x00000001 #define AIC_IPI_SELF 0x80000000 #define AIC_TARGET_CPU(irq) (0x3000 + (irq) * 4) #define AIC_SW_SET(irq) (0x4000 + (((irq) >> 5) * 4)) #define AIC_SW_CLEAR(irq) (0x4080 + (((irq) >> 5) * 4)) #define AIC_MASK_SET(irq) (0x4100 + (((irq) >> 5) * 4)) #define AIC_MASK_CLEAR(irq) (0x4180 + (((irq) >> 5) * 4)) #define AIC_IRQ_MASK(irq) (1u << ((irq) & 0x1f)) #define AIC_IPI_LOCAL_RR_EL1 s3_5_c15_c0_0 #define AIC_IPI_GLOBAL_RR_EL1 s3_5_c15_c0_1 #define AIC_IPI_SR_EL1 s3_5_c15_c1_1 #define AIC_IPI_SR_EL1_PENDING (1 << 0) #define AIC_FIQ_VM_TIMER s3_5_c15_c1_3 #define AIC_FIQ_VM_TIMER_VEN (1 << 0) #define AIC_FIQ_VM_TIMER_PEN (1 << 1) #define AIC_FIQ_VM_TIMER_BITS (AIC_FIQ_VM_TIMER_VEN | AIC_FIQ_VM_TIMER_PEN) #define CNTV_CTL_ENABLE (1 << 0) #define CNTV_CTL_IMASK (1 << 1) #define CNTV_CTL_ISTATUS (1 << 2) #define CNTV_CTL_BITS \ (CNTV_CTL_ENABLE | CNTV_CTL_IMASK | CNTV_CTL_ISTATUS) #define AIC_MAXCPUS 32 #define AIC_MAXDIES 4 static struct ofw_compat_data compat_data[] = { { "apple,aic", 1 }, { NULL, 0 } }; enum apple_aic_irq_type { AIC_TYPE_INVAL, AIC_TYPE_IRQ, AIC_TYPE_FIQ, AIC_TYPE_IPI, }; struct apple_aic_irqsrc { struct intr_irqsrc ai_isrc; enum apple_aic_irq_type ai_type; struct { /* AIC_TYPE_IRQ */ enum intr_polarity ai_pol; enum intr_trigger ai_trig; u_int ai_irq; }; }; #ifdef SMP #define AIC_NIPIS INTR_IPI_COUNT #endif struct apple_aic_softc { device_t sc_dev; struct resource *sc_mem; struct apple_aic_irqsrc *sc_isrcs[AIC_MAXDIES]; u_int sc_nirqs; u_int sc_ndie; #ifdef SMP struct apple_aic_irqsrc sc_ipi_srcs[AIC_NIPIS]; u_int *sc_cpuids; /* cpu index to AIC CPU ID */ uint32_t *sc_ipimasks; #endif }; static u_int aic_next_cpu; static device_probe_t apple_aic_probe; static device_attach_t apple_aic_attach; static pic_disable_intr_t apple_aic_disable_intr; static pic_enable_intr_t apple_aic_enable_intr; static pic_map_intr_t apple_aic_map_intr; static pic_setup_intr_t apple_aic_setup_intr; static pic_teardown_intr_t apple_aic_teardown_intr; static pic_post_filter_t apple_aic_post_filter; static pic_post_ithread_t apple_aic_post_ithread; static pic_pre_ithread_t apple_aic_pre_ithread; #ifdef SMP static pic_bind_intr_t apple_aic_bind_intr; static pic_init_secondary_t apple_aic_init_secondary; static pic_ipi_send_t apple_aic_ipi_send; static pic_ipi_setup_t apple_aic_ipi_setup; #endif static int apple_aic_irq(void *); static int apple_aic_fiq(void *); static int apple_aic_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Apple Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static int apple_aic_attach(device_t dev) { struct apple_aic_softc *sc; struct intr_irqsrc *isrc; const char *name; intptr_t xref; int error, rid; u_int i, cpu, j, info; sc = device_get_softc(dev); sc->sc_dev = dev; rid = 0; sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem == NULL) { device_printf(dev, "Unable to allocate memory\n"); return (ENXIO); } info = bus_read_4(sc->sc_mem, AIC_INFO); sc->sc_nirqs = AIC_INFO_NIRQS(info); sc->sc_ndie = AIC_INFO_NDIE(info) + 1; if (bootverbose) device_printf(dev, "Found %d interrupts, %d die\n", sc->sc_nirqs, sc->sc_ndie); for (i = 0; i < sc->sc_ndie; i++) { sc->sc_isrcs[i] = mallocarray(sc->sc_nirqs, sizeof(**sc->sc_isrcs), M_DEVBUF, M_WAITOK | M_ZERO); } #ifdef SMP sc->sc_ipimasks = malloc(sizeof(*sc->sc_ipimasks) * mp_maxid + 1, M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_cpuids = malloc(sizeof(*sc->sc_cpuids) * mp_maxid + 1, M_DEVBUF, M_WAITOK | M_ZERO); cpu = PCPU_GET(cpuid); sc->sc_cpuids[cpu] = bus_read_4(sc->sc_mem, AIC_WHOAMI); if (bootverbose) device_printf(dev, "BSP CPU %d: whoami %x\n", cpu, sc->sc_cpuids[cpu]); #endif name = device_get_nameunit(dev); for (i = 0; i < sc->sc_ndie; i++) { struct apple_aic_irqsrc *die_isrcs; die_isrcs = sc->sc_isrcs[i]; for (j = 0; j < sc->sc_nirqs; j++) { isrc = &die_isrcs[j].ai_isrc; die_isrcs[j].ai_pol = INTR_POLARITY_CONFORM; die_isrcs[j].ai_trig = INTR_TRIGGER_CONFORM; die_isrcs[j].ai_type = AIC_TYPE_INVAL; die_isrcs[j].ai_irq = j; error = intr_isrc_register(isrc, dev, 0, "%s,d%us%u", name, i, j); if (error != 0) { device_printf(dev, "Unable to register irq %u:%u\n", i, j); return (error); } } } xref = OF_xref_from_node(ofw_bus_get_node(dev)); if (intr_pic_register(dev, xref) == NULL) { device_printf(dev, "Unable to register interrupt handler\n"); return (ENXIO); } if (intr_pic_claim_root(dev, xref, apple_aic_irq, sc, INTR_ROOT_IRQ) != 0) { device_printf(dev, "Unable to set root interrupt controller\n"); intr_pic_deregister(dev, xref); return (ENXIO); } if (intr_pic_claim_root(dev, xref, apple_aic_fiq, sc, INTR_ROOT_FIQ) != 0) { device_printf(dev, "Unable to set root fiq controller\n"); intr_pic_deregister(dev, xref); return (ENXIO); } #ifdef SMP if (intr_ipi_pic_register(dev, 0) != 0) { device_printf(dev, "could not register for IPIs\n"); return (ENXIO); } #endif OF_device_register_xref(xref, dev); return (0); } static int apple_aic_map_intr_fdt(struct apple_aic_softc *sc, struct intr_map_data_fdt *data, u_int *irq, enum apple_aic_irq_type *typep, enum intr_polarity *polp, enum intr_trigger *trigp, u_int *die) { if (data->ncells != 3) return (EINVAL); /* XXX AIC2 */ *die = 0; /* * The first cell is the interrupt type: * 0 = IRQ * 1 = FIQ * The second cell is the interrupt number * The third cell is the flags */ switch(data->cells[0]) { case 0: if (typep != NULL) *typep = AIC_TYPE_IRQ; break; case 1: if (typep != NULL) *typep = AIC_TYPE_FIQ; break; default: return (EINVAL); } *irq = data->cells[1]; if (*irq > sc->sc_nirqs) return (EINVAL); if (trigp != NULL) { if ((data->cells[2] & FDT_INTR_EDGE_MASK) != 0) *trigp = INTR_TRIGGER_EDGE; else *trigp = INTR_TRIGGER_LEVEL; } if (polp != NULL) { if ((data->cells[2] & FDT_INTR_LEVEL_HIGH) != 0) *polp = INTR_POLARITY_HIGH; else *polp = INTR_POLARITY_LOW; } return (0); } static int apple_aic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { struct apple_aic_softc *sc; int error; u_int irq; u_int die; sc = device_get_softc(dev); error = 0; switch(data->type) { case INTR_MAP_DATA_FDT: error = apple_aic_map_intr_fdt(sc, (struct intr_map_data_fdt *)data, &irq, NULL, NULL, NULL, &die); if (error == 0) *isrcp = &sc->sc_isrcs[0 /* XXX */][irq].ai_isrc; break; default: return (ENOTSUP); } return (error); } static int apple_aic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct apple_aic_softc *sc; enum apple_aic_irq_type type; struct apple_aic_irqsrc *ai; enum intr_trigger trig; enum intr_polarity pol; int error; u_int die, irq; sc = device_get_softc(dev); ai = (struct apple_aic_irqsrc *)isrc; if (data != NULL) { KASSERT(data->type == INTR_MAP_DATA_FDT, ("%s: Only FDT data is supported (got %#x)", __func__, data->type)); error = apple_aic_map_intr_fdt(sc, (struct intr_map_data_fdt *)data, &irq, &type, &pol, &trig, &die); if (error != 0) return (error); } else { pol = INTR_POLARITY_CONFORM; trig = INTR_TRIGGER_CONFORM; } if (isrc->isrc_handlers != 0) { /* TODO */ return (0); } if (pol == INTR_POLARITY_CONFORM) pol = INTR_POLARITY_LOW; if (trig == INTR_TRIGGER_CONFORM) trig = INTR_TRIGGER_EDGE; ai->ai_pol = pol; ai->ai_trig = trig; ai->ai_type = type; /* * Only the timer uses FIQs. These could be sent to any CPU. */ switch (type) { case AIC_TYPE_IRQ: /* XXX die sensitive? */ aic_next_cpu = intr_irq_next_cpu(aic_next_cpu, &all_cpus); bus_write_4(sc->sc_mem, AIC_TARGET_CPU(irq), 1 << sc->sc_cpuids[aic_next_cpu]); break; case AIC_TYPE_FIQ: isrc->isrc_flags |= INTR_ISRCF_PPI; break; default: return (EINVAL); } return (0); } static int apple_aic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { panic("%s\n", __func__); } static void apple_aic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct apple_aic_irqsrc *ai; struct apple_aic_softc *sc; u_int irq; ai = (struct apple_aic_irqsrc *)isrc; irq = ai->ai_irq; switch(ai->ai_type) { case AIC_TYPE_IRQ: sc = device_get_softc(dev); bus_write_4(sc->sc_mem, AIC_MASK_CLEAR(irq), AIC_IRQ_MASK(irq)); break; case AIC_TYPE_IPI: /* Nothing needed here. */ break; case AIC_TYPE_FIQ: /* TODO */ break; default: panic("%s: %x\n", __func__, ai->ai_type); } } static void apple_aic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct apple_aic_irqsrc *ai; struct apple_aic_softc *sc; u_int irq; ai = (struct apple_aic_irqsrc *)isrc; irq = ai->ai_irq; switch(ai->ai_type) { case AIC_TYPE_IRQ: sc = device_get_softc(dev); bus_write_4(sc->sc_mem, AIC_MASK_SET(irq), AIC_IRQ_MASK(irq)); break; case AIC_TYPE_IPI: /* Nothing needed here. */ break; case AIC_TYPE_FIQ: /* TODO */ break; default: panic("%s: %x\n", __func__, ai->ai_type); } } static void apple_aic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct apple_aic_softc *sc; struct apple_aic_irqsrc *ai; int irq; ai = (struct apple_aic_irqsrc *)isrc; irq = ai->ai_irq; switch(ai->ai_type) { case AIC_TYPE_IRQ: sc = device_get_softc(dev); bus_write_4(sc->sc_mem, AIC_SW_CLEAR(irq), AIC_IRQ_MASK(irq)); bus_write_4(sc->sc_mem, AIC_MASK_CLEAR(irq), AIC_IRQ_MASK(irq)); break; case AIC_TYPE_FIQ: /* TODO */ break; default: panic("%s: %x\n", __func__, ai->ai_type); } } static void apple_aic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct apple_aic_softc *sc; struct apple_aic_irqsrc *ai; int irq; ai = (struct apple_aic_irqsrc *)isrc; sc = device_get_softc(dev); irq = ai->ai_irq; bus_write_4(sc->sc_mem, AIC_SW_CLEAR(irq), AIC_IRQ_MASK(irq)); apple_aic_disable_intr(dev, isrc); /* ACK IT */ } static void apple_aic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct apple_aic_softc *sc; struct apple_aic_irqsrc *ai; int irq; ai = (struct apple_aic_irqsrc *)isrc; sc = device_get_softc(dev); irq = ai->ai_irq; bus_write_4(sc->sc_mem, AIC_MASK_CLEAR(irq), AIC_IRQ_MASK(irq)); apple_aic_enable_intr(dev, isrc); } #ifdef SMP static void apple_aic_ipi_received(struct apple_aic_softc *sc, struct trapframe *tf) { uint32_t mask; uint32_t ipi; int cpu; cpu = PCPU_GET(cpuid); mask = atomic_readandclear_32(&sc->sc_ipimasks[cpu]); while (mask != 0) { ipi = ffs(mask) - 1; mask &= ~(1 << ipi); intr_ipi_dispatch(ipi); } } #endif static int apple_aic_irq(void *arg) { struct apple_aic_softc *sc; uint32_t die, event, irq, type; struct apple_aic_irqsrc *aisrc; struct trapframe *tf; sc = arg; tf = curthread->td_intr_frame; event = bus_read_4(sc->sc_mem, AIC_EVENT); type = AIC_EVENT_TYPE(event); /* If we get an IPI here, we really goofed. */ MPASS(type != AIC_EVENT_TYPE_IPI); if (type != AIC_EVENT_TYPE_IRQ) { if (type != AIC_EVENT_TYPE_NONE) device_printf(sc->sc_dev, "unexpected event type %d\n", type); return (FILTER_STRAY); } die = AIC_EVENT_DIE(event); irq = AIC_EVENT_IRQ(event); if (die >= sc->sc_ndie) panic("%s: unexpected die %d", __func__, die); if (irq >= sc->sc_nirqs) panic("%s: unexpected irq %d", __func__, irq); aisrc = &sc->sc_isrcs[die][irq]; if (intr_isrc_dispatch(&aisrc->ai_isrc, tf) != 0) { device_printf(sc->sc_dev, "Stray irq %u:%u disabled\n", die, irq); return (FILTER_STRAY); } return (FILTER_HANDLED); } static int apple_aic_fiq(void *arg) { struct apple_aic_softc *sc; struct apple_aic_irqsrc *isrcs; struct trapframe *tf; sc = arg; tf = curthread->td_intr_frame; #ifdef SMP /* Handle IPIs. */ if ((READ_SPECIALREG(AIC_IPI_SR_EL1) & AIC_IPI_SR_EL1_PENDING) != 0) { WRITE_SPECIALREG(AIC_IPI_SR_EL1, AIC_IPI_SR_EL1_PENDING); apple_aic_ipi_received(sc, tf); } #endif /* * FIQs don't store any state in the interrupt controller at all outside * of IPI handling, so we have to probe around outside of AIC to * determine if we might have been fired off due to a timer. */ isrcs = sc->sc_isrcs[0]; if ((READ_SPECIALREG(cntv_ctl_el0) & CNTV_CTL_BITS) == (CNTV_CTL_ENABLE | CNTV_CTL_ISTATUS)) { intr_isrc_dispatch(&isrcs[AIC_TMR_GUEST_VIRT].ai_isrc, tf); } if (has_hyp()) { uint64_t reg; if ((READ_SPECIALREG(cntp_ctl_el0) & CNTV_CTL_ISTATUS) != 0) { intr_isrc_dispatch(&isrcs[AIC_TMR_GUEST_PHYS].ai_isrc, tf); } reg = READ_SPECIALREG(AIC_FIQ_VM_TIMER); if ((reg & AIC_FIQ_VM_TIMER_PEN) != 0) { intr_isrc_dispatch(&isrcs[AIC_TMR_HV_PHYS].ai_isrc, tf); } if ((reg & AIC_FIQ_VM_TIMER_VEN) != 0) { intr_isrc_dispatch(&isrcs[AIC_TMR_HV_VIRT].ai_isrc, tf); } } return (FILTER_HANDLED); } #ifdef SMP static int apple_aic_bind_intr(device_t dev, struct intr_irqsrc *isrc) { struct apple_aic_softc *sc = device_get_softc(dev); static int aic_next_cpu; uint32_t targets = 0; u_int irq, cpu; MPASS(((struct apple_aic_irqsrc *)isrc)->ai_type == AIC_TYPE_IRQ); irq = ((struct apple_aic_irqsrc *)isrc)->ai_irq; if (CPU_EMPTY(&isrc->isrc_cpu)) { aic_next_cpu = intr_irq_next_cpu(aic_next_cpu, &all_cpus); CPU_SETOF(aic_next_cpu, &isrc->isrc_cpu); bus_write_4(sc->sc_mem, AIC_TARGET_CPU(irq), sc->sc_cpuids[aic_next_cpu] << 1); } else { CPU_FOREACH_ISSET(cpu, &isrc->isrc_cpu) { targets |= sc->sc_cpuids[cpu] << 1; } bus_write_4(sc->sc_mem, AIC_TARGET_CPU(irq), targets); } return (0); } static void apple_aic_ipi_send(device_t dev, struct intr_irqsrc *isrc, cpuset_t cpus, u_int ipi) { struct apple_aic_softc *sc; uint64_t aff, localgrp, sendmask; u_int cpu; sc = device_get_softc(dev); sendmask = 0; localgrp = CPU_AFF1(CPU_AFFINITY(PCPU_GET(cpuid))); KASSERT(isrc == &sc->sc_ipi_srcs[ipi].ai_isrc, ("%s: bad ISRC %p argument", __func__, isrc)); for (cpu = 0; cpu <= mp_maxid; cpu++) { if (CPU_ISSET(cpu, &cpus)) { aff = CPU_AFFINITY(cpu); sendmask = CPU_AFF0(aff); atomic_set_32(&sc->sc_ipimasks[cpu], 1 << ipi); /* * The above write to sc_ipimasks needs to be visible * before we write to the ipi register to avoid the * targetted CPU missing the dispatch in * apple_aic_ipi_received(). Note that WRITE_SPECIALREG * isn't a memory operation, so we can't relax this to a * a dmb. */ dsb(ishst); if (CPU_AFF1(aff) == localgrp) { WRITE_SPECIALREG(AIC_IPI_LOCAL_RR_EL1, sendmask); } else { sendmask |= CPU_AFF1(aff) << 16; WRITE_SPECIALREG(AIC_IPI_GLOBAL_RR_EL1, sendmask); } isb(); } } } static int apple_aic_ipi_setup(device_t dev, u_int ipi, struct intr_irqsrc **isrcp) { struct apple_aic_softc *sc = device_get_softc(dev); struct apple_aic_irqsrc *ai; KASSERT(ipi < AIC_NIPIS, ("%s: ipi %u too high", __func__, ipi)); ai = &sc->sc_ipi_srcs[ipi]; ai->ai_type = AIC_TYPE_IPI; *isrcp = &ai->ai_isrc; return (0); } static void apple_aic_init_secondary(device_t dev, uint32_t rootnum) { struct apple_aic_softc *sc = device_get_softc(dev); u_int cpu = PCPU_GET(cpuid); /* We don't need to re-initialize for the FIQ root. */ if (rootnum != INTR_ROOT_IRQ) return; sc->sc_cpuids[cpu] = bus_read_4(sc->sc_mem, AIC_WHOAMI); if (bootverbose) device_printf(dev, "CPU %d: whoami %x\n", cpu, sc->sc_cpuids[cpu]); bus_write_4(sc->sc_mem, AIC_IPI_MASK_SET, AIC_IPI_SELF | AIC_IPI_OTHER); } #endif static device_method_t apple_aic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, apple_aic_probe), DEVMETHOD(device_attach, apple_aic_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, apple_aic_disable_intr), DEVMETHOD(pic_enable_intr, apple_aic_enable_intr), DEVMETHOD(pic_map_intr, apple_aic_map_intr), DEVMETHOD(pic_setup_intr, apple_aic_setup_intr), DEVMETHOD(pic_teardown_intr, apple_aic_teardown_intr), DEVMETHOD(pic_post_filter, apple_aic_post_filter), DEVMETHOD(pic_post_ithread, apple_aic_post_ithread), DEVMETHOD(pic_pre_ithread, apple_aic_pre_ithread), #ifdef SMP DEVMETHOD(pic_bind_intr, apple_aic_bind_intr), DEVMETHOD(pic_init_secondary, apple_aic_init_secondary), DEVMETHOD(pic_ipi_send, apple_aic_ipi_send), DEVMETHOD(pic_ipi_setup, apple_aic_ipi_setup), #endif /* End */ DEVMETHOD_END }; static DEFINE_CLASS_0(aic, apple_aic_driver, apple_aic_methods, sizeof(struct apple_aic_softc)); EARLY_DRIVER_MODULE(aic, simplebus, apple_aic_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);