[BACK]Return to ioapic.c CVS log [TXT][DIR] Up to [local] / sys / arch / i386 / i386

File: [local] / sys / arch / i386 / i386 / ioapic.c (download)

Revision 1.1, Tue Mar 4 16:06:22 2008 UTC (16 years, 2 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*	$OpenBSD: ioapic.c,v 1.14 2007/02/22 19:46:16 marco Exp $	*/
/* 	$NetBSD: ioapic.c,v 1.7 2003/07/14 22:32:40 lukem Exp $	*/

/*-
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by RedBack Networks Inc.
 *
 * Author: Bill Sommerfeld
 *
 * 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 NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. 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.
 */


/*
 * Copyright (c) 1999 Stefan Grefen
 *
 * 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 NetBSD
 *      Foundation, Inc. and its contributors.
 * 4. 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 AUTHOR 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 AUTHOR AND 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/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>

#include <machine/bus.h>
#include <machine/psl.h>

#include <uvm/uvm_extern.h>

#include <machine/i82093reg.h>
#include <machine/i82093var.h>

#include <machine/i82489reg.h>
#include <machine/i82489var.h>

#include <machine/pmap.h>

#include <machine/mpbiosvar.h>

#include "isa.h"

/*
 * XXX locking
 */

int     ioapic_match(struct device *, void *, void *);
void    ioapic_attach(struct device *, struct device *, void *);

/* XXX */
extern int bus_mem_add_mapping(bus_addr_t, bus_size_t, int,
    bus_space_handle_t *);

void	apic_set_redir(struct ioapic_softc *, int);
void	apic_vectorset(struct ioapic_softc *, int, int, int);

void	apic_stray(int);

int apic_verbose = 0;

int ioapic_bsp_id = 0;
int ioapic_cold = 1;

struct ioapic_softc *ioapics;	 /* head of linked list */
int nioapics = 0;	   	 /* number attached */
static int ioapic_vecbase;

void ioapic_set_id(struct ioapic_softc *);

/*
 * A bitmap telling what APIC IDs usable for I/O APICs are free.
 * The size must be at least IOAPIC_ID_MAX bits (16).
 */
u_int16_t ioapic_id_map = (1 << IOAPIC_ID_MAX) - 1;

/*
 * When we renumber I/O APICs we provide a mapping vector giving us the new
 * ID out of the old BIOS supplied one.  Each item must be able to hold IDs
 * in [0, IOAPIC_ID_MAX << 1), since we use an extra bit to tell if the ID
 * has actually been remapped.
 */
u_int8_t ioapic_id_remap[IOAPIC_ID_MAX];

/*
 * Register read/write routines.
 */
static __inline u_int32_t
ioapic_read(struct ioapic_softc *sc, int regid)
{
	u_int32_t val;

	/*
	 * XXX lock apic
	 */
	*(sc->sc_reg) = regid;
	val = *sc->sc_data;

	return (val);

}

static __inline void
ioapic_write(struct ioapic_softc *sc, int regid, int val)
{
	/*
	 * XXX lock apic
	 */
	*(sc->sc_reg) = regid;
	*(sc->sc_data) = val;
}

struct ioapic_softc *
ioapic_find(int apicid)
{
	struct ioapic_softc *sc;

	if (apicid == MPS_ALL_APICS) {	/* XXX mpbios-specific */
		/*
		 * XXX kludge for all-ioapics interrupt support
		 * on single ioapic systems
		 */
		if (nioapics <= 1)
			return (ioapics);
		panic("unsupported: all-ioapics interrupt with >1 ioapic");
	}

	for (sc = ioapics; sc != NULL; sc = sc->sc_next)
		if (sc->sc_apicid == apicid)
			return (sc);

	return (NULL);
}

/*
 * For the case the I/O APICs were configured using ACPI, there must
 * be an option to match global ACPI interrupts with APICs.
 */
struct ioapic_softc *
ioapic_find_bybase(int vec)
{
	struct ioapic_softc *sc;

	for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
		if (vec >= sc->sc_apic_vecbase &&
		    vec < (sc->sc_apic_vecbase + sc->sc_apic_sz))
			return sc;
	}

	return NULL;
}

