Code for dealing with external interrupts.

Obtained from:	NetBSD
This commit is contained in:
Benno Rice 2001-06-27 12:33:51 +00:00
parent aa623ed281
commit bda034cf8f
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=78883

View File

@ -0,0 +1,832 @@
/*-
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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.
*
* $NetBSD: extintr.c,v 1.12 2000/02/14 12:45:52 tsubai Exp $
* @(#)isa.c 7.2 (Berkeley) 5/12/91
*/
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/mutex.h>
#include <sys/interrupt.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <machine/autoconf.h>
#include <machine/intr.h>
#include <machine/psl.h>
#include <machine/pio.h>
#include <machine/openpicreg.h>
#include <dev/ofw/openfirm.h>
#define NIRQ 32
#define HWIRQ_MAX (NIRQ - 4 - 1)
#define HWIRQ_MASK 0x0fffffff
void intr_calculatemasks __P((void));
char *intr_typename __P((int));
int fakeintr __P((void *));
static __inline int cntlzw __P((int));
static __inline int read_irq __P((void));
static __inline int mapirq __P((int));
static void enable_irq __P((int));
static __inline u_int openpic_read __P((int));
static __inline void openpic_write __P((int, u_int));
void openpic_enable_irq __P((int));
void openpic_disable_irq __P((int));
void openpic_set_priority __P((int, int));
static __inline int openpic_read_irq __P((int));
static __inline void openpic_eoi __P((int));
unsigned int imen = 0xffffffff;
u_int cpl, ipending, tickspending;
int astpending;
int imask[NIPL];
int intrtype[NIRQ], intrmask[NIRQ], intrlevel[NIRQ];
struct intrhand *intrhand[NIRQ];
static u_char hwirq[NIRQ], virq[ICU_LEN];
static int virq_max = 0;
static u_char *obio_base, *openpic_base;
#if 0 /* XXX */
extern u_int *heathrow_FCR;
#endif
extern void install_extint(void (*)(void));
#define interrupt_reg (obio_base + 0x10)
#define INT_STATE_REG_H (interrupt_reg + 0x00)
#define INT_ENABLE_REG_H (interrupt_reg + 0x04)
#define INT_CLEAR_REG_H (interrupt_reg + 0x08)
#define INT_LEVEL_REG_H (interrupt_reg + 0x0c)
#define INT_STATE_REG_L (interrupt_reg + 0x10)
#define INT_ENABLE_REG_L (interrupt_reg + 0x14)
#define INT_CLEAR_REG_L (interrupt_reg + 0x18)
#define INT_LEVEL_REG_L (interrupt_reg + 0x1c)
#define have_openpic (openpic_base != NULL)
/*
* Map 64 irqs into 32 (bits).
*/
int
mapirq(irq)
int irq;
{
int v;
if (irq < 0 || irq >= ICU_LEN)
panic("invalid irq");
if (virq[irq])
return virq[irq];
virq_max++;
v = virq_max;
if (v > HWIRQ_MAX)
panic("virq overflow");
hwirq[v] = irq;
virq[irq] = v;
return v;
}
/*
* Count leading zeros.
*/
static __inline int
cntlzw(x)
int x;
{
int a;
__asm __volatile ("cntlzw %0,%1" : "=r"(a) : "r"(x));
return a;
}
int
read_irq()
{
int rv, lo, hi, p;
rv = 0;
lo = in32rb(INT_STATE_REG_L);
if (lo)
out32rb(INT_CLEAR_REG_L, lo);
while (lo) {
p = 31 - cntlzw(lo);
rv |= 1 << virq[p];
lo &= ~(1 << p);
}
#if 0 /* XXX */
if (heathrow_FCR) /* has heathrow? */
hi = in32rb(INT_STATE_REG_H);
else
#endif
hi = 0;
if (hi)
out32rb(INT_CLEAR_REG_H, hi);
while (hi) {
p = 31 - cntlzw(hi);
rv |= 1 << virq[p + 32];
hi &= ~(1 << p);
}
/* 1 << 0 is invalid. */
return rv & ~1;
}
void
enable_irq(x)
int x;
{
int lo, hi, v, irq;
x &= HWIRQ_MASK; /* XXX Higher bits are software interrupts. */
lo = hi = 0;
while (x) {
v = 31 - cntlzw(x);
irq = hwirq[v];
if (irq < 32)
lo |= 1 << irq;
else
hi |= 1 << (irq - 32);
x &= ~(1 << v);
}
out32rb(INT_ENABLE_REG_L, lo);
#if 0 /* XXX */
if (heathrow_FCR)
out32rb(INT_ENABLE_REG_H, hi);
#endif
}
u_int
openpic_read(reg)
int reg;
{
char *addr
addr = openpic_base + reg;
return in32rb(addr);
}
void
openpic_write(reg, val)
int reg;
u_int val;
{
char *addr
addr = openpic_base + reg;
out32rb(addr, val);
}
void
openpic_enable_irq(irq)
int irq;
{
u_int x;
x = openpic_read(OPENPIC_SRC_VECTOR(irq));
x &= ~OPENPIC_IMASK;
openpic_write(OPENPIC_SRC_VECTOR(irq), x);
}
void
openpic_disable_irq(irq)
int irq;
{
u_int x;
x = openpic_read(OPENPIC_SRC_VECTOR(irq));
x |= OPENPIC_IMASK;
openpic_write(OPENPIC_SRC_VECTOR(irq), x);
}
void
openpic_set_priority(cpu, pri)
int cpu, pri;
{
u_int x;
x = openpic_read(OPENPIC_CPU_PRIORITY(cpu));
x &= ~OPENPIC_CPU_PRIORITY_MASK;
x |= pri;
openpic_write(OPENPIC_CPU_PRIORITY(cpu), x);
}
int
openpic_read_irq(cpu)
int cpu;
{
return openpic_read(OPENPIC_IACK(cpu)) & OPENPIC_VECTOR_MASK;
}
void
openpic_eoi(cpu)
int cpu;
{
openpic_write(OPENPIC_EOI(cpu), 0);
openpic_read(OPENPIC_EOI(cpu));
}
/*
* 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
intr_calculatemasks()
{
int irq, level, irqs;
struct intrhand *q;
irqs = 0;
/* First, figure out which levels each IRQ uses. */
#if 0 /* XXX */
for (irq = 0; irq < NIRQ; irq++) {
register int levels = 0;
for (q = intrhand[irq]; q; q = q->ih_next)
levels |= 1 << q->ih_level;
intrlevel[irq] = levels;
}
#endif
/* Then figure out which IRQs use each level. */
for (level = 0; level < NIPL; level++) {
register int irqs = 0;
for (irq = 0; irq < NIRQ; irq++)
if (intrlevel[irq] & (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;
/*
* Enforce a hierarchy that gives slow devices a better chance at not
* dropping data.
*/
imask[IPL_SOFTCLOCK] |= imask[IPL_NONE];
imask[IPL_SOFTNET] |= imask[IPL_SOFTCLOCK];
imask[IPL_BIO] |= imask[IPL_SOFTNET];
imask[IPL_NET] |= imask[IPL_BIO];
imask[IPL_SOFTSERIAL] |= imask[IPL_NET];
imask[IPL_TTY] |= imask[IPL_SOFTSERIAL];
/*
* There are tty, network and disk drivers that use free() at interrupt
* time, so imp > (tty | net | bio).
*/
imask[IPL_IMP] |= imask[IPL_TTY];
imask[IPL_AUDIO] |= imask[IPL_IMP];
/*
* Since run queues may be manipulated by both the statclock and tty,
* network, and disk drivers, clock > imp.
*/
imask[IPL_CLOCK] |= imask[IPL_AUDIO];
/*
* IPL_HIGH must block everything that can manipulate a run queue.
*/
imask[IPL_HIGH] |= imask[IPL_CLOCK];
/*
* We need serial drivers to run at the absolute highest priority to
* avoid overruns, so serial > high.
*/
imask[IPL_SERIAL] |= imask[IPL_HIGH];
/* And eventually calculate the complete masks. */
#if 0 /* XXX */
for (irq = 0; irq < NIRQ; irq++) {
register int irqs = 1 << irq;
for (q = intrhand[irq]; q; q = q->ih_next)
irqs |= imask[q->ih_level];
intrmask[irq] = irqs;
}
#endif
/* Lastly, determine which IRQs are actually in use. */
for (irq = 0; irq < NIRQ; irq++)
if (intrhand[irq])
irqs |= 1 << irq;
if (have_openpic) {
for (irq = 0; irq < NIRQ; irq++) {
if (irqs & (1 << irq))
openpic_enable_irq(hwirq[irq]);
else
openpic_disable_irq(hwirq[irq]);
}
} else {
imen = ~irqs;
enable_irq(~imen);
}
}
int
fakeintr(arg)
void *arg;
{
return 0;
}
#define LEGAL_IRQ(x) ((x) >= 0 && (x) < NIRQ)
char *
intr_typename(type)
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);
#if 1 /* XXX */
return ("unknown");
#endif
}
}
/*
* Register an interrupt handler.
*/
void *
intr_establish(int irq, int type, int level, int (*ih_fun)(void *),
void *ih_arg)
{
struct intrhand **p, *q, *ih;
static struct intrhand fakehand = {fakeintr};
irq = mapirq(irq);
/* 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_IRQ(irq) || type == IST_NONE)
panic("intr_establish: bogus irq or type");
switch (intrtype[irq]) {
case IST_NONE:
intrtype[irq] = type;
break;
case IST_EDGE:
case IST_LEVEL:
if (type == intrtype[irq])
break;
case IST_PULSE:
if (type != IST_NONE)
panic("intr_establish: can't share %s with %s",
intr_typename(intrtype[irq]),
intr_typename(type));
break;
}
/*
* Figure out where to put the handler.
* This is O(N^2), but we want to preserve the order, and N is
* generally small.
*/
#if 0 /* XXX */
for (p = &intrhand[irq]; (q = *p) != NULL; p = &q->ih_next)
;
#endif
/*
* 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.
*/
#if 0 /* XXX */
fakehand.ih_level = level;
#endif
*p = &fakehand;
intr_calculatemasks();
/*
* Poke the real handler in now.
*/
#if 0
ih->ih_fun = ih_fun;
ih->ih_arg = ih_arg;
ih->ih_count = 0;
ih->ih_next = NULL;
ih->ih_level = level;
ih->ih_irq = irq;
#endif
*p = ih;
return (ih);
}
/*
* Deregister an interrupt handler.
*/
void
intr_disestablish(void *arg)
{
struct intrhand *ih, **p, *q;
int irq;
#if 0 /* XXX */
irq = ih->ih_irq;
#endif
ih = arg;
if (!LEGAL_IRQ(irq))
panic("intr_disestablish: bogus irq");
/*
* Remove the handler from the chain.
* This is O(n^2), too.
*/
for (p = &intrhand[irq]; (q = *p) != NULL && q != ih; p = &q->ih_next)
;
if (q)
#if 0 /* XXX */
*p = q->ih_next;
#else
p = p;
#endif
else
panic("intr_disestablish: handler not registered");
free((void *)ih, M_DEVBUF);
intr_calculatemasks();
if (intrhand[irq] == NULL)
intrtype[irq] = IST_NONE;
}
/*
* external interrupt handler
*/
void
ext_intr(void)
{
int irq;
int o_imen, r_imen;
int pcpl;
struct intrhand *ih;
volatile unsigned long int_state;
pcpl = splhigh(); /* Turn off all */
int_state = read_irq();
if (int_state == 0)
goto out;
start:
irq = 31 - cntlzw(int_state);
o_imen = imen;
r_imen = 1 << irq;
if ((pcpl & r_imen) != 0) {
ipending |= r_imen; /* Masked! Mark this as pending */
imen |= r_imen;
enable_irq(~imen);
} else {
ih = intrhand[irq];
while (ih) {
#if 0 /* XXX */
(*ih->ih_fun)(ih->ih_arg);
ih = ih->ih_next;
#endif
}
intrcnt[hwirq[irq]]++;
}
int_state &= ~r_imen;
if (int_state)
goto start;
out:
splx(pcpl); /* Process pendings. */
}
void
ext_intr_openpic()
{
int irq, realirq;
int r_imen;
int pcpl;
struct intrhand *ih;
pcpl = splhigh(); /* Turn off all */
realirq = openpic_read_irq(0);
if (realirq == 255) {
printf("sprious interrupt\n");
goto out;
}
start:
irq = virq[realirq];
/* XXX check range */
r_imen = 1 << irq;
if ((pcpl & r_imen) != 0) {
ipending |= r_imen; /* Masked! Mark this as pending */
openpic_disable_irq(realirq);
} else {
ih = intrhand[irq];
while (ih) {
#if 0 /* XXX */
(*ih->ih_fun)(ih->ih_arg);
ih = ih->ih_next;
#endif
}
intrcnt[hwirq[irq]]++;
}
openpic_eoi(0);
realirq = openpic_read_irq(0);
if (realirq != 255)
goto start;
out:
splx(pcpl); /* Process pendings. */
}
void
do_pending_int()
{
struct intrhand *ih;
int irq;
int pcpl;
int hwpend;
int emsr, dmsr;
static int processing;
return; /* XXX */
if (processing)
return;
processing = 1;
emsr = mfmsr();
dmsr = emsr & ~PSL_EE;
mtmsr(dmsr);
pcpl = splhigh(); /* Turn off all */
hwpend = ipending & ~pcpl; /* Do now unmasked pendings */
if (!have_openpic) {
imen &= ~hwpend;
enable_irq(~imen);
}
hwpend &= HWIRQ_MASK;
while (hwpend) {
irq = 31 - cntlzw(hwpend);
hwpend &= ~(1L << irq);
ih = intrhand[irq];
while(ih) {
#if 0 /* XXX */
(*ih->ih_fun)(ih->ih_arg);
ih = ih->ih_next;
#endif
}
intrcnt[hwirq[irq]]++;
if (have_openpic)
openpic_enable_irq(hwirq[irq]);
}
/*out32rb(INT_ENABLE_REG, ~imen);*/
if ((ipending & ~pcpl) & (1 << SIR_CLOCK)) {
ipending &= ~(1 << SIR_CLOCK);
softclock(NULL);
intrcnt[CNT_SOFTCLOCK]++;
}
#if 0 /* XXX */
if ((ipending & ~pcpl) & (1 << SIR_NET)) {
ipending &= ~(1 << SIR_NET);
softnet();
intrcnt[CNT_SOFTNET]++;
}
if ((ipending & ~pcpl) & (1 << SIR_SERIAL)) {
ipending &= ~(1 << SIR_SERIAL);
softserial();
intrcnt[CNT_SOFTSERIAL]++;
}
#endif
ipending &= pcpl;
cpl = pcpl; /* Don't use splx... we are here already! */
mtmsr(emsr);
processing = 0;
}
void
openpic_init(void)
{
int irq;
u_int x;
/* disable all interrupts */
for (irq = 0; irq < 256; irq++)
openpic_write(OPENPIC_SRC_VECTOR(irq), OPENPIC_IMASK);
openpic_set_priority(0, 15);
/* we don't need 8259 pass through mode */
x = openpic_read(OPENPIC_CONFIG);
x |= OPENPIC_CONFIG_8259_PASSTHRU_DISABLE;
openpic_write(OPENPIC_CONFIG, x);
/* send all interrupts to cpu 0 */
for (irq = 0; irq < ICU_LEN; irq++)
openpic_write(OPENPIC_IDEST(irq), 1 << 0);
for (irq = 0; irq < ICU_LEN; irq++) {
x = irq;
x |= OPENPIC_IMASK;
x |= OPENPIC_POLARITY_POSITIVE;
x |= OPENPIC_SENSE_LEVEL;
x |= 8 << OPENPIC_PRIORITY_SHIFT;
openpic_write(OPENPIC_SRC_VECTOR(irq), x);
}
/* XXX set spurious intr vector */
openpic_set_priority(0, 0);
/* clear all pending interrunts */
for (irq = 0; irq < 256; irq++) {
openpic_read_irq(0);
openpic_eoi(0);
}
for (irq = 0; irq < ICU_LEN; irq++)
openpic_disable_irq(irq);
install_extint(ext_intr_openpic);
}
void
legacy_int_init(void)
{
out32rb(INT_ENABLE_REG_L, 0); /* disable all intr. */
out32rb(INT_CLEAR_REG_L, 0xffffffff); /* clear pending intr. */
install_extint(ext_intr);
}
#define HEATHROW_FCR_OFFSET 0x38 /* XXX should not here */
#define GC_OBIO_BASE 0xf3000000
void
init_interrupt(void)
{
int chosen;
int mac_io, reg[5];
int32_t ictlr;
char type[32];
openpic_base = NULL;
mac_io = OF_finddevice("mac-io");
if (mac_io == -1)
mac_io = OF_finddevice("/pci/mac-io");
if (mac_io == -1) {
/*
* No mac-io. Assume Grand-Central or OHare.
*/
obio_base = (void *)GC_OBIO_BASE;
legacy_int_init();
return;
}
if (OF_getprop(mac_io, "assigned-addresses", reg, sizeof(reg)) < 20)
goto failed;
obio_base = (void *)reg[2];
#if 0 /* XXX */
heathrow_FCR = (void *)(obio_base + HEATHROW_FCR_OFFSET);
#endif
bzero(type, sizeof(type));
chosen = OF_finddevice("/chosen");
if (OF_getprop(chosen, "interrupt-controller", &ictlr, 4) == 4)
OF_getprop(ictlr, "device_type", type, sizeof(type));
if (strcmp(type, "open-pic") != 0) {
/*
* Not an open-pic. Must be a Heathrow (compatible).
*/
legacy_int_init();
return;
} else {
/*
* We have an Open PIC.
*/
if (OF_getprop(ictlr, "reg", reg, sizeof(reg)) < 8)
goto failed;
openpic_base = (void *)(obio_base + reg[0]);
openpic_init();
return;
}
printf("unknown interrupt controller\n");
failed:
panic("init_interrupt: failed to initialize interrupt controller");
}