/* $NetBSD: intr.c,v 1.6 2008/04/29 06:53:02 martin Exp $ */ /*- * Copyright (c) 2007 Michael Lorenz * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h> __KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.6 2008/04/29 06:53:02 martin Exp $"); #include "opt_multiprocessor.h" #include <sys/param.h> #include <sys/malloc.h> #include <sys/kernel.h> #include <sys/cpu.h> #include <uvm/uvm_extern.h> #include <arch/powerpc/pic/picvar.h> #include "opt_pic.h" #include "opt_interrupt.h" #if defined(PIC_I8259) || defined (PIC_PREPIVR) #include <machine/isa_machdep.h> #endif #ifdef MULTIPROCESSOR #include <arch/powerpc/pic/ipivar.h> #endif #define MAX_PICS 8 /* 8 PICs ought to be enough for everyone */ #define NVIRQ 32 /* 32 virtual IRQs */ #define NIRQ 128 /* up to 128 HW IRQs */ #define HWIRQ_MAX (NVIRQ - 4 - 1) #define HWIRQ_MASK 0x0fffffff #define LEGAL_VIRQ(x) ((x) >= 0 && (x) < NVIRQ) struct pic_ops *pics[MAX_PICS]; int num_pics = 0; int max_base = 0; uint8_t virq[NIRQ]; int virq_max = 0; int imask[NIPL]; int primary_pic = 0; static int fakeintr(void *); static int mapirq(uint32_t); static void intr_calculatemasks(void); static struct pic_ops *find_pic_by_irq(int); static struct intr_source intrsources[NVIRQ]; void pic_init(void) { int i; for (i = 0; i < NIRQ; i++) virq[i] = 0; memset(intrsources, 0, sizeof(intrsources)); } int pic_add(struct pic_ops *pic) { if (num_pics >= MAX_PICS) return -1; pics[num_pics] = pic; pic->pic_intrbase = max_base; max_base += pic->pic_numintrs; num_pics++; return pic->pic_intrbase; } void pic_finish_setup(void) { struct pic_ops *pic; int i; for (i = 0; i < num_pics; i++) { pic = pics[i]; if (pic->pic_finish_setup != NULL) pic->pic_finish_setup(pic); } } static struct pic_ops * find_pic_by_irq(int irq) { struct pic_ops *current; int base = 0; while (base < num_pics) { current = pics[base]; if ((irq >= current->pic_intrbase) && (irq < (current->pic_intrbase + current->pic_numintrs))) { return current; } base++; } return NULL; } static int fakeintr(void *arg) { return 0; } /* * Register an interrupt handler. */ void * intr_establish(int hwirq, int type, int level, int (*ih_fun)(void *), void *ih_arg) { struct intrhand **p, *q, *ih; struct intr_source *is; struct pic_ops *pic; static struct intrhand fakehand; int irq, maxlevel = level; if (maxlevel == IPL_NONE) maxlevel = IPL_HIGH; if (hwirq >= max_base) { panic("%s: bogus IRQ %d, max is %d", __func__, hwirq, max_base - 1); } pic = find_pic_by_irq(hwirq); if (pic == NULL) { panic("%s: cannot find a pic for IRQ %d", __func__, hwirq); } irq = mapirq(hwirq); /* no point in sleeping unless someone can free memory. */ ih = malloc(sizeof *ih, M_DEVBUF, cold ? M_NOWAIT : M_WAITOK); if (ih == NULL) panic("intr_establish: can't malloc handler info"); if (!LEGAL_VIRQ(irq) || type == IST_NONE) panic("intr_establish: bogus irq (%d) or type (%d)", irq, type); is = &intrsources[irq]; switch (is->is_type) { case IST_NONE: is->is_type = type; break; case IST_EDGE: case IST_LEVEL: if (type == is->is_type) break; case IST_PULSE: if (type != IST_NONE) panic("intr_establish: can't share %s with %s", intr_typename(is->is_type), intr_typename(type)); break; } if (is->is_hand == NULL) { snprintf(is->is_source, sizeof(is->is_source), "irq %d", is->is_hwirq); evcnt_attach_dynamic(&is->is_ev, EVCNT_TYPE_INTR, NULL, pic->pic_name, is->is_source); } /* * Figure out where to put the handler. * This is O(N^2), but we want to preserve the order, and N is * generally small. */ for (p = &is->is_hand; (q = *p) != NULL; p = &q->ih_next) { maxlevel = max(maxlevel, q->ih_level); } /* * Actually install a fake handler momentarily, since we might be doing * this with interrupts enabled and don't want the real routine called * until masking is set up. */ fakehand.ih_level = level; fakehand.ih_fun = fakeintr; *p = &fakehand; /* * Poke the real handler in now. */ ih->ih_fun = ih_fun; ih->ih_arg = ih_arg; ih->ih_next = NULL; ih->ih_level = level; ih->ih_irq = irq; *p = ih; if (pic->pic_establish_irq != NULL) pic->pic_establish_irq(pic, hwirq - pic->pic_intrbase, is->is_type, maxlevel); /* * now that the handler is established we're actually ready to * calculate the masks */ intr_calculatemasks(); return ih; } void dummy_pic_establish_intr(struct pic_ops *pic, int irq, int type, int pri) { } /* * Deregister an interrupt handler. */ void intr_disestablish(void *arg) { struct intrhand *ih = arg; int irq = ih->ih_irq; struct intr_source *is = &intrsources[irq]; struct intrhand **p, *q; if (!LEGAL_VIRQ(irq)) panic("intr_disestablish: bogus irq %d", irq); /* * Remove the handler from the chain. * This is O(n^2), too. */ for (p = &is->is_hand; (q = *p) != NULL && q != ih; p = &q->ih_next) ; if (q) *p = q->ih_next; else panic("intr_disestablish: handler not registered"); free((void *)ih, M_DEVBUF); intr_calculatemasks(); if (is->is_hand == NULL) { is->is_type = IST_NONE; evcnt_detach(&is->is_ev); } } /* * Map max_base irqs into 32 (bits). */ static int mapirq(uint32_t irq) { struct pic_ops *pic; int v; if (irq >= max_base) panic("invalid irq %d", irq); if ((pic = find_pic_by_irq(irq)) == NULL) panic("%s: cannot find PIC for IRQ %d", __func__, irq); if (virq[irq]) return virq[irq]; virq_max++; v = virq_max; if (v > HWIRQ_MAX) panic("virq overflow"); intrsources[v].is_hwirq = irq; intrsources[v].is_pic = pic; virq[irq] = v; #ifdef PIC_DEBUG printf("mapping irq %d to virq %d\n", irq, v); #endif return v; } static const char * const intr_typenames[] = { [IST_NONE] = "none", [IST_PULSE] = "pulsed", [IST_EDGE] = "edge-triggered", [IST_LEVEL] = "level-triggered", }; const char * intr_typename(int type) { KASSERT((unsigned int) type < __arraycount(intr_typenames)); KASSERT(intr_typenames[type] != NULL); return intr_typenames[type]; } /* * 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. */ static void intr_calculatemasks(void) { struct intr_source *is; struct intrhand *q; struct pic_ops *current; int irq, level, i, base; /* First, figure out which levels each IRQ uses. */ for (irq = 0, is = intrsources; irq < NVIRQ; irq++, is++) { register int levels = 0; for (q = is->is_hand; q; q = q->ih_next) levels |= 1 << q->ih_level; is->is_level = levels; } /* Then figure out which IRQs use each level. */ for (level = 0; level < NIPL; level++) { register int irqs = 0; for (irq = 0, is = intrsources; irq < NVIRQ; irq++, is++) if (is->is_level & (1 << level)) irqs |= 1 << irq; imask[level] = irqs; } /* * IPL_CLOCK should mask clock interrupt even if interrupt handler * is not registered. */ imask[IPL_CLOCK] |= 1 << SPL_CLOCK; /* * Initialize soft interrupt masks to block themselves. */ imask[IPL_SOFTCLOCK] = 1 << SIR_CLOCK; imask[IPL_SOFTNET] = 1 << SIR_NET; imask[IPL_SOFTSERIAL] = 1 << SIR_SERIAL; /* * IPL_NONE is used for hardware interrupts that are never blocked, * and do not block anything else. */ imask[IPL_NONE] = 0; #ifdef SLOPPY_IPLS /* * Enforce a sloppy hierarchy as in spl(9) */ /* everything above softclock must block softclock */ for (i = IPL_SOFTCLOCK; i < NIPL; i++) imask[i] |= imask[IPL_SOFTCLOCK]; /* everything above softnet must block softnet */ for (i = IPL_SOFTNET; i < NIPL; i++) imask[i] |= imask[IPL_SOFTNET]; /* IPL_TTY must block softserial */ imask[IPL_TTY] |= imask[IPL_SOFTSERIAL]; /* IPL_VM must block net, block IO and tty */ imask[IPL_VM] |= (imask[IPL_NET] | imask[IPL_BIO] | imask[IPL_TTY]); /* IPL_SERIAL must block IPL_TTY */ imask[IPL_SERIAL] |= imask[IPL_TTY]; /* IPL_HIGH must block all other priority levels */ for (i = IPL_NONE; i < IPL_HIGH; i++) imask[IPL_HIGH] |= imask[i]; #else /* !SLOPPY_IPLS */ /* * strict hierarchy - all IPLs block everything blocked by any lower * IPL */ for (i = 1; i < NIPL; i++) imask[i] |= imask[i - 1]; #endif /* !SLOPPY_IPLS */ #ifdef DEBUG_IPL for (i = 0; i < NIPL; i++) { printf("%2d: %08x\n", i, imask[i]); } #endif /* And eventually calculate the complete masks. */ for (irq = 0, is = intrsources; irq < NVIRQ; irq++, is++) { register int irqs = 1 << irq; for (q = is->is_hand; q; q = q->ih_next) irqs |= imask[q->ih_level]; is->is_mask = irqs; } /* Lastly, enable IRQs actually in use. */ for (base = 0; base < num_pics; base++) { current = pics[base]; for (i = 0; i < current->pic_numintrs; i++) current->pic_disable_irq(current, i); } for (irq = 0, is = intrsources; irq < NVIRQ; irq++, is++) { if (is->is_hand) pic_enable_irq(is->is_hwirq); } } void pic_enable_irq(int num) { struct pic_ops *current; int type; current = find_pic_by_irq(num); if (current == NULL) panic("%s: bogus IRQ %d", __func__, num); type = intrsources[virq[num]].is_type; current->pic_enable_irq(current, num - current->pic_intrbase, type); } void pic_mark_pending(int irq) { struct cpu_info * const ci = curcpu(); int v, msr; v = virq[irq]; if (v == 0) printf("IRQ %d maps to 0\n", irq); msr = mfmsr(); mtmsr(msr & ~PSL_EE); ci->ci_ipending |= 1 << v; mtmsr(msr); } void pic_do_pending_int(void) { struct cpu_info * const ci = curcpu(); struct intr_source *is; struct intrhand *ih; struct pic_ops *pic; int irq; int pcpl; int hwpend; int emsr, dmsr; if (ci->ci_iactive) return; ci->ci_iactive = 1; emsr = mfmsr(); KASSERT(emsr & PSL_EE); dmsr = emsr & ~PSL_EE; mtmsr(dmsr); pcpl = ci->ci_cpl; #ifdef __HAVE_FAST_SOFTINTS again: #endif /* Do now unmasked pendings */ ci->ci_idepth++; while ((hwpend = (ci->ci_ipending & ~pcpl & HWIRQ_MASK)) != 0) { irq = 31 - cntlzw(hwpend); KASSERT(irq <= virq_max); ci->ci_ipending &= ~(1 << irq); if (irq == 0) { printf("VIRQ0"); continue; } is = &intrsources[irq]; pic = is->is_pic; splraise(is->is_mask); mtmsr(emsr); ih = is->is_hand; while (ih) { #ifdef DIAGNOSTIC if (!ih->ih_fun) { printf("NULL interrupt handler!\n"); panic("irq %02d, hwirq %02d, is %p\n", irq, is->is_hwirq, is); } #endif if (ih->ih_level == IPL_VM) { KERNEL_LOCK(1, NULL); } (*ih->ih_fun)(ih->ih_arg); if (ih->ih_level == IPL_VM) { KERNEL_UNLOCK_ONE(NULL); } ih = ih->ih_next; } mtmsr(dmsr); ci->ci_cpl = pcpl; is->is_ev.ev_count++; pic->pic_reenable_irq(pic, is->is_hwirq - pic->pic_intrbase, is->is_type); } ci->ci_idepth--; #ifdef __HAVE_FAST_SOFTINTS if ((ci->ci_ipending & ~pcpl) & (1 << SIR_SERIAL)) { ci->ci_ipending &= ~(1 << SIR_SERIAL); splsoftserial(); mtmsr(emsr); softintr__run(IPL_SOFTSERIAL); mtmsr(dmsr); ci->ci_cpl = pcpl; ci->ci_ev_softserial.ev_count++; goto again; } if ((ci->ci_ipending & ~pcpl) & (1 << SIR_NET)) { ci->ci_ipending &= ~(1 << SIR_NET); splsoftnet(); mtmsr(emsr); softintr__run(IPL_SOFTNET); mtmsr(dmsr); ci->ci_cpl = pcpl; ci->ci_ev_softnet.ev_count++; goto again; } if ((ci->ci_ipending & ~pcpl) & (1 << SIR_CLOCK)) { ci->ci_ipending &= ~(1 << SIR_CLOCK); splsoftclock(); mtmsr(emsr); softintr__run(IPL_SOFTCLOCK); mtmsr(dmsr); ci->ci_cpl = pcpl; ci->ci_ev_softclock.ev_count++; goto again; } #endif ci->ci_cpl = pcpl; /* Don't use splx... we are here already! */ ci->ci_iactive = 0; mtmsr(emsr); } int pic_handle_intr(void *cookie) { struct pic_ops *pic = cookie; struct cpu_info *ci = curcpu(); struct intr_source *is; struct intrhand *ih; int irq, realirq; int pcpl, msr, r_imen, bail; realirq = pic->pic_get_irq(pic, PIC_GET_IRQ); if (realirq == 255) return 0; msr = mfmsr(); pcpl = ci->ci_cpl; start: #ifdef MULTIPROCESSOR /* THIS IS WRONG XXX */ while (realirq == ipiops.ppc_ipi_vector) { ppcipi_intr(NULL); pic->pic_ack_irq(pic, realirq); realirq = pic->pic_get_irq(pic, PIC_GET_RECHECK); } if (realirq == 255) { return 0; } #endif irq = virq[realirq + pic->pic_intrbase]; #ifdef PIC_DEBUG if (irq == 0) { printf("%s: %d virq 0\n", pic->pic_name, realirq); goto boo; } #endif /* PIC_DEBUG */ KASSERT(realirq < pic->pic_numintrs); r_imen = 1 << irq; is = &intrsources[irq]; if ((pcpl & r_imen) != 0) { ci->ci_ipending |= r_imen; /* Masked! Mark this as pending */ pic->pic_disable_irq(pic, realirq); } else { /* this interrupt is no longer pending */ ci->ci_ipending &= ~r_imen; ci->ci_idepth++; splraise(is->is_mask); mtmsr(msr | PSL_EE); ih = is->is_hand; bail = 0; while ((ih != NULL) && (bail < 10)) { if (ih->ih_fun == NULL) panic("bogus handler for IRQ %s %d", pic->pic_name, realirq); if (ih->ih_level == IPL_VM) { KERNEL_LOCK(1, NULL); } (*ih->ih_fun)(ih->ih_arg); if (ih->ih_level == IPL_VM) { KERNEL_UNLOCK_ONE(NULL); } ih = ih->ih_next; bail++; } mtmsr(msr); ci->ci_cpl = pcpl; uvmexp.intrs++; is->is_ev.ev_count++; ci->ci_idepth--; } #ifdef PIC_DEBUG boo: #endif /* PIC_DEBUG */ pic->pic_ack_irq(pic, realirq); realirq = pic->pic_get_irq(pic, PIC_GET_RECHECK); if (realirq != 255) goto start; mtmsr(msr | PSL_EE); splx(pcpl); /* Process pendings. */ mtmsr(msr); return 0; } void pic_ext_intr(void) { KASSERT(pics[primary_pic] != NULL); pic_handle_intr(pics[primary_pic]); return; } int splraise(int ncpl) { struct cpu_info *ci = curcpu(); int ocpl; __asm volatile("sync; eieio"); /* don't reorder.... */ ocpl = ci->ci_cpl; ci->ci_cpl = ocpl | ncpl; __asm volatile("sync; eieio"); /* reorder protect */ return ocpl; } void splx(int ncpl) { struct cpu_info *ci = curcpu(); __asm volatile("sync; eieio"); /* reorder protect */ ci->ci_cpl = ncpl; if (ci->ci_ipending & ~ncpl) pic_do_pending_int(); __asm volatile("sync; eieio"); /* reorder protect */ } int spllower(int ncpl) { struct cpu_info *ci = curcpu(); int ocpl; __asm volatile("sync; eieio"); /* reorder protect */ ocpl = ci->ci_cpl; ci->ci_cpl = ncpl; if (ci->ci_ipending & ~ncpl) pic_do_pending_int(); __asm volatile("sync; eieio"); /* reorder protect */ return ocpl; } /* Following code should be implemented with lwarx/stwcx to avoid * the disable/enable. i need to read the manual once more.... */ void softintr(int ipl) { int msrsave; msrsave = mfmsr(); mtmsr(msrsave & ~PSL_EE); curcpu()->ci_ipending |= 1 << ipl; mtmsr(msrsave); } void genppc_cpu_configure(void) { aprint_normal("biomask %x netmask %x ttymask %x\n", imask[IPL_BIO] & 0x1fffffff, imask[IPL_NET] & 0x1fffffff, imask[IPL_TTY] & 0x1fffffff); spl0(); } #if defined(PIC_PREPIVR) || defined(PIC_I8259) /* * isa_intr_alloc needs to be done here, because it needs direct access to * the various interrupt handler structures. */ int genppc_isa_intr_alloc(isa_chipset_tag_t ic, struct pic_ops *pic, int mask, int type, int *irq_p) { int irq, vi; int maybe_irq = -1; int shared_depth = 0; struct intr_source *is; if (pic == NULL) return 1; for (irq = 0; (mask != 0 && irq < pic->pic_numintrs); mask >>= 1, irq++) { if ((mask & 1) == 0) continue; vi = virq[irq + pic->pic_intrbase]; if (!vi) { *irq_p = irq; return 0; } is = &intrsources[vi]; if (is->is_type == IST_NONE) { *irq_p = irq; return 0; } /* Level interrupts can be shared */ if (type == IST_LEVEL && is->is_type == IST_LEVEL) { struct intrhand *ih = is->is_hand; int depth; if (maybe_irq == -1) { maybe_irq = irq; continue; } for (depth = 0; ih != NULL; ih = ih->ih_next) depth++; if (depth < shared_depth) { maybe_irq = irq; shared_depth = depth; } } } if (maybe_irq != -1) { *irq_p = maybe_irq; return 0; } return 1; } #endif