static __inline void
ioapic_add(struct ioapic_softc *sc)
{
	sc->sc_next = ioapics;
	ioapics = sc;
	nioapics++;
}

void
ioapic_print_redir(struct ioapic_softc *sc, char *why, int pin)
{
	u_int32_t redirlo = ioapic_read(sc, IOAPIC_REDLO(pin));
	u_int32_t redirhi = ioapic_read(sc, IOAPIC_REDHI(pin));

	apic_format_redir(sc->sc_dev.dv_xname, why, pin, redirhi, redirlo);
}

struct cfattach ioapic_ca = {
	sizeof(struct ioapic_softc), ioapic_match, ioapic_attach
};

struct cfdriver ioapic_cd = {
	NULL, "ioapic", DV_DULL /* XXX DV_CPU ? */
};

int
ioapic_match(struct device *parent, void *matchv, void *aux)
{
        struct cfdata *match = (struct cfdata *)matchv;
	struct apic_attach_args * aaa = (struct apic_attach_args *)aux;

	if (strcmp(aaa->aaa_name, match->cf_driver->cd_name) == 0)
		return (1);
	return (0);
}

/* Reprogram the APIC ID, and check that it actually got set. */
void
ioapic_set_id(struct ioapic_softc *sc) {
	u_int8_t apic_id;

	ioapic_write(sc, IOAPIC_ID,
	    (ioapic_read(sc, IOAPIC_ID) & ~IOAPIC_ID_MASK) |
	    (sc->sc_apicid << IOAPIC_ID_SHIFT));

	apic_id = (ioapic_read(sc, IOAPIC_ID) & IOAPIC_ID_MASK) >>
	    IOAPIC_ID_SHIFT;

	if (apic_id != sc->sc_apicid)
		printf(", can't remap to apid %d\n", sc->sc_apicid);
	else
		printf(", remapped to apid %d\n", sc->sc_apicid);
}

/*
 * can't use bus_space_xxx as we don't have a bus handle ...
 */
