/* $OpenBSD: ampintc.c,v 1.29 2022/07/16 12:07:55 kettenis Exp $ */ /* * Copyright (c) 2007,2009,2011 Dale Rahn * * 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. */ /* * This driver implements the interrupt controller as specified in * DDI0407E_cortex_a9_mpcore_r2p0_trm with the * IHI0048A_gic_architecture_spec_v1_0 underlying specification */ #include #include #include #include #include #include #include #include #include #include #include #include /* registers */ #define ICD_DCR 0x000 #define ICD_DCR_ES 0x00000001 #define ICD_DCR_ENS 0x00000002 #define ICD_ICTR 0x004 #define ICD_ICTR_LSPI_SH 11 #define ICD_ICTR_LSPI_M 0x1f #define ICD_ICTR_CPU_SH 5 #define ICD_ICTR_CPU_M 0x07 #define ICD_ICTR_ITL_SH 0 #define ICD_ICTR_ITL_M 0x1f #define ICD_IDIR 0x008 #define ICD_DIR_PROD_SH 24 #define ICD_DIR_PROD_M 0xff #define ICD_DIR_REV_SH 12 #define ICD_DIR_REV_M 0xfff #define ICD_DIR_IMP_SH 0 #define ICD_DIR_IMP_M 0xfff #define IRQ_TO_REG32(i) (((i) >> 5) & 0x1f) #define IRQ_TO_REG32BIT(i) ((i) & 0x1f) #define IRQ_TO_REG4(i) (((i) >> 2) & 0xff) #define IRQ_TO_REG4BIT(i) ((i) & 0x3) #define IRQ_TO_REG16(i) (((i) >> 4) & 0x3f) #define IRQ_TO_REG16BIT(i) ((i) & 0xf) #define IRQ_TO_REGBIT_S(i) 8 #define IRQ_TO_REG4BIT_M(i) 8 #define ICD_ISRn(i) (0x080 + (IRQ_TO_REG32(i) * 4)) #define ICD_ISERn(i) (0x100 + (IRQ_TO_REG32(i) * 4)) #define ICD_ICERn(i) (0x180 + (IRQ_TO_REG32(i) * 4)) #define ICD_ISPRn(i) (0x200 + (IRQ_TO_REG32(i) * 4)) #define ICD_ICPRn(i) (0x280 + (IRQ_TO_REG32(i) * 4)) #define ICD_ABRn(i) (0x300 + (IRQ_TO_REG32(i) * 4)) #define ICD_IPRn(i) (0x400 + (i)) #define ICD_IPTRn(i) (0x800 + (i)) #define ICD_ICRn(i) (0xC00 + (IRQ_TO_REG16(i) * 4)) #define ICD_ICR_TRIG_LEVEL(i) (0x0 << (IRQ_TO_REG16BIT(i) * 2)) #define ICD_ICR_TRIG_EDGE(i) (0x2 << (IRQ_TO_REG16BIT(i) * 2)) #define ICD_ICR_TRIG_MASK(i) (0x2 << (IRQ_TO_REG16BIT(i) * 2)) /* * what about (ppi|spi)_status */ #define ICD_PPI 0xD00 #define ICD_PPI_GTIMER (1 << 11) #define ICD_PPI_FIQ (1 << 12) #define ICD_PPI_PTIMER (1 << 13) #define ICD_PPI_PWDOG (1 << 14) #define ICD_PPI_IRQ (1 << 15) #define ICD_SPI_BASE 0xD04 #define ICD_SPIn(i) (ICD_SPI_BASE + ((i) * 4)) #define ICD_SGIR 0xF00 #define ICD_PERIPH_ID_0 0xFD0 #define ICD_PERIPH_ID_1 0xFD4 #define ICD_PERIPH_ID_2 0xFD8 #define ICD_PERIPH_ID_3 0xFDC #define ICD_PERIPH_ID_4 0xFE0 #define ICD_PERIPH_ID_5 0xFE4 #define ICD_PERIPH_ID_6 0xFE8 #define ICD_PERIPH_ID_7 0xFEC #define ICD_COMP_ID_0 0xFEC #define ICD_COMP_ID_1 0xFEC #define ICD_COMP_ID_2 0xFEC #define ICD_COMP_ID_3 0xFEC #define ICPICR 0x00 #define ICPIPMR 0x04 /* XXX - must left justify bits to 0 - 7 */ #define ICMIPMR_SH 4 #define ICPBPR 0x08 #define ICPIAR 0x0C #define ICPIAR_IRQ_SH 0 #define ICPIAR_IRQ_M 0x3ff #define ICPIAR_CPUID_SH 10 #define ICPIAR_CPUID_M 0x7 #define ICPIAR_NO_PENDING_IRQ ICPIAR_IRQ_M #define ICPEOIR 0x10 #define ICPPRP 0x14 #define ICPHPIR 0x18 #define ICPIIR 0xFC /* * what about periph_id and component_id */ #define IRQ_ENABLE 1 #define IRQ_DISABLE 0 struct ampintc_softc { struct simplebus_softc sc_sbus; struct intrq *sc_handler; int sc_nintr; bus_space_tag_t sc_iot; bus_space_handle_t sc_d_ioh, sc_p_ioh; uint8_t sc_cpu_mask[ICD_ICTR_CPU_M + 1]; struct evcount sc_spur; struct interrupt_controller sc_ic; int sc_ipi_reason[ICD_ICTR_CPU_M + 1]; int sc_ipi_num[3]; }; struct ampintc_softc *ampintc; struct intrhand { TAILQ_ENTRY(intrhand) ih_list; /* link on intrq list */ int (*ih_func)(void *); /* handler */ void *ih_arg; /* arg for handler */ int ih_ipl; /* IPL_* */ int ih_flags; int ih_irq; /* IRQ number */ struct evcount ih_count; char *ih_name; struct cpu_info *ih_ci; /* CPU the IRQ runs on */ }; struct intrq { TAILQ_HEAD(, intrhand) iq_list; /* handler list */ struct cpu_info *iq_ci; /* CPU the IRQ runs on */ int iq_irq_max; /* IRQ to mask while handling */ int iq_irq_min; /* lowest IRQ when shared */ int iq_ist; /* share type */ }; int ampintc_match(struct device *, void *, void *); void ampintc_attach(struct device *, struct device *, void *); int ampintc_activate(struct device *, int); void ampintc_init(struct ampintc_softc *); void ampintc_cpuinit(void); int ampintc_spllower(int); void ampintc_splx(int); int ampintc_splraise(int); void ampintc_setipl(int); void ampintc_calc_mask(void); void ampintc_calc_irq(struct ampintc_softc *, int); void *ampintc_intr_establish(int, int, int, struct cpu_info *, int (*)(void *), void *, char *); void *ampintc_intr_establish_fdt(void *, int *, int, struct cpu_info *, int (*)(void *), void *, char *); void ampintc_intr_disestablish(void *); void ampintc_irq_handler(void *); const char *ampintc_intr_string(void *); uint32_t ampintc_iack(void); void ampintc_eoi(uint32_t); void ampintc_set_priority(int, int); void ampintc_intr_enable(int); void ampintc_intr_disable(int); void ampintc_intr_config(int, int); void ampintc_route(int, int, struct cpu_info *); void ampintc_route_irq(void *, int, struct cpu_info *); void ampintc_intr_barrier(void *); int ampintc_ipi_combined(void *); int ampintc_ipi_nop(void *); int ampintc_ipi_ddb(void *); int ampintc_ipi_halt(void *); void ampintc_send_ipi(struct cpu_info *, int); const struct cfattach ampintc_ca = { sizeof (struct ampintc_softc), ampintc_match, ampintc_attach, NULL, ampintc_activate }; struct cfdriver ampintc_cd = { NULL, "ampintc", DV_DULL }; static char *ampintc_compatibles[] = { "arm,cortex-a7-gic", "arm,cortex-a9-gic", "arm,cortex-a15-gic", "arm,gic-400", NULL }; int ampintc_match(struct device *parent, void *cfdata, void *aux) { struct fdt_attach_args *faa = aux; int i; for (i = 0; ampintc_compatibles[i]; i++) if (OF_is_compatible(faa->fa_node, ampintc_compatibles[i])) return (1); return (0); } void ampintc_attach(struct device *parent, struct device *self, void *aux) { struct ampintc_softc *sc = (struct ampintc_softc *)self; struct fdt_attach_args *faa = aux; int i, nintr, ncpu; uint32_t ictr; #ifdef MULTIPROCESSOR int nipi, ipiirq[3]; #endif ampintc = sc; arm_init_smask(); sc->sc_iot = faa->fa_iot; /* First row: ICD */ if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_d_ioh)) panic("%s: ICD bus_space_map failed!", __func__); /* Second row: ICP */ if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr, faa->fa_reg[1].size, 0, &sc->sc_p_ioh)) panic("%s: ICP bus_space_map failed!", __func__); evcount_attach(&sc->sc_spur, "irq1023/spur", NULL); ictr = bus_space_read_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICTR); nintr = 32 * ((ictr >> ICD_ICTR_ITL_SH) & ICD_ICTR_ITL_M); nintr += 32; /* ICD_ICTR + 1, irq 0-31 is SGI, 32+ is PPI */ sc->sc_nintr = nintr; ncpu = ((ictr >> ICD_ICTR_CPU_SH) & ICD_ICTR_CPU_M) + 1; printf(" nirq %d, ncpu %d", nintr, ncpu); KASSERT(curcpu()->ci_cpuid <= ICD_ICTR_CPU_M); sc->sc_cpu_mask[curcpu()->ci_cpuid] = bus_space_read_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPTRn(0)); ampintc_init(sc); /* software reset of the part? */ /* set protection bit (kernel only)? */ /* XXX - check power saving bit */ sc->sc_handler = mallocarray(nintr, sizeof(*sc->sc_handler), M_DEVBUF, M_ZERO | M_NOWAIT); for (i = 0; i < nintr; i++) { TAILQ_INIT(&sc->sc_handler[i].iq_list); } ampintc_setipl(IPL_HIGH); /* XXX ??? */ ampintc_calc_mask(); /* insert self as interrupt handler */ arm_set_intr_handler(ampintc_splraise, ampintc_spllower, ampintc_splx, ampintc_setipl, ampintc_irq_handler, NULL); #ifdef MULTIPROCESSOR /* setup IPI interrupts */ /* * Ideally we want three IPI interrupts, one for NOP, one for * DDB and one for HALT. However we can survive if only one * is available; it is possible that most are not available to * the non-secure OS. */ nipi = 0; for (i = 0; i < 16; i++) { int reg, oldreg; oldreg = bus_space_read_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPRn(i)); bus_space_write_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPRn(i), oldreg ^ 0x20); /* if this interrupt is not usable, route will be zero */ reg = bus_space_read_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPRn(i)); if (reg == oldreg) continue; /* return to original value, will be set when used */ bus_space_write_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPRn(i), oldreg); if (nipi == 0) printf(" ipi: %d", i); else printf(", %d", i); ipiirq[nipi++] = i; if (nipi == 3) break; } if (nipi == 0) panic ("no irq available for IPI"); switch (nipi) { case 1: ampintc_intr_establish(ipiirq[0], IST_EDGE_RISING, IPL_IPI|IPL_MPSAFE, NULL, ampintc_ipi_combined, sc, "ipi"); sc->sc_ipi_num[ARM_IPI_NOP] = ipiirq[0]; sc->sc_ipi_num[ARM_IPI_DDB] = ipiirq[0]; sc->sc_ipi_num[ARM_IPI_HALT] = ipiirq[0]; break; case 2: ampintc_intr_establish(ipiirq[0], IST_EDGE_RISING, IPL_IPI|IPL_MPSAFE, NULL, ampintc_ipi_nop, sc, "ipinop"); sc->sc_ipi_num[ARM_IPI_NOP] = ipiirq[0]; ampintc_intr_establish(ipiirq[1], IST_EDGE_RISING, IPL_IPI|IPL_MPSAFE, NULL, ampintc_ipi_combined, sc, "ipi"); sc->sc_ipi_num[ARM_IPI_DDB] = ipiirq[1]; sc->sc_ipi_num[ARM_IPI_HALT] = ipiirq[1]; break; case 3: ampintc_intr_establish(ipiirq[0], IST_EDGE_RISING, IPL_IPI|IPL_MPSAFE, NULL, ampintc_ipi_nop, sc, "ipinop"); sc->sc_ipi_num[ARM_IPI_NOP] = ipiirq[0]; ampintc_intr_establish(ipiirq[1], IST_EDGE_RISING, IPL_IPI|IPL_MPSAFE, NULL, ampintc_ipi_ddb, sc, "ipiddb"); sc->sc_ipi_num[ARM_IPI_DDB] = ipiirq[1]; ampintc_intr_establish(ipiirq[2], IST_EDGE_RISING, IPL_IPI|IPL_MPSAFE, NULL, ampintc_ipi_halt, sc, "ipihalt"); sc->sc_ipi_num[ARM_IPI_HALT] = ipiirq[2]; break; default: panic("nipi unexpected number %d", nipi); } intr_send_ipi_func = ampintc_send_ipi; #endif /* enable interrupts */ bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_DCR, 3); bus_space_write_4(sc->sc_iot, sc->sc_p_ioh, ICPICR, 1); intr_enable(); sc->sc_ic.ic_node = faa->fa_node; sc->sc_ic.ic_cookie = self; sc->sc_ic.ic_establish = ampintc_intr_establish_fdt; sc->sc_ic.ic_disestablish = ampintc_intr_disestablish; sc->sc_ic.ic_route = ampintc_route_irq; sc->sc_ic.ic_cpu_enable = ampintc_cpuinit; sc->sc_ic.ic_barrier = ampintc_intr_barrier; arm_intr_register_fdt(&sc->sc_ic); /* attach GICv2M frame controller */ simplebus_attach(parent, &sc->sc_sbus.sc_dev, faa); } int ampintc_activate(struct device *self, int act) { struct ampintc_softc *sc = (struct ampintc_softc *)self; struct cpu_info *ci; int irq, min; switch (act) { case DVACT_RESUME: for (irq = 0; irq < sc->sc_nintr; irq++) { ci = sc->sc_handler[irq].iq_ci; min = sc->sc_handler[irq].iq_irq_min; if (min != IPL_NONE) { ampintc_set_priority(irq, min); ampintc_intr_enable(irq); ampintc_route(irq, IRQ_ENABLE, ci); } else { ampintc_intr_disable(irq); } } /* enable interrupts */ bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_DCR, 3); bus_space_write_4(sc->sc_iot, sc->sc_p_ioh, ICPICR, 1); break; } return 0; } void ampintc_init(struct ampintc_softc *sc) { int i; /* Disable all interrupts, clear all pending */ for (i = 0; i < sc->sc_nintr / 32; i++) { bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICERn(i * 32), ~0); bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICPRn(i * 32), ~0); } for (i = 0; i < sc->sc_nintr; i++) { /* lowest priority ?? */ bus_space_write_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPRn(i), 0xff); /* target no cpus */ bus_space_write_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPTRn(i), 0); } for (i = 2; i < sc->sc_nintr / 16; i++) { /* irq 32 - N */ bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICRn(i * 16), 0); } } void ampintc_set_priority(int irq, int pri) { struct ampintc_softc *sc = ampintc; uint32_t prival; /* * We only use 16 (13 really) interrupt priorities, * and a CPU is only required to implement bit 4-7 of each field * so shift into the top bits. * also low values are higher priority thus IPL_HIGH - pri */ prival = (IPL_HIGH - pri) << ICMIPMR_SH; bus_space_write_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPRn(irq), prival); } void ampintc_setipl(int new) { struct cpu_info *ci = curcpu(); struct ampintc_softc *sc = ampintc; u_long psw; /* disable here is only to keep hardware in sync with ci->ci_cpl */ psw = intr_disable(); ci->ci_cpl = new; /* low values are higher priority thus IPL_HIGH - pri */ bus_space_write_4(sc->sc_iot, sc->sc_p_ioh, ICPIPMR, (IPL_HIGH - new) << ICMIPMR_SH); intr_restore(psw); } void ampintc_intr_enable(int irq) { struct ampintc_softc *sc = ampintc; #ifdef DEBUG printf("enable irq %d register %x bitmask %08x\n", irq, ICD_ISERn(irq), 1 << IRQ_TO_REG32BIT(irq)); #endif bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ISERn(irq), 1 << IRQ_TO_REG32BIT(irq)); } void ampintc_intr_disable(int irq) { struct ampintc_softc *sc = ampintc; bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICERn(irq), 1 << IRQ_TO_REG32BIT(irq)); } void ampintc_intr_config(int irqno, int type) { struct ampintc_softc *sc = ampintc; uint32_t ctrl; ctrl = bus_space_read_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICRn(irqno)); ctrl &= ~ICD_ICR_TRIG_MASK(irqno); if (type == IST_EDGE_RISING) ctrl |= ICD_ICR_TRIG_EDGE(irqno); else ctrl |= ICD_ICR_TRIG_LEVEL(irqno); bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICRn(irqno), ctrl); } void ampintc_calc_mask(void) { struct ampintc_softc *sc = ampintc; int irq; for (irq = 0; irq < sc->sc_nintr; irq++) ampintc_calc_irq(sc, irq); } void ampintc_calc_irq(struct ampintc_softc *sc, int irq) { struct cpu_info *ci = sc->sc_handler[irq].iq_ci; struct intrhand *ih; int max = IPL_NONE; int min = IPL_HIGH; TAILQ_FOREACH(ih, &sc->sc_handler[irq].iq_list, ih_list) { if (ih->ih_ipl > max) max = ih->ih_ipl; if (ih->ih_ipl < min) min = ih->ih_ipl; } if (max == IPL_NONE) min = IPL_NONE; if (sc->sc_handler[irq].iq_irq_max == max && sc->sc_handler[irq].iq_irq_min == min) return; sc->sc_handler[irq].iq_irq_max = max; sc->sc_handler[irq].iq_irq_min = min; /* Enable interrupts at lower levels, clear -> enable */ /* Set interrupt priority/enable */ if (min != IPL_NONE) { ampintc_set_priority(irq, min); ampintc_intr_enable(irq); ampintc_route(irq, IRQ_ENABLE, ci); } else { ampintc_intr_disable(irq); ampintc_route(irq, IRQ_DISABLE, ci); } } void ampintc_splx(int new) { struct cpu_info *ci = curcpu(); if (ci->ci_ipending & arm_smask[new]) arm_do_pending_intr(new); ampintc_setipl(new); } int ampintc_spllower(int new) { struct cpu_info *ci = curcpu(); int old = ci->ci_cpl; ampintc_splx(new); return (old); } int ampintc_splraise(int new) { struct cpu_info *ci = curcpu(); int old; old = ci->ci_cpl; /* * setipl must always be called because there is a race window * where the variable is updated before the mask is set * an interrupt occurs in that window without the mask always * being set, the hardware might not get updated on the next * splraise completely messing up spl protection. */ if (old > new) new = old; ampintc_setipl(new); return (old); } uint32_t ampintc_iack(void) { uint32_t intid; struct ampintc_softc *sc = ampintc; intid = bus_space_read_4(sc->sc_iot, sc->sc_p_ioh, ICPIAR); return (intid); } void ampintc_eoi(uint32_t eoi) { struct ampintc_softc *sc = ampintc; bus_space_write_4(sc->sc_iot, sc->sc_p_ioh, ICPEOIR, eoi); } void ampintc_route(int irq, int enable, struct cpu_info *ci) { struct ampintc_softc *sc = ampintc; uint8_t mask, val; KASSERT(ci->ci_cpuid <= ICD_ICTR_CPU_M); mask = sc->sc_cpu_mask[ci->ci_cpuid]; val = bus_space_read_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPTRn(irq)); if (enable == IRQ_ENABLE) val |= mask; else val &= ~mask; bus_space_write_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPTRn(irq), val); } void ampintc_cpuinit(void) { struct ampintc_softc *sc = ampintc; int i, irq; /* XXX - this is the only cpu specific call to set this */ if (sc->sc_cpu_mask[cpu_number()] == 0) { for (i = 0; i < 32; i++) { int cpumask = bus_space_read_1(sc->sc_iot, sc->sc_d_ioh, ICD_IPTRn(i)); if (cpumask != 0) { sc->sc_cpu_mask[cpu_number()] = cpumask; break; } } } if (sc->sc_cpu_mask[cpu_number()] == 0) panic("could not determine cpu target mask"); for (irq = 0; irq < sc->sc_nintr; irq++) { if (sc->sc_handler[irq].iq_ci != curcpu()) continue; if (sc->sc_handler[irq].iq_irq_min != IPL_NONE) ampintc_route(irq, IRQ_ENABLE, curcpu()); else ampintc_route(irq, IRQ_DISABLE, curcpu()); } /* * If a secondary CPU is turned off from an IPI handler and * the GIC did not go through a full reset (for example when * we fail to suspend) the IPI might still be active. So * signal EOI here to make sure new interrupts will be * serviced. */ ampintc_eoi(sc->sc_ipi_num[ARM_IPI_HALT]); } void ampintc_route_irq(void *v, int enable, struct cpu_info *ci) { struct ampintc_softc *sc = ampintc; struct intrhand *ih = v; bus_space_write_4(sc->sc_iot, sc->sc_p_ioh, ICPICR, 1); bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_ICRn(ih->ih_irq), 0); if (enable) { ampintc_set_priority(ih->ih_irq, sc->sc_handler[ih->ih_irq].iq_irq_min); ampintc_intr_enable(ih->ih_irq); } ampintc_route(ih->ih_irq, enable, ci); } void ampintc_intr_barrier(void *cookie) { struct intrhand *ih = cookie; sched_barrier(ih->ih_ci); } void ampintc_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 ampintc_irq_handler(void *frame) { struct ampintc_softc *sc = ampintc; struct intrhand *ih; uint32_t iack_val; int irq, pri, s; iack_val = ampintc_iack(); #ifdef DEBUG_INTC if (iack_val != 27) printf("irq %d fired\n", iack_val); else { static int cnt = 0; if ((cnt++ % 100) == 0) { printf("irq %d fired * _100\n", iack_val); #ifdef DDB db_enter(); #endif } } #endif irq = iack_val & ICPIAR_IRQ_M; if (irq == 1023) { sc->sc_spur.ec_count++; return; } if (irq >= sc->sc_nintr) return; pri = sc->sc_handler[irq].iq_irq_max; s = ampintc_splraise(pri); intr_enable(); TAILQ_FOREACH(ih, &sc->sc_handler[irq].iq_list, ih_list) { ampintc_run_handler(ih, frame, s); } intr_disable(); ampintc_eoi(iack_val); ampintc_splx(s); } void * ampintc_intr_establish_fdt(void *cookie, int *cell, int level, struct cpu_info *ci, int (*func)(void *), void *arg, char *name) { struct ampintc_softc *sc = (struct ampintc_softc *)cookie; int irq; int type; /* 2nd cell contains the interrupt number */ irq = cell[1]; /* 1st cell contains type: 0 SPI (32-X), 1 PPI (16-31) */ if (cell[0] == 0) irq += 32; else if (cell[0] == 1) irq += 16; else panic("%s: bogus interrupt type", sc->sc_sbus.sc_dev.dv_xname); /* SPIs are only active-high level or low-to-high edge */ if (cell[2] & 0x3) type = IST_EDGE_RISING; else type = IST_LEVEL_HIGH; return ampintc_intr_establish(irq, type, level, ci, func, arg, name); } void * ampintc_intr_establish(int irqno, int type, int level, struct cpu_info *ci, int (*func)(void *), void *arg, char *name) { struct ampintc_softc *sc = ampintc; struct intrhand *ih; u_long psw; if (irqno < 0 || irqno >= sc->sc_nintr) panic("ampintc_intr_establish: bogus irqnumber %d: %s", irqno, name); if (ci == NULL) ci = &cpu_info_primary; if (irqno < 16) { /* SGI are only EDGE */ type = IST_EDGE_RISING; } else if (irqno < 32) { /* PPI are only LEVEL */ type = IST_LEVEL_HIGH; } 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 = irqno; ih->ih_name = name; ih->ih_ci = ci; psw = intr_disable(); if (!TAILQ_EMPTY(&sc->sc_handler[irqno].iq_list) && sc->sc_handler[irqno].iq_ci != ci) { free(ih, M_DEVBUF, sizeof(*ih)); intr_restore(psw); return NULL; } TAILQ_INSERT_TAIL(&sc->sc_handler[irqno].iq_list, ih, ih_list); sc->sc_handler[irqno].iq_ci = ci; if (name != NULL) evcount_attach(&ih->ih_count, name, &ih->ih_irq); #ifdef DEBUG_INTC printf("ampintc_intr_establish irq %d level %d [%s]\n", irqno, level, name); #endif ampintc_intr_config(irqno, type); ampintc_calc_mask(); intr_restore(psw); return (ih); } void ampintc_intr_disestablish(void *cookie) { struct ampintc_softc *sc = ampintc; struct intrhand *ih = cookie; u_long psw; #ifdef DEBUG_INTC printf("ampintc_intr_disestablish irq %d level %d [%s]\n", ih->ih_irq, ih->ih_ipl, ih->ih_name); #endif psw = intr_disable(); TAILQ_REMOVE(&sc->sc_handler[ih->ih_irq].iq_list, ih, ih_list); if (ih->ih_name != NULL) evcount_detach(&ih->ih_count); ampintc_calc_mask(); intr_restore(psw); free(ih, M_DEVBUF, sizeof(*ih)); } const char * ampintc_intr_string(void *cookie) { struct intrhand *ih = (struct intrhand *)cookie; static char irqstr[1 + sizeof("ampintc irq ") + 4]; snprintf(irqstr, sizeof irqstr, "ampintc irq %d", ih->ih_irq); return irqstr; } /* * GICv2m frame controller for MSI interrupts. */ #define GICV2M_TYPER 0x008 #define GICV2M_TYPER_SPI_BASE(x) (((x) >> 16) & 0x3ff) #define GICV2M_TYPER_SPI_COUNT(x) (((x) >> 0) & 0x3ff) #define GICV2M_SETSPI_NS 0x040 int ampintc_msi_match(struct device *, void *, void *); void ampintc_msi_attach(struct device *, struct device *, void *); void *ampintc_intr_establish_msi(void *, uint64_t *, uint64_t *, int , struct cpu_info *, int (*)(void *), void *, char *); void ampintc_intr_disestablish_msi(void *); void ampintc_intr_barrier_msi(void *); struct ampintc_msi_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int sc_node; paddr_t sc_addr; int sc_bspi; int sc_nspi; void **sc_spi; struct interrupt_controller sc_ic; }; const struct cfattach ampintcmsi_ca = { sizeof (struct ampintc_msi_softc), ampintc_msi_match, ampintc_msi_attach }; struct cfdriver ampintcmsi_cd = { NULL, "ampintcmsi", DV_DULL }; int ampintc_msi_match(struct device *parent, void *cfdata, void *aux) { struct fdt_attach_args *faa = aux; return OF_is_compatible(faa->fa_node, "arm,gic-v2m-frame"); } void ampintc_msi_attach(struct device *parent, struct device *self, void *aux) { struct ampintc_msi_softc *sc = (struct ampintc_msi_softc *)self; struct fdt_attach_args *faa = aux; uint32_t typer; sc->sc_node = faa->fa_node; 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)) panic("%s: bus_space_map failed!", __func__); /* XXX: Hack to retrieve the physical address (from a CPU PoV). */ if (!pmap_extract(pmap_kernel(), sc->sc_ioh, &sc->sc_addr)) { printf(": cannot retrieve msi addr\n"); return; } typer = bus_space_read_4(sc->sc_iot, sc->sc_ioh, GICV2M_TYPER); sc->sc_bspi = GICV2M_TYPER_SPI_BASE(typer); sc->sc_nspi = GICV2M_TYPER_SPI_COUNT(typer); sc->sc_bspi = OF_getpropint(faa->fa_node, "arm,msi-base-spi", sc->sc_bspi); sc->sc_nspi = OF_getpropint(faa->fa_node, "arm,msi-num-spis", sc->sc_nspi); printf(": nspi %d\n", sc->sc_nspi); sc->sc_spi = mallocarray(sc->sc_nspi, sizeof(void *), M_DEVBUF, M_WAITOK|M_ZERO); sc->sc_ic.ic_node = faa->fa_node; sc->sc_ic.ic_cookie = sc; sc->sc_ic.ic_establish_msi = ampintc_intr_establish_msi; sc->sc_ic.ic_disestablish = ampintc_intr_disestablish_msi; sc->sc_ic.ic_barrier = ampintc_intr_barrier_msi; arm_intr_register_fdt(&sc->sc_ic); } void * ampintc_intr_establish_msi(void *self, uint64_t *addr, uint64_t *data, int level, struct cpu_info *ci, int (*func)(void *), void *arg, char *name) { struct ampintc_msi_softc *sc = (struct ampintc_msi_softc *)self; extern LIST_HEAD(, interrupt_controller) interrupt_controllers; struct interrupt_controller *ic; struct machine_intr_handle *ih; void *cookie; int cells[3]; int i; LIST_FOREACH(ic, &interrupt_controllers, ic_list) { if (ic->ic_node == OF_parent(sc->sc_node)) break; } if (ic == NULL) return NULL; cells[0] = 0; /* SPI */ cells[2] = 1; /* Edge-Rising */ for (i = 0; i < sc->sc_nspi; i++) { if (sc->sc_spi[i] != NULL) continue; cells[1] = sc->sc_bspi + i - 32; cookie = ic->ic_establish(ic->ic_cookie, cells, level, ci, func, arg, name); if (cookie == NULL) return NULL; ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK); ih->ih_ic = ic; ih->ih_ih = cookie; *addr = sc->sc_addr + GICV2M_SETSPI_NS; *data = sc->sc_bspi + i; sc->sc_spi[i] = ih; return &sc->sc_spi[i]; } return NULL; } void ampintc_intr_disestablish_msi(void *cookie) { fdt_intr_disestablish(*(void **)cookie); *(void **)cookie = NULL; } void ampintc_intr_barrier_msi(void *cookie) { intr_barrier(*(void **)cookie); } #ifdef MULTIPROCESSOR int ampintc_ipi_ddb(void *v) { /* XXX */ #ifdef DDB db_enter(); #endif return 1; } int ampintc_ipi_halt(void *v) { cpu_halt(); return 1; } int ampintc_ipi_nop(void *v) { /* Nothing to do here, just enough to wake up from WFI */ return 1; } int ampintc_ipi_combined(void *v) { struct ampintc_softc *sc = (struct ampintc_softc *)v; if (sc->sc_ipi_reason[cpu_number()] == ARM_IPI_DDB) { sc->sc_ipi_reason[cpu_number()] = ARM_IPI_NOP; return ampintc_ipi_ddb(v); } else if (sc->sc_ipi_reason[cpu_number()] == ARM_IPI_HALT) { sc->sc_ipi_reason[cpu_number()] = ARM_IPI_NOP; return ampintc_ipi_halt(v); } else { return ampintc_ipi_nop(v); } } void ampintc_send_ipi(struct cpu_info *ci, int id) { struct ampintc_softc *sc = ampintc; int sendmask; if (ci == curcpu() && id == ARM_IPI_NOP) return; /* never overwrite IPI_DDB or IPI_HALT with IPI_NOP */ if (id == ARM_IPI_DDB || id == ARM_IPI_HALT) sc->sc_ipi_reason[ci->ci_cpuid] = id; /* currently will only send to one cpu */ sendmask = sc->sc_cpu_mask[ci->ci_cpuid] << 16; sendmask |= sc->sc_ipi_num[id]; bus_space_write_4(sc->sc_iot, sc->sc_d_ioh, ICD_SGIR, sendmask); } #endif