/* $OpenBSD: openpic.c,v 1.83 2015/09/08 08:29:35 deraadt Exp $ */ /*- * Copyright (c) 2008 Dale Rahn * Copyright (c) 1995 Per Fogelstrom * Copyright (c) 1993, 1994 Charles M. Hannum. * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * William Jolitz and Don Ahn. * * 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)isa.c 7.2 (Berkeley) 5/12/91 */ #include "hpb.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef OPENPIC_DEBUG #define DPRINTF(x...) do { printf(x); } while(0) #else #define DPRINTF(x...) #endif #define ICU_LEN 128 int openpic_numirq = ICU_LEN; #define LEGAL_IRQ(x) ((x >= 0) && (x < ICU_LEN)) int openpic_pri_share[IPL_NUM]; struct intrq openpic_handler[ICU_LEN]; struct openpic_softc { struct device sc_dev; }; vaddr_t openpic_base; int openpic_big_endian; struct evcount openpic_spurious; int openpic_spurious_irq = 255; int openpic_match(struct device *parent, void *cf, void *aux); void openpic_attach(struct device *, struct device *, void *); int openpic_splraise(int); int openpic_spllower(int); void openpic_splx(int); u_int openpic_read(int reg); void openpic_write(int reg, u_int val); void openpic_acknowledge_irq(int, int); void openpic_enable_irq(int, int, int); void openpic_disable_irq(int, int); void openpic_calc_mask(void); void openpic_set_priority(int, int); void *openpic_intr_establish(void *, int, int, int, int (*)(void *), void *, const char *); void openpic_intr_disestablish(void *, void *); void openpic_collect_preconf_intr(void); void openpic_ext_intr(void); int openpic_ext_intr_handler(struct intrhand *, int, int *); /* Generic IRQ management routines. */ void openpic_gen_acknowledge_irq(int, int); void openpic_gen_enable_irq(int, int, int); void openpic_gen_disable_irq(int, int); #if NHPB > 0 /* CPC945 IRQ management routines. */ void openpic_cpc945_acknowledge_irq(int, int); void openpic_cpc945_enable_irq(int, int, int); void openpic_cpc945_disable_irq(int, int); #endif /* NHPB */ struct openpic_ops { void (*acknowledge_irq)(int, int); void (*enable_irq)(int, int, int); void (*disable_irq)(int, int); } openpic_ops = { openpic_gen_acknowledge_irq, openpic_gen_enable_irq, openpic_gen_disable_irq }; #ifdef MULTIPROCESSOR void openpic_ipi_ddb(void); /* IRQ vector used for inter-processor interrupts. */ #define IPI_VECTOR_NOP 64 #define IPI_VECTOR_DDB 65 static struct evcount ipi_count; static int ipi_irq = IPI_VECTOR_NOP; intr_send_ipi_t openpic_send_ipi; #endif /* MULTIPROCESSOR */ const struct cfattach openpic_ca = { sizeof(struct openpic_softc), openpic_match, openpic_attach }; struct cfdriver openpic_cd = { NULL, "openpic", DV_DULL }; u_int openpic_read(int reg) { char *addr = (void *)(openpic_base + reg); membar_sync(); if (openpic_big_endian) return in32(addr); else return in32rb(addr); } void openpic_write(int reg, u_int val) { char *addr = (void *)(openpic_base + reg); if (openpic_big_endian) out32(addr, val); else out32rb(addr, val); membar_sync(); } static inline int openpic_read_irq(int cpu) { return openpic_read(OPENPIC_IACK(cpu)) & OPENPIC_VECTOR_MASK; } static inline void openpic_eoi(int cpu) { openpic_write(OPENPIC_EOI(cpu), 0); } int openpic_match(struct device *parent, void *cf, void *aux) { char type[40]; int pirq; struct confargs *ca = aux; bzero (type, sizeof(type)); if (OF_getprop(ca->ca_node, "interrupt-parent", &pirq, sizeof(pirq)) == sizeof(pirq)) return 0; /* XXX */ if (strcmp(ca->ca_name, "interrupt-controller") != 0 && strcmp(ca->ca_name, "mpic") != 0) return 0; OF_getprop(ca->ca_node, "device_type", type, sizeof(type)); if (strcmp(type, "open-pic") != 0) return 0; if (ca->ca_nreg < 8) return 0; return 1; } void openpic_attach(struct device *parent, struct device *self, void *aux) { struct cpu_info *ci = curcpu(); struct confargs *ca = aux; struct intrq *iq; uint32_t reg = 0; int i, irq; u_int x; if (OF_getprop(ca->ca_node, "big-endian", ®, sizeof reg) == 0) openpic_big_endian = 1; openpic_base = (vaddr_t) mapiodev (ca->ca_baseaddr + ca->ca_reg[0], 0x40000); /* Reset the PIC */ x = openpic_read(OPENPIC_CONFIG) | OPENPIC_CONFIG_RESET; openpic_write(OPENPIC_CONFIG, x); while (openpic_read(OPENPIC_CONFIG) & OPENPIC_CONFIG_RESET) delay(100); /* openpic may support more than 128 interupts but driver doesn't */ openpic_numirq = ((openpic_read(OPENPIC_FEATURE) >> 16) & 0x7f)+1; printf(": version 0x%x feature %x %s", openpic_read(OPENPIC_VENDOR_ID), openpic_read(OPENPIC_FEATURE), openpic_big_endian ? "BE" : "LE" ); openpic_set_priority(ci->ci_cpuid, 15); /* disable all interrupts */ for (irq = 0; irq < openpic_numirq; irq++) openpic_write(OPENPIC_SRC_VECTOR(irq), OPENPIC_IMASK); for (i = 0; i < openpic_numirq; i++) { iq = &openpic_handler[i]; TAILQ_INIT(&iq->iq_list); } /* we don't need 8259 pass through mode */ x = openpic_read(OPENPIC_CONFIG); x |= OPENPIC_CONFIG_8259_PASSTHRU_DISABLE; openpic_write(OPENPIC_CONFIG, x); /* initialize all vectors to something sane */ for (irq = 0; irq < ICU_LEN; irq++) { x = irq; x |= OPENPIC_IMASK; x |= OPENPIC_POLARITY_NEGATIVE; x |= OPENPIC_SENSE_LEVEL; x |= 8 << OPENPIC_PRIORITY_SHIFT; openpic_write(OPENPIC_SRC_VECTOR(irq), x); } /* send all interrupts to cpu 0 */ for (irq = 0; irq < openpic_numirq; irq++) openpic_write(OPENPIC_IDEST(irq), 1 << 0); /* clear all pending interrunts */ for (irq = 0; irq < ICU_LEN; irq++) { openpic_read_irq(ci->ci_cpuid); openpic_eoi(ci->ci_cpuid); } #ifdef MULTIPROCESSOR /* Set up inter-processor interrupts. */ /* IPI0 - NOP */ x = IPI_VECTOR_NOP; x |= 15 << OPENPIC_PRIORITY_SHIFT; openpic_write(OPENPIC_IPI_VECTOR(0), x); /* IPI1 - DDB */ x = IPI_VECTOR_DDB; x |= 15 << OPENPIC_PRIORITY_SHIFT; openpic_write(OPENPIC_IPI_VECTOR(1), x); evcount_attach(&ipi_count, "ipi", &ipi_irq); #endif /* clear all pending interrunts */ for (irq = 0; irq < ICU_LEN; irq++) { openpic_read_irq(0); openpic_eoi(0); } #if 0 openpic_write(OPENPIC_SPURIOUS_VECTOR, 255); #endif #if NHPB > 0 /* Only U4 systems have a big-endian MPIC. */ if (openpic_big_endian) { openpic_ops.acknowledge_irq = openpic_cpc945_acknowledge_irq; openpic_ops.enable_irq = openpic_cpc945_enable_irq; openpic_ops.disable_irq = openpic_cpc945_disable_irq; } #endif install_extint(openpic_ext_intr); openpic_set_priority(ci->ci_cpuid, 0); intr_establish_func = openpic_intr_establish; intr_disestablish_func = openpic_intr_disestablish; #ifdef MULTIPROCESSOR intr_send_ipi_func = openpic_send_ipi; #endif ppc_smask_init(); openpic_collect_preconf_intr(); evcount_attach(&openpic_spurious, "spurious", &openpic_spurious_irq); ppc_intr_func.raise = openpic_splraise; ppc_intr_func.lower = openpic_spllower; ppc_intr_func.x = openpic_splx; openpic_set_priority(0, ci->ci_cpl); ppc_intr_enable(1); printf("\n"); } /* Must be called with interrupt disable. */ static inline void openpic_setipl(int newcpl) { struct cpu_info *ci = curcpu(); ci->ci_cpl = newcpl; openpic_set_priority(ci->ci_cpuid, newcpl); } int openpic_splraise(int newcpl) { struct cpu_info *ci = curcpu(); int ocpl = ci->ci_cpl; int s; newcpl = openpic_pri_share[newcpl]; if (ocpl > newcpl) newcpl = ocpl; s = ppc_intr_disable(); openpic_setipl(newcpl); ppc_intr_enable(s); return ocpl; } int openpic_spllower(int newcpl) { struct cpu_info *ci = curcpu(); int ocpl = ci->ci_cpl; openpic_splx(newcpl); return ocpl; } void openpic_splx(int newcpl) { struct cpu_info *ci = curcpu(); int intr, s; intr = ppc_intr_disable(); openpic_setipl(newcpl); if (newcpl < IPL_SOFTTTY && (ci->ci_ipending & ppc_smask[newcpl])) { s = splsofttty(); dosoftint(newcpl); openpic_setipl(s); /* no-overhead splx */ } ppc_intr_enable(intr); } void openpic_collect_preconf_intr() { int i; for (i = 0; i < ppc_configed_intr_cnt; i++) { DPRINTF("\n\t%s irq %d level %d fun %p arg %p", ppc_configed_intr[i].ih_what, ppc_configed_intr[i].ih_irq, ppc_configed_intr[i].ih_level, ppc_configed_intr[i].ih_fun, ppc_configed_intr[i].ih_arg); openpic_intr_establish(NULL, ppc_configed_intr[i].ih_irq, IST_LEVEL, ppc_configed_intr[i].ih_level, ppc_configed_intr[i].ih_fun, ppc_configed_intr[i].ih_arg, ppc_configed_intr[i].ih_what); } } /* * Register an interrupt handler. */ void * openpic_intr_establish(void *lcv, int irq, int type, int level, int (*ih_fun)(void *), void *ih_arg, const char *name) { struct intrhand *ih; struct intrq *iq; int s, flags; if (!LEGAL_IRQ(irq) || type == IST_NONE) { printf("%s: bogus irq %d or type %d", __func__, irq, type); return (NULL); } /* no point in sleeping unless someone can free memory. */ ih = malloc(sizeof *ih, M_DEVBUF, cold ? M_NOWAIT : M_WAITOK); if (ih == NULL) panic("%s: can't malloc handler info", __func__); iq = &openpic_handler[irq]; switch (iq->iq_ist) { case IST_NONE: iq->iq_ist = type; break; case IST_EDGE: intr_shared_edge = 1; /* FALLTHROUGH */ case IST_LEVEL: if (type == iq->iq_ist) break; case IST_PULSE: if (type != IST_NONE) panic("intr_establish: can't share %s with %s", ppc_intr_typename(iq->iq_ist), ppc_intr_typename(type)); break; } flags = level & IPL_MPSAFE; level &= ~IPL_MPSAFE; KASSERT(level <= IPL_TTY || level >= IPL_CLOCK || flags & IPL_MPSAFE); ih->ih_fun = ih_fun; ih->ih_arg = ih_arg; ih->ih_level = level; ih->ih_flags = flags; ih->ih_irq = irq; evcount_attach(&ih->ih_count, name, &ih->ih_irq); /* * Append handler to end of list */ s = ppc_intr_disable(); TAILQ_INSERT_TAIL(&iq->iq_list, ih, ih_list); openpic_calc_mask(); ppc_intr_enable(s); return (ih); } /* * Deregister an interrupt handler. */ void openpic_intr_disestablish(void *lcp, void *arg) { struct intrhand *ih = arg; int irq = ih->ih_irq; struct intrq *iq = &openpic_handler[irq]; int s; if (!LEGAL_IRQ(irq)) { printf("%s: bogus irq %d", __func__, irq); return; } /* * Remove the handler from the chain. */ s = ppc_intr_disable(); TAILQ_REMOVE(&iq->iq_list, ih, ih_list); openpic_calc_mask(); ppc_intr_enable(s); evcount_detach(&ih->ih_count); free(ih, M_DEVBUF, sizeof *ih); if (TAILQ_EMPTY(&iq->iq_list)) iq->iq_ist = IST_NONE; } /* * Recalculate the interrupt masks from scratch. * We could code special registry and deregistry versions of this function that * would be faster, but the code would be nastier, and we don't expect this to * happen very much anyway. */ void openpic_calc_mask() { struct cpu_info *ci = curcpu(); int irq; struct intrhand *ih; int i; /* disable all openpic interrupts */ openpic_set_priority(ci->ci_cpuid, 15); for (i = IPL_NONE; i < IPL_NUM; i++) { openpic_pri_share[i] = i; } for (irq = 0; irq < openpic_numirq; irq++) { int maxipl = IPL_NONE; int minipl = IPL_HIGH; struct intrq *iq = &openpic_handler[irq]; TAILQ_FOREACH(ih, &iq->iq_list, ih_list) { if (ih->ih_level > maxipl) maxipl = ih->ih_level; if (ih->ih_level < minipl) minipl = ih->ih_level; } if (maxipl == IPL_NONE) { minipl = IPL_NONE; /* Interrupt not enabled */ openpic_disable_irq(irq, iq->iq_ist); } else { for (i = minipl; i <= maxipl; i++) { openpic_pri_share[i] = maxipl; } openpic_enable_irq(irq, iq->iq_ist, maxipl); } iq->iq_ipl = maxipl; } /* restore interrupts */ openpic_set_priority(ci->ci_cpuid, ci->ci_cpl); } void openpic_gen_acknowledge_irq(int irq, int cpuid) { openpic_eoi(cpuid); } void openpic_gen_enable_irq(int irq, int ist, int pri) { u_int x; x = irq; if (ist == IST_LEVEL) x |= OPENPIC_SENSE_LEVEL; else x |= OPENPIC_SENSE_EDGE; x |= OPENPIC_POLARITY_NEGATIVE; x |= pri << OPENPIC_PRIORITY_SHIFT; openpic_write(OPENPIC_SRC_VECTOR(irq), x); } void openpic_gen_disable_irq(int irq, int ist) { u_int x; x = openpic_read(OPENPIC_SRC_VECTOR(irq)); x |= OPENPIC_IMASK; openpic_write(OPENPIC_SRC_VECTOR(irq), x); } void openpic_set_priority(int cpu, int pri) { openpic_write(OPENPIC_CPU_PRIORITY(cpu), pri); } int openpic_irqnest[PPC_MAXPROCS]; int openpic_irqloop[PPC_MAXPROCS]; void openpic_ext_intr(void) { struct cpu_info *ci = curcpu(); int irq, pcpl, ret; int maxipl = IPL_NONE; struct intrhand *ih; struct intrq *iq; int spurious; pcpl = ci->ci_cpl; openpic_irqloop[ci->ci_cpuid] = 0; irq = openpic_read_irq(ci->ci_cpuid); openpic_irqnest[ci->ci_cpuid]++; while (irq != 255) { openpic_irqloop[ci->ci_cpuid]++; #ifdef OPENPIC_DEBUG if (openpic_irqloop[ci->ci_cpuid] > 20 || openpic_irqnest[ci->ci_cpuid] > 3) { printf("irqloop %d irqnest %d\n", openpic_irqloop[ci->ci_cpuid], openpic_irqnest[ci->ci_cpuid]); } #endif if (openpic_irqloop[ci->ci_cpuid] > 20) { DPRINTF("irqloop %d irqnest %d: returning\n", openpic_irqloop[ci->ci_cpuid], openpic_irqnest[ci->ci_cpuid]); openpic_irqnest[ci->ci_cpuid]--; return; } #ifdef MULTIPROCESSOR if (irq == IPI_VECTOR_NOP || irq == IPI_VECTOR_DDB) { ipi_count.ec_count++; openpic_eoi(ci->ci_cpuid); if (irq == IPI_VECTOR_DDB) openpic_ipi_ddb(); irq = openpic_read_irq(ci->ci_cpuid); continue; } #endif iq = &openpic_handler[irq]; #ifdef OPENPIC_DEBUG if (iq->iq_ipl <= pcpl) printf("invalid interrupt %d lvl %d at %d hw %d\n", irq, iq->iq_ipl, pcpl, openpic_read(OPENPIC_CPU_PRIORITY(ci->ci_cpuid))); #endif if (iq->iq_ipl > maxipl) maxipl = iq->iq_ipl; openpic_splraise(iq->iq_ipl); openpic_acknowledge_irq(irq, ci->ci_cpuid); spurious = 1; TAILQ_FOREACH(ih, &iq->iq_list, ih_list) { ppc_intr_enable(1); ret = openpic_ext_intr_handler(ih, pcpl, &spurious); (void)ppc_intr_disable(); if (intr_shared_edge == 00 && ret == 1) break; } if (spurious) { openpic_spurious.ec_count++; DPRINTF("spurious intr %d\n", irq); } uvmexp.intrs++; openpic_setipl(pcpl); irq = openpic_read_irq(ci->ci_cpuid); } openpic_splx(pcpl); /* Process pendings. */ openpic_irqnest[ci->ci_cpuid]--; } int openpic_ext_intr_handler(struct intrhand *ih, int pcpl, int *spurious) { int ret; #ifdef MULTIPROCESSOR int need_lock; if (ih->ih_flags & IPL_MPSAFE) need_lock = 0; else need_lock = pcpl < IPL_SCHED; if (need_lock) KERNEL_LOCK(); #endif ret = (*ih->ih_fun)(ih->ih_arg); if (ret) { ih->ih_count.ec_count++; *spurious = 0; } #ifdef MULTIPROCESSOR if (need_lock) KERNEL_UNLOCK(); #endif return (ret); } void openpic_acknowledge_irq(int irq, int cpuid) { (openpic_ops.acknowledge_irq)(irq, cpuid); } void openpic_enable_irq(int irq, int ist, int pri) { (openpic_ops.enable_irq)(irq, ist, pri); } void openpic_disable_irq(int irq, int ist) { (openpic_ops.disable_irq)(irq, ist); } #ifdef MULTIPROCESSOR void openpic_send_ipi(struct cpu_info *ci, int id) { switch (id) { case PPC_IPI_NOP: id = 0; break; case PPC_IPI_DDB: id = 1; break; default: panic("invalid ipi send to cpu %d %d", ci->ci_cpuid, id); } openpic_write(OPENPIC_IPI(curcpu()->ci_cpuid, id), 1 << ci->ci_cpuid); } void openpic_ipi_ddb(void) { #ifdef DDB Debugger(); #endif } #endif /* MULTIPROCESSOR */ #if NHPB > 0 extern int hpb_enable_irq(int, int); extern int hpb_disable_irq(int, int); extern void hpb_eoi(int); void openpic_cpc945_acknowledge_irq(int irq, int cpuid) { hpb_eoi(irq); openpic_gen_acknowledge_irq(irq, cpuid); } void openpic_cpc945_enable_irq(int irq, int ist, int pri) { if (hpb_enable_irq(irq, ist)) { u_int x = irq; x |= OPENPIC_SENSE_EDGE; x |= OPENPIC_POLARITY_POSITIVE; x |= pri << OPENPIC_PRIORITY_SHIFT; openpic_write(OPENPIC_SRC_VECTOR(irq), x); hpb_eoi(irq); } else openpic_gen_enable_irq(irq, ist, pri); } void openpic_cpc945_disable_irq(int irq, int ist) { hpb_disable_irq(irq, ist); openpic_gen_disable_irq(irq, ist); } #endif /* NHPB */