void
ioapic_attach(struct device *parent, struct device *self, void *aux)
{
	struct ioapic_softc *sc = (struct ioapic_softc *)self;
	struct apic_attach_args  *aaa = (struct apic_attach_args *)aux;
	int apic_id;
	int8_t new_id;
	bus_space_handle_t bh;
	u_int32_t ver_sz;
	int i, ioapic_found;

	sc->sc_flags = aaa->flags;
	sc->sc_apicid = aaa->apic_id;

	printf(": apid %d pa 0x%lx", aaa->apic_id, aaa->apic_address);

	if (bus_mem_add_mapping(aaa->apic_address, PAGE_SIZE, 0, &bh) != 0) {
		printf(", map failed\n");
		return;
	}
	sc->sc_reg = (volatile u_int32_t *)(bh + IOAPIC_REG);
	sc->sc_data = (volatile u_int32_t *)(bh + IOAPIC_DATA);

	ver_sz = ioapic_read(sc, IOAPIC_VER);
	sc->sc_apic_vers = (ver_sz & IOAPIC_VER_MASK) >> IOAPIC_VER_SHIFT;
	sc->sc_apic_sz = (ver_sz & IOAPIC_MAX_MASK) >> IOAPIC_MAX_SHIFT;
	sc->sc_apic_sz++;

	if (aaa->apic_vecbase != -1)
		sc->sc_apic_vecbase = aaa->apic_vecbase;
	else {
		/*
		 * XXX this assumes ordering of ioapics in the table.
		 * Only needed for broken BIOS workaround (see mpbios.c)
		 */
		sc->sc_apic_vecbase = ioapic_vecbase;
		ioapic_vecbase += sc->sc_apic_sz;
	}

	if (mp_verbose) {
		printf(", %s mode",
		    aaa->flags & IOAPIC_PICMODE ? "PIC" : "virtual wire");
	}

	printf(", version %x, %d pins\n", sc->sc_apic_vers, sc->sc_apic_sz);

	/*
	 * If either a LAPIC or an I/O APIC is already at the ID the BIOS
	 * setup for this I/O APIC, try to find a free ID to use and reprogram
	 * the chip.  Record this remapping since all references done by the
	 * MP BIOS will be through the old ID.
	 */
	ioapic_found = ioapic_find(sc->sc_apicid) != NULL;
	if (cpu_info[sc->sc_apicid] != NULL || ioapic_found) {
		printf("%s: duplicate apic id", sc->sc_dev.dv_xname);
		new_id = ffs(ioapic_id_map) - 1;
		if (new_id == -1) {
			printf(" (and none free, ignoring)\n");
			return;
		}

		/*
		 * If there were many I/O APICs at the same ID, we choose
		 * to let later references to that ID (in the MP BIOS) refer
		 * to the first found.
		 */
		if (!ioapic_found && !IOAPIC_REMAPPED(sc->sc_apicid))
			IOAPIC_REMAP(sc->sc_apicid, new_id);
		sc->sc_apicid = new_id;
		ioapic_set_id(sc);
	}
	ioapic_id_map &= ~(1 << sc->sc_apicid);

	ioapic_add(sc);

	apic_id = (ioapic_read(sc, IOAPIC_ID) & IOAPIC_ID_MASK) >>
	    IOAPIC_ID_SHIFT;

	sc->sc_pins = malloc(sizeof(struct ioapic_pin) * sc->sc_apic_sz,
	    M_DEVBUF, M_WAITOK);

	for (i=0; i<sc->sc_apic_sz; i++) {
		sc->sc_pins[i].ip_handler = NULL;
		sc->sc_pins[i].ip_next = NULL;
		sc->sc_pins[i].ip_map = NULL;
		sc->sc_pins[i].ip_vector = 0;
		sc->sc_pins[i].ip_type = 0;
		sc->sc_pins[i].ip_minlevel = 0xff; /* XXX magic*/
		sc->sc_pins[i].ip_maxlevel = 0;	/* XXX magic */
	}

	/*
	 * In case the APIC is not initialized to the correct ID
	 * do it now.
	 */
	if (apic_id != sc->sc_apicid) {
		printf("%s: misconfigured as apic %d", sc->sc_dev.dv_xname,
		    apic_id);
		ioapic_set_id(sc);
	}
#if 0
	/* output of this was boring. */
	if (mp_verbose)
		for (i=0; i<sc->sc_apic_sz; i++)
			ioapic_print_redir(sc, "boot", i);
#endif
}

/*
 * Interrupt mapping.
 *
 * Multiple handlers may exist for each pin, so there's an
 * intrhand chain for each pin.
 *
 * Ideally, each pin maps to a single vector at the priority of the
 * highest level interrupt for that pin.
 *
 * XXX in the event that there are more than 16 interrupt sources at a
 * single level, some doubling-up may be needed.  This is not yet
 * implemented.
 *
 * XXX we are wasting some space here because we only use a limited
 * range of the vectors here.  (0x30..0xef)
 */

struct intrhand *apic_intrhand[256];
int	apic_intrcount[256];
int	apic_maxlevel[256];


/* XXX should check vs. softc max int number */
#define	LEGAL_IRQ(x)	((x) >= 0 && (x) < APIC_ICU_LEN && (x) != 2)

