2003-11-03 21:53:38 +00:00
|
|
|
/*-
|
|
|
|
* Copyright (c) 2003 John Baldwin <jhb@FreeBSD.org>
|
|
|
|
* 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 author nor the names of any co-contributors
|
|
|
|
* may be used to endorse or promote products derived from this software
|
|
|
|
* without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE 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 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>
|
|
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
|
|
|
|
#include "opt_isa.h"
|
|
|
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/bus.h>
|
|
|
|
#include <sys/kernel.h>
|
|
|
|
#include <sys/lock.h>
|
2007-03-05 16:22:49 +00:00
|
|
|
#include <sys/malloc.h>
|
|
|
|
#include <sys/module.h>
|
2003-11-03 21:53:38 +00:00
|
|
|
#include <sys/mutex.h>
|
2005-07-29 18:58:33 +00:00
|
|
|
#include <sys/sysctl.h>
|
2003-11-03 21:53:38 +00:00
|
|
|
|
2007-03-05 16:22:49 +00:00
|
|
|
#include <dev/pci/pcireg.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
#include <vm/vm.h>
|
|
|
|
#include <vm/pmap.h>
|
|
|
|
|
|
|
|
#include <machine/apicreg.h>
|
|
|
|
#include <machine/frame.h>
|
|
|
|
#include <machine/intr_machdep.h>
|
|
|
|
#include <machine/apicvar.h>
|
2007-03-20 21:53:31 +00:00
|
|
|
#include <machine/resource.h>
|
2003-11-03 21:53:38 +00:00
|
|
|
#include <machine/segments.h>
|
|
|
|
|
|
|
|
#define IOAPIC_ISA_INTS 16
|
|
|
|
#define IOAPIC_MEM_REGION 32
|
|
|
|
#define IOAPIC_REDTBL_LO(i) (IOAPIC_REDTBL + (i) * 2)
|
|
|
|
#define IOAPIC_REDTBL_HI(i) (IOAPIC_REDTBL_LO(i) + 1)
|
|
|
|
|
2005-11-02 20:11:47 +00:00
|
|
|
#define IRQ_EXTINT (NUM_IO_INTS + 1)
|
|
|
|
#define IRQ_NMI (NUM_IO_INTS + 2)
|
|
|
|
#define IRQ_SMI (NUM_IO_INTS + 3)
|
|
|
|
#define IRQ_DISABLED (NUM_IO_INTS + 4)
|
2003-11-03 21:53:38 +00:00
|
|
|
|
2005-10-31 15:41:29 +00:00
|
|
|
static MALLOC_DEFINE(M_IOAPIC, "io_apic", "I/O APIC structures");
|
2003-11-03 21:53:38 +00:00
|
|
|
|
|
|
|
/*
|
2005-11-02 20:11:47 +00:00
|
|
|
* I/O APIC interrupt source driver. Each pin is assigned an IRQ cookie
|
|
|
|
* as laid out in the ACPI System Interrupt number model where each I/O
|
|
|
|
* APIC has a contiguous chunk of the System Interrupt address space.
|
|
|
|
* We assume that IRQs 1 - 15 behave like ISA IRQs and that all other
|
|
|
|
* IRQs behave as PCI IRQs by default. We also assume that the pin for
|
|
|
|
* IRQ 0 is actually an ExtINT pin. The apic enumerators override the
|
|
|
|
* configuration of individual pins as indicated by their tables.
|
2005-11-15 20:18:13 +00:00
|
|
|
*
|
|
|
|
* Documentation for the I/O APIC: "82093AA I/O Advanced Programmable
|
|
|
|
* Interrupt Controller (IOAPIC)", May 1996, Intel Corp.
|
|
|
|
* ftp://download.intel.com/design/chipsets/datashts/29056601.pdf
|
2003-11-03 21:53:38 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
struct ioapic_intsrc {
|
|
|
|
struct intsrc io_intsrc;
|
2005-11-02 20:11:47 +00:00
|
|
|
u_int io_irq;
|
2003-11-05 16:18:06 +00:00
|
|
|
u_int io_intpin:8;
|
|
|
|
u_int io_vector:8;
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
u_int io_cpu:8;
|
2003-11-05 16:18:06 +00:00
|
|
|
u_int io_activehi:1;
|
|
|
|
u_int io_edgetrigger:1;
|
|
|
|
u_int io_masked:1;
|
2004-06-23 15:29:20 +00:00
|
|
|
int io_bus:4;
|
2006-04-05 20:43:19 +00:00
|
|
|
uint32_t io_lowreg;
|
2003-11-03 21:53:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ioapic {
|
|
|
|
struct pic io_pic;
|
|
|
|
u_int io_id:8; /* logical ID */
|
|
|
|
u_int io_apic_id:4;
|
|
|
|
u_int io_intbase:8; /* System Interrupt base */
|
|
|
|
u_int io_numintr:8;
|
|
|
|
volatile ioapic_t *io_addr; /* XXX: should use bus_space */
|
2007-03-20 21:53:31 +00:00
|
|
|
vm_paddr_t io_paddr;
|
2003-11-03 21:53:38 +00:00
|
|
|
STAILQ_ENTRY(ioapic) io_next;
|
|
|
|
struct ioapic_intsrc io_pins[0];
|
|
|
|
};
|
|
|
|
|
|
|
|
static u_int ioapic_read(volatile ioapic_t *apic, int reg);
|
|
|
|
static void ioapic_write(volatile ioapic_t *apic, int reg, u_int val);
|
2004-06-23 15:29:20 +00:00
|
|
|
static const char *ioapic_bus_string(int bus_type);
|
2005-11-02 20:11:47 +00:00
|
|
|
static void ioapic_print_irq(struct ioapic_intsrc *intpin);
|
2003-11-03 21:53:38 +00:00
|
|
|
static void ioapic_enable_source(struct intsrc *isrc);
|
2004-08-02 15:31:10 +00:00
|
|
|
static void ioapic_disable_source(struct intsrc *isrc, int eoi);
|
2003-11-03 21:53:38 +00:00
|
|
|
static void ioapic_eoi_source(struct intsrc *isrc);
|
|
|
|
static void ioapic_enable_intr(struct intsrc *isrc);
|
2007-05-08 21:29:14 +00:00
|
|
|
static void ioapic_disable_intr(struct intsrc *isrc);
|
2003-11-03 21:53:38 +00:00
|
|
|
static int ioapic_vector(struct intsrc *isrc);
|
|
|
|
static int ioapic_source_pending(struct intsrc *isrc);
|
2004-05-04 21:02:56 +00:00
|
|
|
static int ioapic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
|
|
|
|
enum intr_polarity pol);
|
2006-10-10 23:23:12 +00:00
|
|
|
static void ioapic_resume(struct pic *pic);
|
2009-07-01 17:20:07 +00:00
|
|
|
static int ioapic_assign_cpu(struct intsrc *isrc, u_int apic_id);
|
2004-06-01 20:28:42 +00:00
|
|
|
static void ioapic_program_intpin(struct ioapic_intsrc *intpin);
|
2003-11-03 21:53:38 +00:00
|
|
|
|
2004-05-10 18:49:58 +00:00
|
|
|
static STAILQ_HEAD(,ioapic) ioapic_list = STAILQ_HEAD_INITIALIZER(ioapic_list);
|
2003-11-03 21:53:38 +00:00
|
|
|
struct pic ioapic_template = { ioapic_enable_source, ioapic_disable_source,
|
|
|
|
ioapic_eoi_source, ioapic_enable_intr,
|
2007-05-08 21:29:14 +00:00
|
|
|
ioapic_disable_intr, ioapic_vector,
|
|
|
|
ioapic_source_pending, NULL, ioapic_resume,
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
ioapic_config_intr, ioapic_assign_cpu };
|
|
|
|
|
|
|
|
static int next_ioapic_base;
|
|
|
|
static u_int next_id;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
2005-07-29 18:58:33 +00:00
|
|
|
SYSCTL_NODE(_hw, OID_AUTO, apic, CTLFLAG_RD, 0, "APIC options");
|
|
|
|
static int enable_extint;
|
|
|
|
SYSCTL_INT(_hw_apic, OID_AUTO, enable_extint, CTLFLAG_RDTUN, &enable_extint, 0,
|
|
|
|
"Enable the ExtINT pin in the first I/O APIC");
|
|
|
|
TUNABLE_INT("hw.apic.enable_extint", &enable_extint);
|
|
|
|
|
2004-08-02 15:31:10 +00:00
|
|
|
static __inline void
|
|
|
|
_ioapic_eoi_source(struct intsrc *isrc)
|
|
|
|
{
|
|
|
|
lapic_eoi();
|
|
|
|
}
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
static u_int
|
|
|
|
ioapic_read(volatile ioapic_t *apic, int reg)
|
|
|
|
{
|
|
|
|
|
|
|
|
mtx_assert(&icu_lock, MA_OWNED);
|
|
|
|
apic->ioregsel = reg;
|
|
|
|
return (apic->iowin);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ioapic_write(volatile ioapic_t *apic, int reg, u_int val)
|
|
|
|
{
|
|
|
|
|
|
|
|
mtx_assert(&icu_lock, MA_OWNED);
|
|
|
|
apic->ioregsel = reg;
|
|
|
|
apic->iowin = val;
|
|
|
|
}
|
|
|
|
|
2004-06-23 15:29:20 +00:00
|
|
|
static const char *
|
|
|
|
ioapic_bus_string(int bus_type)
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (bus_type) {
|
|
|
|
case APIC_BUS_ISA:
|
|
|
|
return ("ISA");
|
|
|
|
case APIC_BUS_EISA:
|
|
|
|
return ("EISA");
|
|
|
|
case APIC_BUS_PCI:
|
|
|
|
return ("PCI");
|
|
|
|
default:
|
|
|
|
return ("unknown");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2005-11-02 20:11:47 +00:00
|
|
|
ioapic_print_irq(struct ioapic_intsrc *intpin)
|
2004-06-23 15:29:20 +00:00
|
|
|
{
|
|
|
|
|
2005-11-02 20:11:47 +00:00
|
|
|
switch (intpin->io_irq) {
|
|
|
|
case IRQ_DISABLED:
|
2004-06-23 15:29:20 +00:00
|
|
|
printf("disabled");
|
|
|
|
break;
|
2005-11-02 20:11:47 +00:00
|
|
|
case IRQ_EXTINT:
|
2004-06-23 15:29:20 +00:00
|
|
|
printf("ExtINT");
|
|
|
|
break;
|
2005-11-02 20:11:47 +00:00
|
|
|
case IRQ_NMI:
|
2004-06-23 15:29:20 +00:00
|
|
|
printf("NMI");
|
|
|
|
break;
|
2005-11-02 20:11:47 +00:00
|
|
|
case IRQ_SMI:
|
2004-06-23 15:29:20 +00:00
|
|
|
printf("SMI");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printf("%s IRQ %u", ioapic_bus_string(intpin->io_bus),
|
2005-11-02 20:11:47 +00:00
|
|
|
intpin->io_irq);
|
2004-06-23 15:29:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
static void
|
|
|
|
ioapic_enable_source(struct intsrc *isrc)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
struct ioapic *io = (struct ioapic *)isrc->is_pic;
|
|
|
|
uint32_t flags;
|
|
|
|
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
|
|
if (intpin->io_masked) {
|
2006-04-05 20:43:19 +00:00
|
|
|
flags = intpin->io_lowreg & ~IOART_INTMASK;
|
2003-11-03 21:53:38 +00:00
|
|
|
ioapic_write(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin),
|
|
|
|
flags);
|
|
|
|
intpin->io_masked = 0;
|
|
|
|
}
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2004-08-02 15:31:10 +00:00
|
|
|
ioapic_disable_source(struct intsrc *isrc, int eoi)
|
2003-11-03 21:53:38 +00:00
|
|
|
{
|
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
struct ioapic *io = (struct ioapic *)isrc->is_pic;
|
|
|
|
uint32_t flags;
|
|
|
|
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
|
|
if (!intpin->io_masked && !intpin->io_edgetrigger) {
|
2006-04-05 20:43:19 +00:00
|
|
|
flags = intpin->io_lowreg | IOART_INTMSET;
|
2003-11-03 21:53:38 +00:00
|
|
|
ioapic_write(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin),
|
|
|
|
flags);
|
|
|
|
intpin->io_masked = 1;
|
|
|
|
}
|
2004-08-02 15:31:10 +00:00
|
|
|
|
|
|
|
if (eoi == PIC_EOI)
|
|
|
|
_ioapic_eoi_source(isrc);
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ioapic_eoi_source(struct intsrc *isrc)
|
|
|
|
{
|
2003-11-05 23:07:39 +00:00
|
|
|
|
2004-08-02 15:31:10 +00:00
|
|
|
_ioapic_eoi_source(isrc);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
|
|
|
|
2004-06-01 20:28:42 +00:00
|
|
|
/*
|
|
|
|
* Completely program an intpin based on the data in its interrupt source
|
|
|
|
* structure.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ioapic_program_intpin(struct ioapic_intsrc *intpin)
|
|
|
|
{
|
|
|
|
struct ioapic *io = (struct ioapic *)intpin->io_intsrc.is_pic;
|
|
|
|
uint32_t low, high, value;
|
|
|
|
|
2005-11-02 20:11:47 +00:00
|
|
|
/*
|
|
|
|
* If a pin is completely invalid or if it is valid but hasn't
|
|
|
|
* been enabled yet, just ensure that the pin is masked.
|
|
|
|
*/
|
|
|
|
if (intpin->io_irq == IRQ_DISABLED || (intpin->io_irq < NUM_IO_INTS &&
|
|
|
|
intpin->io_vector == 0)) {
|
|
|
|
mtx_lock_spin(&icu_lock);
|
2004-06-01 20:28:42 +00:00
|
|
|
low = ioapic_read(io->io_addr,
|
|
|
|
IOAPIC_REDTBL_LO(intpin->io_intpin));
|
|
|
|
if ((low & IOART_INTMASK) == IOART_INTMCLR)
|
|
|
|
ioapic_write(io->io_addr,
|
|
|
|
IOAPIC_REDTBL_LO(intpin->io_intpin),
|
|
|
|
low | IOART_INTMSET);
|
2005-11-02 20:11:47 +00:00
|
|
|
mtx_unlock_spin(&icu_lock);
|
2004-06-01 20:28:42 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the destination. */
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
low = IOART_DESTPHY;
|
|
|
|
high = intpin->io_cpu << APIC_ID_SHIFT;
|
2004-06-01 20:28:42 +00:00
|
|
|
|
|
|
|
/* Program the rest of the low word. */
|
|
|
|
if (intpin->io_edgetrigger)
|
|
|
|
low |= IOART_TRGREDG;
|
|
|
|
else
|
|
|
|
low |= IOART_TRGRLVL;
|
|
|
|
if (intpin->io_activehi)
|
|
|
|
low |= IOART_INTAHI;
|
|
|
|
else
|
|
|
|
low |= IOART_INTALO;
|
|
|
|
if (intpin->io_masked)
|
|
|
|
low |= IOART_INTMSET;
|
2005-11-02 20:11:47 +00:00
|
|
|
switch (intpin->io_irq) {
|
|
|
|
case IRQ_EXTINT:
|
2004-06-01 20:28:42 +00:00
|
|
|
KASSERT(intpin->io_edgetrigger,
|
2005-07-29 18:58:33 +00:00
|
|
|
("ExtINT not edge triggered"));
|
2004-06-01 20:28:42 +00:00
|
|
|
low |= IOART_DELEXINT;
|
|
|
|
break;
|
2005-11-02 20:11:47 +00:00
|
|
|
case IRQ_NMI:
|
2004-06-01 20:28:42 +00:00
|
|
|
KASSERT(intpin->io_edgetrigger,
|
|
|
|
("NMI not edge triggered"));
|
|
|
|
low |= IOART_DELNMI;
|
|
|
|
break;
|
2005-11-02 20:11:47 +00:00
|
|
|
case IRQ_SMI:
|
2004-06-01 20:28:42 +00:00
|
|
|
KASSERT(intpin->io_edgetrigger,
|
|
|
|
("SMI not edge triggered"));
|
|
|
|
low |= IOART_DELSMI;
|
|
|
|
break;
|
|
|
|
default:
|
2005-11-02 20:11:47 +00:00
|
|
|
KASSERT(intpin->io_vector != 0, ("No vector for IRQ %u",
|
|
|
|
intpin->io_irq));
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
low |= IOART_DELFIXED | intpin->io_vector;
|
2004-06-01 20:28:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Write the values to the APIC. */
|
|
|
|
mtx_lock_spin(&icu_lock);
|
2006-04-05 20:43:19 +00:00
|
|
|
intpin->io_lowreg = low;
|
2004-06-01 20:28:42 +00:00
|
|
|
ioapic_write(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin), low);
|
|
|
|
value = ioapic_read(io->io_addr, IOAPIC_REDTBL_HI(intpin->io_intpin));
|
|
|
|
value &= ~IOART_DEST;
|
|
|
|
value |= high;
|
|
|
|
ioapic_write(io->io_addr, IOAPIC_REDTBL_HI(intpin->io_intpin), value);
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
|
|
}
|
|
|
|
|
2009-07-01 17:20:07 +00:00
|
|
|
static int
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
ioapic_assign_cpu(struct intsrc *isrc, u_int apic_id)
|
2003-11-03 21:53:38 +00:00
|
|
|
{
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
struct ioapic *io = (struct ioapic *)isrc->is_pic;
|
2009-07-06 18:23:00 +00:00
|
|
|
u_int old_vector, new_vector;
|
2009-01-29 09:22:56 +00:00
|
|
|
u_int old_id;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
2009-01-29 09:22:56 +00:00
|
|
|
/*
|
|
|
|
* keep 1st core as the destination for NMI
|
|
|
|
*/
|
|
|
|
if (intpin->io_irq == IRQ_NMI)
|
|
|
|
apic_id = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set us up to free the old irq.
|
|
|
|
*/
|
|
|
|
old_vector = intpin->io_vector;
|
|
|
|
old_id = intpin->io_cpu;
|
|
|
|
if (old_vector && apic_id == old_id)
|
2009-07-01 17:20:07 +00:00
|
|
|
return (0);
|
2009-01-29 09:22:56 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate an APIC vector for this interrupt pin. Once
|
|
|
|
* we have a vector we program the interrupt pin.
|
|
|
|
*/
|
2009-07-06 18:23:00 +00:00
|
|
|
new_vector = apic_alloc_vector(apic_id, intpin->io_irq);
|
|
|
|
if (new_vector == 0)
|
2009-07-01 17:20:07 +00:00
|
|
|
return (ENOSPC);
|
|
|
|
|
2009-07-06 18:23:00 +00:00
|
|
|
intpin->io_cpu = apic_id;
|
|
|
|
intpin->io_vector = new_vector;
|
|
|
|
if (isrc->is_handlers > 0)
|
|
|
|
apic_enable_vector(intpin->io_cpu, intpin->io_vector);
|
2003-11-03 21:53:38 +00:00
|
|
|
if (bootverbose) {
|
2009-01-29 09:22:56 +00:00
|
|
|
printf("ioapic%u: routing intpin %u (", io->io_id,
|
|
|
|
intpin->io_intpin);
|
2005-11-02 20:11:47 +00:00
|
|
|
ioapic_print_irq(intpin);
|
2009-01-29 09:22:56 +00:00
|
|
|
printf(") to lapic %u vector %u\n", intpin->io_cpu,
|
|
|
|
intpin->io_vector);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
2004-06-01 20:28:42 +00:00
|
|
|
ioapic_program_intpin(intpin);
|
2009-01-29 09:22:56 +00:00
|
|
|
/*
|
|
|
|
* Free the old vector after the new one is established. This is done
|
|
|
|
* to prevent races where we could miss an interrupt.
|
|
|
|
*/
|
2009-07-06 18:23:00 +00:00
|
|
|
if (old_vector) {
|
|
|
|
if (isrc->is_handlers > 0)
|
|
|
|
apic_disable_vector(old_id, old_vector);
|
2009-01-29 09:22:56 +00:00
|
|
|
apic_free_vector(old_id, old_vector, intpin->io_irq);
|
2009-07-06 18:23:00 +00:00
|
|
|
}
|
2009-07-01 17:20:07 +00:00
|
|
|
return (0);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ioapic_enable_intr(struct intsrc *isrc)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
|
2009-01-29 09:22:56 +00:00
|
|
|
if (intpin->io_vector == 0)
|
2009-07-01 17:20:07 +00:00
|
|
|
if (ioapic_assign_cpu(isrc, intr_next_cpu()) != 0)
|
|
|
|
panic("Couldn't find an APIC vector for IRQ %d",
|
|
|
|
intpin->io_irq);
|
2009-01-29 09:22:56 +00:00
|
|
|
apic_enable_vector(intpin->io_cpu, intpin->io_vector);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
|
|
|
|
2009-01-29 09:22:56 +00:00
|
|
|
|
2007-05-08 21:29:14 +00:00
|
|
|
static void
|
|
|
|
ioapic_disable_intr(struct intsrc *isrc)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
u_int vector;
|
|
|
|
|
|
|
|
if (intpin->io_vector != 0) {
|
|
|
|
/* Mask this interrupt pin and free its APIC vector. */
|
|
|
|
vector = intpin->io_vector;
|
2009-01-29 09:22:56 +00:00
|
|
|
apic_disable_vector(intpin->io_cpu, vector);
|
2007-05-08 21:29:14 +00:00
|
|
|
intpin->io_masked = 1;
|
|
|
|
intpin->io_vector = 0;
|
|
|
|
ioapic_program_intpin(intpin);
|
2009-01-29 09:22:56 +00:00
|
|
|
apic_free_vector(intpin->io_cpu, vector, intpin->io_irq);
|
2007-05-08 21:29:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
static int
|
|
|
|
ioapic_vector(struct intsrc *isrc)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *pin;
|
|
|
|
|
|
|
|
pin = (struct ioapic_intsrc *)isrc;
|
2005-11-02 20:11:47 +00:00
|
|
|
return (pin->io_irq);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ioapic_source_pending(struct intsrc *isrc)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
|
2005-11-02 20:11:47 +00:00
|
|
|
if (intpin->io_vector == 0)
|
|
|
|
return 0;
|
2003-11-03 21:53:38 +00:00
|
|
|
return (lapic_intr_pending(intpin->io_vector));
|
|
|
|
}
|
|
|
|
|
2004-05-04 21:02:56 +00:00
|
|
|
static int
|
|
|
|
ioapic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
|
|
|
|
enum intr_polarity pol)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc;
|
|
|
|
struct ioapic *io = (struct ioapic *)isrc->is_pic;
|
2004-06-23 18:11:33 +00:00
|
|
|
int changed;
|
2004-05-04 21:02:56 +00:00
|
|
|
|
|
|
|
KASSERT(!(trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM),
|
|
|
|
("%s: Conforming trigger or polarity\n", __func__));
|
|
|
|
|
|
|
|
/*
|
2004-06-23 18:11:33 +00:00
|
|
|
* EISA interrupts always use active high polarity, so don't allow
|
|
|
|
* them to be set to active low.
|
|
|
|
*
|
|
|
|
* XXX: Should we write to the ELCR if the trigger mode changes for
|
2005-01-18 20:27:24 +00:00
|
|
|
* an EISA IRQ or an ISA IRQ with the ELCR present?
|
2004-05-04 21:02:56 +00:00
|
|
|
*/
|
2004-06-23 18:11:33 +00:00
|
|
|
if (intpin->io_bus == APIC_BUS_EISA)
|
|
|
|
pol = INTR_POLARITY_HIGH;
|
|
|
|
changed = 0;
|
|
|
|
if (intpin->io_edgetrigger != (trig == INTR_TRIGGER_EDGE)) {
|
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: Changing trigger for pin %u to %s\n",
|
|
|
|
io->io_id, intpin->io_intpin,
|
|
|
|
trig == INTR_TRIGGER_EDGE ? "edge" : "level");
|
|
|
|
intpin->io_edgetrigger = (trig == INTR_TRIGGER_EDGE);
|
|
|
|
changed++;
|
|
|
|
}
|
|
|
|
if (intpin->io_activehi != (pol == INTR_POLARITY_HIGH)) {
|
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: Changing polarity for pin %u to %s\n",
|
|
|
|
io->io_id, intpin->io_intpin,
|
|
|
|
pol == INTR_POLARITY_HIGH ? "high" : "low");
|
|
|
|
intpin->io_activehi = (pol == INTR_POLARITY_HIGH);
|
|
|
|
changed++;
|
|
|
|
}
|
|
|
|
if (changed)
|
|
|
|
ioapic_program_intpin(intpin);
|
2004-05-04 21:02:56 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
static void
|
2006-10-10 23:23:12 +00:00
|
|
|
ioapic_resume(struct pic *pic)
|
2003-11-03 21:53:38 +00:00
|
|
|
{
|
2006-10-10 23:23:12 +00:00
|
|
|
struct ioapic *io = (struct ioapic *)pic;
|
|
|
|
int i;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
2006-10-10 23:23:12 +00:00
|
|
|
for (i = 0; i < io->io_numintr; i++)
|
|
|
|
ioapic_program_intpin(&io->io_pins[i]);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a plain I/O APIC object.
|
|
|
|
*/
|
|
|
|
void *
|
2007-03-05 20:35:17 +00:00
|
|
|
ioapic_create(vm_paddr_t addr, int32_t apic_id, int intbase)
|
2003-11-03 21:53:38 +00:00
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
struct ioapic_intsrc *intpin;
|
|
|
|
volatile ioapic_t *apic;
|
|
|
|
u_int numintr, i;
|
|
|
|
uint32_t value;
|
|
|
|
|
2005-04-14 05:55:34 +00:00
|
|
|
/* Map the register window so we can access the device. */
|
2006-03-20 19:39:08 +00:00
|
|
|
apic = pmap_mapdev(addr, IOAPIC_MEM_REGION);
|
2003-11-03 21:53:38 +00:00
|
|
|
mtx_lock_spin(&icu_lock);
|
2005-04-14 05:55:34 +00:00
|
|
|
value = ioapic_read(apic, IOAPIC_VER);
|
2003-11-03 21:53:38 +00:00
|
|
|
mtx_unlock_spin(&icu_lock);
|
2005-04-14 05:55:34 +00:00
|
|
|
|
|
|
|
/* If it's version register doesn't seem to work, punt. */
|
2005-11-16 20:29:29 +00:00
|
|
|
if (value == 0xffffffff) {
|
2005-04-14 06:33:23 +00:00
|
|
|
pmap_unmapdev((vm_offset_t)apic, IOAPIC_MEM_REGION);
|
2005-04-14 05:55:34 +00:00
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine the number of vectors and set the APIC ID. */
|
|
|
|
numintr = ((value & IOART_VER_MAXREDIR) >> MAXREDIRSHIFT) + 1;
|
2003-11-03 21:53:38 +00:00
|
|
|
io = malloc(sizeof(struct ioapic) +
|
|
|
|
numintr * sizeof(struct ioapic_intsrc), M_IOAPIC, M_WAITOK);
|
|
|
|
io->io_pic = ioapic_template;
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
|
|
io->io_id = next_id++;
|
2009-07-01 17:20:07 +00:00
|
|
|
io->io_apic_id = ioapic_read(apic, IOAPIC_ID) >> APIC_ID_SHIFT;
|
2003-11-03 21:53:38 +00:00
|
|
|
if (apic_id != -1 && io->io_apic_id != apic_id) {
|
|
|
|
ioapic_write(apic, IOAPIC_ID, apic_id << APIC_ID_SHIFT);
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
|
|
io->io_apic_id = apic_id;
|
|
|
|
printf("ioapic%u: Changing APIC ID to %d\n", io->io_id,
|
|
|
|
apic_id);
|
|
|
|
} else
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
|
|
if (intbase == -1) {
|
|
|
|
intbase = next_ioapic_base;
|
|
|
|
printf("ioapic%u: Assuming intbase of %d\n", io->io_id,
|
|
|
|
intbase);
|
2007-06-05 18:57:48 +00:00
|
|
|
} else if (intbase != next_ioapic_base && bootverbose)
|
2003-11-03 21:53:38 +00:00
|
|
|
printf("ioapic%u: WARNING: intbase %d != expected base %d\n",
|
|
|
|
io->io_id, intbase, next_ioapic_base);
|
|
|
|
io->io_intbase = intbase;
|
2003-11-05 16:18:06 +00:00
|
|
|
next_ioapic_base = intbase + numintr;
|
2003-11-03 21:53:38 +00:00
|
|
|
io->io_numintr = numintr;
|
|
|
|
io->io_addr = apic;
|
2007-03-20 21:53:31 +00:00
|
|
|
io->io_paddr = addr;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize pins. Start off with interrupts disabled. Default
|
|
|
|
* to active-hi and edge-triggered for ISA interrupts and active-lo
|
|
|
|
* and level-triggered for all others.
|
|
|
|
*/
|
|
|
|
bzero(io->io_pins, sizeof(struct ioapic_intsrc) * numintr);
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
|
|
for (i = 0, intpin = io->io_pins; i < numintr; i++, intpin++) {
|
|
|
|
intpin->io_intsrc.is_pic = (struct pic *)io;
|
|
|
|
intpin->io_intpin = i;
|
2005-11-02 20:11:47 +00:00
|
|
|
intpin->io_irq = intbase + i;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
|
|
|
/*
|
2005-04-14 17:59:58 +00:00
|
|
|
* Assume that pin 0 on the first I/O APIC is an ExtINT pin.
|
2005-02-22 21:50:41 +00:00
|
|
|
* Assume that pins 1-15 are ISA interrupts and that all
|
2004-06-23 15:29:20 +00:00
|
|
|
* other pins are PCI interrupts.
|
2003-11-03 21:53:38 +00:00
|
|
|
*/
|
2005-11-02 20:11:47 +00:00
|
|
|
if (intpin->io_irq == 0)
|
2004-06-23 15:29:20 +00:00
|
|
|
ioapic_set_extint(io, i);
|
2005-11-02 20:11:47 +00:00
|
|
|
else if (intpin->io_irq < IOAPIC_ISA_INTS) {
|
2004-06-23 15:29:20 +00:00
|
|
|
intpin->io_bus = APIC_BUS_ISA;
|
2003-11-03 21:53:38 +00:00
|
|
|
intpin->io_activehi = 1;
|
|
|
|
intpin->io_edgetrigger = 1;
|
|
|
|
intpin->io_masked = 1;
|
|
|
|
} else {
|
2004-06-23 15:29:20 +00:00
|
|
|
intpin->io_bus = APIC_BUS_PCI;
|
2003-11-03 21:53:38 +00:00
|
|
|
intpin->io_activehi = 0;
|
|
|
|
intpin->io_edgetrigger = 0;
|
|
|
|
intpin->io_masked = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
* Route interrupts to the BSP by default. Interrupts may
|
|
|
|
* be routed to other CPUs later after they are enabled.
|
2003-11-03 21:53:38 +00:00
|
|
|
*/
|
Rework how we wire up interrupt sources to CPUs:
- Throw out all of the logical APIC ID stuff. The Intel docs are somewhat
ambiguous, but it seems that the "flat" cluster model we are currently
using is only supported on Pentium and P6 family CPUs. The other
"hierarchy" cluster model that is supported on all Intel CPUs with
local APICs is severely underdocumented. For example, it's not clear
if the OS needs to glean the topology of the APIC hierarchy from
somewhere (neither ACPI nor MP Table include it) and setup the logical
clusters based on the physical hierarchy or not. Not only that, but on
certain Intel chipsets, even though there were 4 CPUs in a logical
cluster, all the interrupts were only sent to one CPU anyway.
- We now bind interrupts to individual CPUs using physical addressing via
the local APIC IDs. This code has also moved out of the ioapic PIC
driver and into the common interrupt source code so that it can be
shared with MSI interrupt sources since MSI is addressed to APICs the
same way that I/O APIC pins are.
- Interrupt source classes grow a new method pic_assign_cpu() to bind an
interrupt source to a specific local APIC ID.
- The SMP code now tells the interrupt code which CPUs are avaiable to
handle interrupts in a simpler and more intuitive manner. For one thing,
it means we could now choose to not route interrupts to HT cores if we
wanted to (this code is currently in place in fact, but under an #if 0
for now).
- For now we simply do static round-robin of IRQs to CPUs when the first
interrupt handler just as before, with the change that IRQs are now
bound to individual CPUs rather than groups of up to 4 CPUs.
- Because the IRQ to CPU mapping has now been moved up a layer, it would
be easier to manage this mapping from higher levels. For example, we
could allow drivers to specify a CPU affinity map for their interrupts,
or we could allow a userland tool to bind IRQs to specific CPUs.
The MFC is tentative, but I want to see if this fixes problems some folks
had with UP APIC kernels on 6.0 on SMP machines (an SMP kernel would work
fine, but a UP APIC kernel (such as GENERIC in RELENG_6) would lose
interrupts).
MFC after: 1 week
2006-02-28 22:24:55 +00:00
|
|
|
intpin->io_cpu = PCPU_GET(apic_id);
|
2003-11-03 21:53:38 +00:00
|
|
|
value = ioapic_read(apic, IOAPIC_REDTBL_LO(i));
|
|
|
|
ioapic_write(apic, IOAPIC_REDTBL_LO(i), value | IOART_INTMSET);
|
|
|
|
}
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
|
|
|
|
|
|
return (io);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ioapic_get_vector(void *cookie, u_int pin)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr)
|
|
|
|
return (-1);
|
2005-11-02 20:11:47 +00:00
|
|
|
return (io->io_pins[pin].io_irq);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ioapic_disable_pin(void *cookie, u_int pin)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr)
|
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq == IRQ_DISABLED)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
io->io_pins[pin].io_irq = IRQ_DISABLED;
|
2003-11-03 21:53:38 +00:00
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: intpin %d disabled\n", io->io_id, pin);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ioapic_remap_vector(void *cookie, u_int pin, int vector)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr || vector < 0)
|
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
io->io_pins[pin].io_irq = vector;
|
2003-11-03 21:53:38 +00:00
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: Routing IRQ %d -> intpin %d\n", io->io_id,
|
|
|
|
vector, pin);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2004-06-23 15:29:20 +00:00
|
|
|
int
|
|
|
|
ioapic_set_bus(void *cookie, u_int pin, int bus_type)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
if (bus_type < 0 || bus_type > APIC_BUS_MAX)
|
|
|
|
return (EINVAL);
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr)
|
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2004-06-23 15:29:20 +00:00
|
|
|
return (EINVAL);
|
2006-11-17 16:41:03 +00:00
|
|
|
if (io->io_pins[pin].io_bus == bus_type)
|
|
|
|
return (0);
|
2004-06-23 15:29:20 +00:00
|
|
|
io->io_pins[pin].io_bus = bus_type;
|
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: intpin %d bus %s\n", io->io_id, pin,
|
|
|
|
ioapic_bus_string(bus_type));
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2003-11-03 21:53:38 +00:00
|
|
|
int
|
|
|
|
ioapic_set_nmi(void *cookie, u_int pin)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr)
|
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq == IRQ_NMI)
|
2004-06-23 15:29:20 +00:00
|
|
|
return (0);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2004-06-23 15:29:20 +00:00
|
|
|
io->io_pins[pin].io_bus = APIC_BUS_UNKNOWN;
|
2005-11-02 20:11:47 +00:00
|
|
|
io->io_pins[pin].io_irq = IRQ_NMI;
|
2003-11-03 21:53:38 +00:00
|
|
|
io->io_pins[pin].io_masked = 0;
|
|
|
|
io->io_pins[pin].io_edgetrigger = 1;
|
|
|
|
io->io_pins[pin].io_activehi = 1;
|
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: Routing NMI -> intpin %d\n",
|
|
|
|
io->io_id, pin);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ioapic_set_smi(void *cookie, u_int pin)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr)
|
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq == IRQ_SMI)
|
2004-06-23 15:29:20 +00:00
|
|
|
return (0);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2004-06-23 15:29:20 +00:00
|
|
|
io->io_pins[pin].io_bus = APIC_BUS_UNKNOWN;
|
2005-11-02 20:11:47 +00:00
|
|
|
io->io_pins[pin].io_irq = IRQ_SMI;
|
2003-11-03 21:53:38 +00:00
|
|
|
io->io_pins[pin].io_masked = 0;
|
|
|
|
io->io_pins[pin].io_edgetrigger = 1;
|
|
|
|
io->io_pins[pin].io_activehi = 1;
|
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: Routing SMI -> intpin %d\n",
|
|
|
|
io->io_id, pin);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ioapic_set_extint(void *cookie, u_int pin)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
if (pin >= io->io_numintr)
|
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq == IRQ_EXTINT)
|
2004-06-23 15:29:20 +00:00
|
|
|
return (0);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2004-06-23 15:29:20 +00:00
|
|
|
io->io_pins[pin].io_bus = APIC_BUS_UNKNOWN;
|
2005-11-02 20:11:47 +00:00
|
|
|
io->io_pins[pin].io_irq = IRQ_EXTINT;
|
2005-07-29 18:58:33 +00:00
|
|
|
if (enable_extint)
|
|
|
|
io->io_pins[pin].io_masked = 0;
|
|
|
|
else
|
|
|
|
io->io_pins[pin].io_masked = 1;
|
2003-11-03 21:53:38 +00:00
|
|
|
io->io_pins[pin].io_edgetrigger = 1;
|
|
|
|
io->io_pins[pin].io_activehi = 1;
|
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: Routing external 8259A's -> intpin %d\n",
|
|
|
|
io->io_id, pin);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2004-05-04 20:39:24 +00:00
|
|
|
ioapic_set_polarity(void *cookie, u_int pin, enum intr_polarity pol)
|
2003-11-03 21:53:38 +00:00
|
|
|
{
|
|
|
|
struct ioapic *io;
|
2006-11-17 16:41:03 +00:00
|
|
|
int activehi;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
2004-05-04 20:39:24 +00:00
|
|
|
if (pin >= io->io_numintr || pol == INTR_POLARITY_CONFORM)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2006-11-17 16:41:03 +00:00
|
|
|
activehi = (pol == INTR_POLARITY_HIGH);
|
|
|
|
if (io->io_pins[pin].io_activehi == activehi)
|
|
|
|
return (0);
|
|
|
|
io->io_pins[pin].io_activehi = activehi;
|
2003-11-03 21:53:38 +00:00
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: intpin %d polarity: %s\n", io->io_id, pin,
|
2004-05-04 20:39:24 +00:00
|
|
|
pol == INTR_POLARITY_HIGH ? "high" : "low");
|
2003-11-03 21:53:38 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2004-05-04 20:39:24 +00:00
|
|
|
ioapic_set_triggermode(void *cookie, u_int pin, enum intr_trigger trigger)
|
2003-11-03 21:53:38 +00:00
|
|
|
{
|
|
|
|
struct ioapic *io;
|
2006-11-17 16:41:03 +00:00
|
|
|
int edgetrigger;
|
2003-11-03 21:53:38 +00:00
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
2004-05-04 20:39:24 +00:00
|
|
|
if (pin >= io->io_numintr || trigger == INTR_TRIGGER_CONFORM)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2005-11-02 20:11:47 +00:00
|
|
|
if (io->io_pins[pin].io_irq >= NUM_IO_INTS)
|
2003-11-03 21:53:38 +00:00
|
|
|
return (EINVAL);
|
2006-11-17 16:41:03 +00:00
|
|
|
edgetrigger = (trigger == INTR_TRIGGER_EDGE);
|
|
|
|
if (io->io_pins[pin].io_edgetrigger == edgetrigger)
|
|
|
|
return (0);
|
|
|
|
io->io_pins[pin].io_edgetrigger = edgetrigger;
|
2003-11-03 21:53:38 +00:00
|
|
|
if (bootverbose)
|
|
|
|
printf("ioapic%u: intpin %d trigger: %s\n", io->io_id, pin,
|
2004-05-04 20:39:24 +00:00
|
|
|
trigger == INTR_TRIGGER_EDGE ? "edge" : "level");
|
2003-11-03 21:53:38 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Register a complete I/O APIC object with the interrupt subsystem.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ioapic_register(void *cookie)
|
|
|
|
{
|
|
|
|
struct ioapic_intsrc *pin;
|
|
|
|
struct ioapic *io;
|
|
|
|
volatile ioapic_t *apic;
|
|
|
|
uint32_t flags;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
io = (struct ioapic *)cookie;
|
|
|
|
apic = io->io_addr;
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
|
|
flags = ioapic_read(apic, IOAPIC_VER) & IOART_VER_VERSION;
|
|
|
|
STAILQ_INSERT_TAIL(&ioapic_list, io, io_next);
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
2003-11-04 19:22:20 +00:00
|
|
|
printf("ioapic%u <Version %u.%u> irqs %u-%u on motherboard\n",
|
|
|
|
io->io_id, flags >> 4, flags & 0xf, io->io_intbase,
|
|
|
|
io->io_intbase + io->io_numintr - 1);
|
2005-11-02 20:11:47 +00:00
|
|
|
|
|
|
|
/* Register valid pins as interrupt sources. */
|
2006-10-10 23:23:12 +00:00
|
|
|
intr_register_pic(&io->io_pic);
|
2005-11-02 20:11:47 +00:00
|
|
|
for (i = 0, pin = io->io_pins; i < io->io_numintr; i++, pin++)
|
|
|
|
if (pin->io_irq < NUM_IO_INTS)
|
|
|
|
intr_register_source(&pin->io_intsrc);
|
2003-11-03 21:53:38 +00:00
|
|
|
}
|
2007-03-05 16:22:49 +00:00
|
|
|
|
|
|
|
/* A simple new-bus driver to consume PCI I/O APIC devices. */
|
|
|
|
static int
|
|
|
|
ioapic_pci_probe(device_t dev)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (pci_get_class(dev) == PCIC_BASEPERIPH &&
|
|
|
|
pci_get_subclass(dev) == PCIS_BASEPERIPH_PIC) {
|
|
|
|
switch (pci_get_progif(dev)) {
|
|
|
|
case PCIP_BASEPERIPH_PIC_IO_APIC:
|
|
|
|
device_set_desc(dev, "IO APIC");
|
|
|
|
break;
|
|
|
|
case PCIP_BASEPERIPH_PIC_IOX_APIC:
|
|
|
|
device_set_desc(dev, "IO(x) APIC");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return (ENXIO);
|
|
|
|
}
|
|
|
|
device_quiet(dev);
|
|
|
|
return (-10000);
|
|
|
|
}
|
|
|
|
return (ENXIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ioapic_pci_attach(device_t dev)
|
|
|
|
{
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static device_method_t ioapic_pci_methods[] = {
|
|
|
|
/* Device interface */
|
|
|
|
DEVMETHOD(device_probe, ioapic_pci_probe),
|
|
|
|
DEVMETHOD(device_attach, ioapic_pci_attach),
|
|
|
|
|
|
|
|
{ 0, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_CLASS_0(ioapic, ioapic_pci_driver, ioapic_pci_methods, 0);
|
|
|
|
|
|
|
|
static devclass_t ioapic_devclass;
|
|
|
|
DRIVER_MODULE(ioapic, pci, ioapic_pci_driver, ioapic_devclass, 0, 0);
|
2007-03-20 21:53:31 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* A new-bus driver to consume the memory resources associated with
|
|
|
|
* the APICs in the system. On some systems ACPI or PnPBIOS system
|
|
|
|
* resource devices may already claim these resources. To keep from
|
|
|
|
* breaking those devices, we attach ourself to the nexus device after
|
|
|
|
* legacy0 and acpi0 and ignore any allocation failures.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
apic_identify(driver_t *driver, device_t parent)
|
|
|
|
{
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add at order 12. acpi0 is probed at order 10 and legacy0
|
|
|
|
* is probed at order 11.
|
|
|
|
*/
|
|
|
|
if (lapic_paddr != 0)
|
|
|
|
BUS_ADD_CHILD(parent, 12, "apic", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
apic_probe(device_t dev)
|
|
|
|
{
|
|
|
|
|
|
|
|
device_set_desc(dev, "APIC resources");
|
|
|
|
device_quiet(dev);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
apic_add_resource(device_t dev, int rid, vm_paddr_t base, size_t length)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
|
|
|
|
#ifdef PAE
|
|
|
|
/*
|
|
|
|
* Resources use long's to track resources, so we can't
|
|
|
|
* include memory regions above 4GB.
|
|
|
|
*/
|
|
|
|
if (base >= ~0ul)
|
|
|
|
return;
|
|
|
|
#endif
|
|
|
|
error = bus_set_resource(dev, SYS_RES_MEMORY, rid, base, length);
|
|
|
|
if (error)
|
|
|
|
panic("apic_add_resource: resource %d failed set with %d", rid,
|
|
|
|
error);
|
|
|
|
bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
apic_attach(device_t dev)
|
|
|
|
{
|
|
|
|
struct ioapic *io;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Reserve the local APIC. */
|
|
|
|
apic_add_resource(dev, 0, lapic_paddr, sizeof(lapic_t));
|
|
|
|
i = 1;
|
|
|
|
STAILQ_FOREACH(io, &ioapic_list, io_next) {
|
|
|
|
apic_add_resource(dev, i, io->io_paddr, IOAPIC_MEM_REGION);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static device_method_t apic_methods[] = {
|
|
|
|
/* Device interface */
|
|
|
|
DEVMETHOD(device_identify, apic_identify),
|
|
|
|
DEVMETHOD(device_probe, apic_probe),
|
|
|
|
DEVMETHOD(device_attach, apic_attach),
|
|
|
|
|
|
|
|
{ 0, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_CLASS_0(apic, apic_driver, apic_methods, 0);
|
|
|
|
|
|
|
|
static devclass_t apic_devclass;
|
|
|
|
DRIVER_MODULE(apic, nexus, apic_driver, apic_devclass, 0, 0);
|