File: [cvs.NetBSD.org] / src / sys / arch / powerpc / pic / intr.c (download)
Revision 1.1.2.1, Wed May 2 02:59:01 2007 UTC (16 years, 11 months ago) by macallan
Branch: ppcoea-renovation
Changes since 1.1: +674 -0
lines
first try on a generic interrupt handler.
Features:
- PIC details are hidden by struct pic_ops
- PICs can easily be cascaded
- support for soft interrupts
tested so far only on macppc with a PowerBook 3400c
|
/* $NetBSD: intr.c,v 1.1.2.1 2007/05/02 02:59:01 macallan 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.
* 3. Neither the name of The NetBSD Foundation 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 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.1.2.1 2007/05/02 02:59:01 macallan Exp $");
#include "opt_multiprocessor.h"
#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <uvm/uvm_extern.h>
#include <machine/autoconf.h>
#include <arch/powerpc/pic/picvar.h>
#include "opt_pic.h"
#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];
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;
}
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 = {fakeintr};
int irq;
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)
;
/*
* 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;
*p = &fakehand;
intr_calculatemasks();
/*
* 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;
return ih;
}
/*
* 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 < 0 || 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;
}
const char *
intr_typename(int type)
{
switch (type) {
case IST_NONE :
return "none";
case IST_PULSE:
return "pulsed";
case IST_EDGE:
return "edge-triggered";
case IST_LEVEL:
return "level-triggered";
default:
panic("intr_typename: invalid type %d", 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;
current = find_pic_by_irq(num);
if (current == NULL)
panic("%s: bogus IRQ %d", __func__, num);
current->pic_enable_irq(current, num - current->pic_intrbase);
}
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;
again:
#ifdef MULTIPROCESSOR
if (ci->ci_cpuid == 0) {
#endif
/* Do now unmasked pendings */
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);
KERNEL_LOCK(1, NULL);
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
(*ih->ih_fun)(ih->ih_arg);
ih = ih->ih_next;
}
KERNEL_UNLOCK_ONE(NULL);
mtmsr(dmsr);
ci->ci_cpl = pcpl;
is->is_ev.ev_count++;
pic->pic_reenable_irq(pic, is->is_hwirq - pic->pic_intrbase);
}
#ifdef MULTIPROCESSOR
}
#endif
if ((ci->ci_ipending & ~pcpl) & (1 << SIR_SERIAL)) {
ci->ci_ipending &= ~(1 << SIR_SERIAL);
splsoftserial();
mtmsr(emsr);
KERNEL_LOCK(1, NULL);
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
softintr__run(IPL_SOFTSERIAL);
#else
softserial();
#endif
KERNEL_UNLOCK_ONE(NULL);
mtmsr(dmsr);
ci->ci_cpl = pcpl;
ci->ci_ev_softserial.ev_count++;
goto again;
}
if ((ci->ci_ipending & ~pcpl) & (1 << SIR_NET)) {
#ifndef __HAVE_GENERIC_SOFT_INTERRUPTS
int pisr;
#endif
ci->ci_ipending &= ~(1 << SIR_NET);
splsoftnet();
#ifndef __HAVE_GENERIC_SOFT_INTERRUPTS
pisr = netisr;
netisr = 0;
#endif
mtmsr(emsr);
KERNEL_LOCK(1, NULL);
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
softintr__run(IPL_SOFTNET);
#else
softnet(pisr);
#endif
KERNEL_UNLOCK_ONE(NULL);
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);
KERNEL_LOCK(1, NULL);
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
softintr__run(IPL_SOFTCLOCK);
#else
softclock(NULL);
#endif
KERNEL_UNLOCK_ONE(NULL);
mtmsr(dmsr);
ci->ci_cpl = pcpl;
ci->ci_ev_softclock.ev_count++;
goto again;
}
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, 0);
if (realirq == 255)
return 0;
msr = mfmsr();
pcpl = ci->ci_cpl;
#ifdef MULTIPROCESSOR
/* Only cpu0 can handle interrupts. */
if (cpu_number() != 0) {
int realirq;
realirq = pic[0]->pic_get_irq(pic, cpu_number());
while (realirq == IPI_VECTOR) {
pic->pic_ack_irq(pic, cpu_number());
cpuintr(NULL);
realirq = pic->pic_get_irq(pic, cpu_number());
}
if (realirq == 255) {
return 0;
}
panic("non-IPI intr %d on cpu%d", realirq, cpu_number());
}
#endif
start:
#ifdef MULTIPROCESSOR
while (realirq == IPI_VECTOR) {
pic->pic_ack_irq(pic, 0);
cpuintr(NULL);
realirq = pic->pic_get_irq(pic, 0);
if (realirq == 255) {
return 0;
}
}
#endif
//aprint_error("%s: %s realirq %d", __func__, pic->pic_name, realirq);
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;
splraise(is->is_mask);
mtmsr(msr | PSL_EE);
KERNEL_LOCK(1, NULL);
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);
(*ih->ih_fun)(ih->ih_arg);
ih = ih->ih_next;
bail++;
}
KERNEL_UNLOCK_ONE(NULL);
mtmsr(msr);
ci->ci_cpl = pcpl;
pic->pic_reenable_irq(pic, realirq);
uvmexp.intrs++;
is->is_ev.ev_count++;
}
#ifdef PIC_DEBUG
boo:
#endif /* PIC_DEBUG */
pic->pic_ack_irq(pic, 0);
realirq = pic->pic_get_irq(pic, 0);
if (realirq != 255)
goto start;
mtmsr(msr | PSL_EE);
splx(pcpl); /* Process pendings. */
mtmsr(msr);
return 0;
}
void
pic_ext_intr(void)
{
KASSERT(pics[0] != NULL);
pic_handle_intr(pics[0]);
return;
}