void
apic_set_redir(struct ioapic_softc *sc, int pin)
{
	u_int32_t redlo;
	u_int32_t redhi = 0;
	int delmode;

	struct ioapic_pin *pp;
	struct mp_intr_map *map;

	pp = &sc->sc_pins[pin];
	map = pp->ip_map;
	redlo = (map == NULL) ? IOAPIC_REDLO_MASK : map->redir;
	delmode = (redlo & IOAPIC_REDLO_DEL_MASK) >> IOAPIC_REDLO_DEL_SHIFT;

	/* XXX magic numbers */
	if ((delmode != 0) && (delmode != 1))
		;
	else if (pp->ip_handler == NULL) {
		redlo |= IOAPIC_REDLO_MASK;
	} else {
		redlo |= (pp->ip_vector & 0xff);
		redlo &= ~IOAPIC_REDLO_DEL_MASK;
		redlo |= (IOAPIC_REDLO_DEL_FIXED << IOAPIC_REDLO_DEL_SHIFT);
		redlo &= ~IOAPIC_REDLO_DSTMOD;

		/*
		 * Destination: BSP CPU
		 *
		 * XXX will want to distribute interrupts across cpu's
		 * eventually.  most likely, we'll want to vector each
		 * interrupt to a specific CPU and load-balance across
		 * cpu's.  but there's no point in doing that until after
		 * most interrupts run without the kernel lock.
		 */
		redhi |= (ioapic_bsp_id << IOAPIC_REDHI_DEST_SHIFT);

		/* XXX derive this bit from BIOS info */
		if (pp->ip_type == IST_LEVEL)
			redlo |= IOAPIC_REDLO_LEVEL;
		else
			redlo &= ~IOAPIC_REDLO_LEVEL;
		if (map != NULL && ((map->flags & 3) == MPS_INTPO_DEF)) {
			if (pp->ip_type == IST_LEVEL)
				redlo |= IOAPIC_REDLO_ACTLO;
			else
				redlo &= ~IOAPIC_REDLO_ACTLO;
		}
	}
	/* Do atomic write */
	ioapic_write(sc, IOAPIC_REDLO(pin), IOAPIC_REDLO_MASK);
	ioapic_write(sc, IOAPIC_REDHI(pin), redhi);
	ioapic_write(sc, IOAPIC_REDLO(pin), redlo);
	if (mp_verbose)
		ioapic_print_redir(sc, "int", pin);
}

/*
 * XXX To be really correct an NISA > 0 condition should check for these.
 * However, the i386 port pretty much assumes isa is there anyway.
 * For example, pci_intr_establish calls isa_intr_establish unconditionally.
 */
extern int fakeintr(void *); 	/* XXX headerify */
extern char *isa_intr_typename(int); 	/* XXX headerify */

/*
 * apic_vectorset: allocate a vector for the given pin, based on
 * the levels of the interrupts on that pin.
 *
 * XXX if the level of the pin changes while the pin is
 * masked, need to do something special to prevent pending
 * interrupts from being lost.
 * (the answer may be to hang the interrupt chain off of both vectors
 * until any interrupts from the old source have been handled.  the trouble
 * is that we don't have a global view of what interrupts are pending.
 *
 * Deferring for now since MP systems are more likely servers rather
 * than laptops or desktops, and thus will have relatively static
 * interrupt configuration.
 */

void
apic_vectorset(struct ioapic_softc *sc, int pin, int minlevel, int maxlevel)
{
	struct ioapic_pin *pp = &sc->sc_pins[pin];
	int ovector = 0;
	int nvector = 0;

	ovector = pp->ip_vector;
	
	if (maxlevel == 0) {
		/* no vector needed. */
		pp->ip_minlevel = 0xff; /* XXX magic */
		pp->ip_maxlevel = 0; /* XXX magic */
		pp->ip_vector = 0;
	} else if (maxlevel != pp->ip_maxlevel) {
#ifdef MPVERBOSE
		if (minlevel != maxlevel)
			printf("%s: pin %d shares different IPL interrupts "
			    "(%x..%x)\n", sc->sc_dev.dv_xname, pin,
			    minlevel, maxlevel);
#endif

		/*
		 * Allocate interrupt vector at the *lowest* priority level
		 * of any of the handlers invoked by this pin.
		 *
		 * The interrupt handler will raise ipl higher than this
		 * as appropriate.
		 */
		nvector = idt_vec_alloc(minlevel, minlevel+15);

		if (nvector == 0) {
			/*
			 * XXX XXX we should be able to deal here..
			 * need to double-up an existing vector
			 * and install a slightly different handler.
			 */
			panic("%s: can't alloc vector for pin %d at level %x",
			    sc->sc_dev.dv_xname, pin, maxlevel);
		}
		apic_maxlevel[nvector] = maxlevel;
		/*
		 * XXX want special handler for the maxlevel != minlevel
		 * case here!
		 */
		idt_vec_set(nvector, apichandler[nvector & 0xf]);
		pp->ip_vector = nvector;
		pp->ip_minlevel = minlevel;
		pp->ip_maxlevel = maxlevel;
	}
	apic_intrhand[pp->ip_vector] = pp->ip_handler;

	if (ovector) {
		/*
		 * XXX should defer this until we're sure the old vector
		 * doesn't have a pending interrupt on any processor.
		 * do this by setting a counter equal to the number of CPU's,
		 * and firing off a low-priority broadcast IPI to all cpu's.
		 * each cpu then decrements the counter; when it
		 * goes to zero, free the vector..
		 * i.e., defer until all processors have run with a CPL
		 * less than the level of the interrupt..
		 *
		 * this is only an issue for dynamic interrupt configuration
		 * (e.g., cardbus or pcmcia).
		 */
		apic_intrhand[ovector] = NULL;
		idt_vec_free(ovector);
		printf("freed vector %x\n", ovector);
	}

	apic_set_redir(sc, pin);
}

/*
 * Throw the switch and enable interrupts..
 */

void
ioapic_enable(void)
{
	int p, maxlevel, minlevel;
	struct ioapic_softc *sc;
	struct intrhand *q;
	extern void intr_calculatemasks(void); /* XXX */

	intr_calculatemasks();	/* for softints, AST's */

	ioapic_cold = 0;

	if (ioapics == NULL)
		return;

#if 1 /* XXX Will probably get removed */
	lapic_set_softvectors();
	lapic_set_lvt();
#endif

	if (ioapics->sc_flags & IOAPIC_PICMODE) {
		printf("%s: writing to IMCR to disable pics\n",
		    ioapics->sc_dev.dv_xname);
		outb(IMCR_ADDR, IMCR_REGISTER);
		outb(IMCR_DATA, IMCR_APIC);
	}

#if 0 /* XXX Will be removed when we have intrsource. */
	isa_nodefaultirq();
#endif
			
	for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
		if (mp_verbose)
			printf("%s: enabling\n", sc->sc_dev.dv_xname);

		for (p=0; p<sc->sc_apic_sz; p++) {
			maxlevel = 0;	 /* magic */
			minlevel = 0xff; /* magic */
				
			for (q = sc->sc_pins[p].ip_handler; q != NULL;
			     q = q->ih_next) {
				if (q->ih_level > maxlevel)
					maxlevel = q->ih_level;
				if (q->ih_level < minlevel)
					minlevel = q->ih_level;
			}
			apic_vectorset(sc, p, minlevel, maxlevel);
		}
	}
}

/*
 * Interrupt handler management with the apic is radically different from the
 * good old 8259.
 *
 * The APIC adds an additional level of indirection between interrupt
 * signals and interrupt vectors in the IDT.
 * It also encodes a priority into the high-order 4 bits of the IDT vector
 * number.
 *
 *
 * interrupt establishment:
 *	-> locate interrupt pin.
 *	-> locate or allocate vector for pin.
 *	-> locate or allocate handler chain for vector.
 *	-> chain interrupt into handler chain.
 * 	#ifdef notyet
 *	-> if level of handler chain increases, reallocate vector, move chain.
 *	#endif
 */

void *
apic_intr_establish(int irq, int type, int level, int (*ih_fun)(void *),
    void *ih_arg, char *ih_what)
{
	unsigned int ioapic = APIC_IRQ_APIC(irq);
	unsigned int intr = APIC_IRQ_PIN(irq);
	struct ioapic_softc *sc = ioapic_find(ioapic);
	struct ioapic_pin *pin;
	struct intrhand **p, *q, *ih;
	static struct intrhand fakehand = {fakeintr};
	extern int cold;
	int minlevel, maxlevel;

	if (sc == NULL)
		panic("apic_intr_establish: unknown ioapic %d", ioapic);

	if ((irq & APIC_INT_VIA_APIC) == 0)
		panic("apic_intr_establish of non-apic interrupt 0x%x", irq);

	if (intr >= sc->sc_apic_sz || type == IST_NONE)
		panic("apic_intr_establish: bogus intr or type");

	/* no point in sleeping unless someone can free memory. */
	ih = malloc(sizeof *ih, M_DEVBUF, cold ? M_NOWAIT : M_WAITOK);
	if (ih == NULL)
		panic("apic_intr_establish: can't malloc handler info");

	pin = &sc->sc_pins[intr];
	switch (pin->ip_type) {
	case IST_NONE:
		pin->ip_type = type;
		break;
	case IST_EDGE:
	case IST_LEVEL:
		if (type == pin->ip_type)
			break;
	case IST_PULSE:
		if (type != IST_NONE) {
			/*printf("%s: intr_establish: can't share %s with %s, irq %d\n",
			    ih_what, isa_intr_typename(pin->ip_type),
			    isa_intr_typename(type), intr);*/
			free(ih, M_DEVBUF);
			return (NULL);
		}
		break;
	}

	/*
	 * Figure out where to put the handler.
	 * This is O(N^2) to establish N interrupts, but we want to
	 * preserve the order, and N is generally small.
	 */
	maxlevel = level;
	minlevel = level;
	for (p = &pin->ip_handler; (q = *p) != NULL; p = &q->ih_next) {
		if (q->ih_level > maxlevel)
			maxlevel = q->ih_level;
		if (q->ih_level < minlevel)
			minlevel = 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;
	*p = &fakehand;

	/*
	 * Fix up the vector for this pin.
	 * (if cold, defer this until most interrupts have been established,
	 * to avoid too much thrashing of the idt..)
	 */

	if (!ioapic_cold)
		apic_vectorset(sc, intr, minlevel, maxlevel);

#if 0
	apic_calculatemasks();
#endif

	/*
	 * 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;
	evcount_attach(&ih->ih_count, ih_what, (void *)&pin->ip_vector,
	    &evcount_intr);
	*p = ih;

	return (ih);
}

/*
 * apic disestablish:
 *	locate handler chain.
 * 	dechain intrhand from handler chain
 *	if chain empty {
 *		reprogram apic for "safe" vector.
 *		free vector (point at stray handler).
 *	}
 *	#ifdef notyet
 *	else {
 *		recompute level for current chain.
 *		if changed, reallocate vector, move chain.
 *	}
 *	#endif
 */

void
apic_intr_disestablish(void *arg)
{
	struct intrhand *ih = arg;
	int irq = ih->ih_irq;
	unsigned int ioapic = APIC_IRQ_APIC(irq);
	unsigned int intr = APIC_IRQ_PIN(irq);
	struct ioapic_softc *sc = ioapic_find(ioapic);
	struct ioapic_pin *pin = &sc->sc_pins[intr];
	struct intrhand **p, *q;
	int minlevel, maxlevel;

	if (sc == NULL)
		panic("apic_intr_disestablish: unknown ioapic %d", ioapic);

	if (intr >= sc->sc_apic_sz)
		panic("apic_intr_disestablish: bogus irq");

	/*
	 * Remove the handler from the chain.
	 * This is O(n^2), too.
	 */
	maxlevel = 0;
	minlevel = 0xff;
	for (p = &pin->ip_handler; (q = *p) != NULL && q != ih;
	     p = &q->ih_next) {
		if (q->ih_level > maxlevel)
			maxlevel = q->ih_level;
		if (q->ih_level < minlevel)
			minlevel = q->ih_level;
	}

	if (q)
		*p = q->ih_next;
	else
		panic("intr_disestablish: handler not registered");
	for (; q != NULL; q = q->ih_next) {
		if (q->ih_level > maxlevel)
			maxlevel = q->ih_level;
		if (q->ih_level < minlevel)
			minlevel = q->ih_level;
	}

	if (!ioapic_cold)
		apic_vectorset(sc, intr, minlevel, maxlevel);

	evcount_detach(&ih->ih_count);
	free(ih, M_DEVBUF);
}

void
apic_stray(int irqnum) {
	unsigned int apicid;
	struct ioapic_softc *sc;

	apicid = APIC_IRQ_APIC(irqnum);
	sc = ioapic_find(apicid);
	if (sc == NULL)
		return;
	printf("%s: stray interrupt %d\n", sc->sc_dev.dv_xname, irqnum);
}

#ifdef DDB
void ioapic_dump(void);

void
ioapic_dump(void)
{
	struct ioapic_softc *sc;
	struct ioapic_pin *ip;
	int p;

	for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
		for (p = 0; p < sc->sc_apic_sz; p++) {
			ip = &sc->sc_pins[p];
			if (ip->ip_type != IST_NONE)
				ioapic_print_redir(sc, "dump", p);
		}
	}
}
#endif