Initial work towards supporting IO APICs. This cannot work for PCI interrupts
though until we use ACPI for proper PCI IRQ routing through the IO APIC. Therefore the IO APIC code path is not yet enabled and the IO APIC isn't used. ISA interrupts would work though, as would PCI interrupts if you'd hardcode them for your specific configuration. Note that this change also modifies some parts in the bootloader and in the PIC setup to make local APICs available even on non-SMP systems. This causes APIC timers to be used instead the normal PIT if it is available (also on non-SMP configurations). Also fixes some general errors in SMP and PIC code as well as some code cleanup. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@26492 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -8,17 +8,22 @@
|
||||
#ifndef KERNEL_ARCH_INT_H
|
||||
#define KERNEL_ARCH_INT_H
|
||||
|
||||
|
||||
#include <boot/kernel_args.h>
|
||||
#include <arch_int.h>
|
||||
|
||||
// config flags for arch_int_configure_io_interrupt()
|
||||
#define B_EDGE_TRIGGERED 1
|
||||
#define B_LEVEL_TRIGGERED 2
|
||||
#define B_LOW_ACTIVE_POLARITY 4
|
||||
#define B_HIGH_ACTIVE_POLARITY 8
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
status_t arch_int_init(kernel_args *args);
|
||||
status_t arch_int_init_post_vm(kernel_args *args);
|
||||
struct kernel_args;
|
||||
|
||||
status_t arch_int_init(struct kernel_args *args);
|
||||
status_t arch_int_init_post_vm(struct kernel_args *args);
|
||||
status_t arch_int_init_post_device_manager(struct kernel_args *args);
|
||||
|
||||
void arch_int_enable_interrupts(void);
|
||||
@@ -26,6 +31,7 @@ int arch_int_disable_interrupts(void);
|
||||
void arch_int_restore_interrupts(int oldstate);
|
||||
void arch_int_enable_io_interrupt(int irq);
|
||||
void arch_int_disable_io_interrupt(int irq);
|
||||
void arch_int_configure_io_interrupt(int irq, uint32 config);
|
||||
bool arch_int_are_interrupts_enabled(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#define B_SAFEMODE_DISABLE_USER_ADD_ONS "disableuseraddons"
|
||||
#define B_SAFEMODE_DISABLE_IDE_DMA "disableidedma"
|
||||
#define B_SAFEMODE_DISABLE_IOAPIC "disable_ioapic"
|
||||
#define B_SAFEMODE_DISABLE_ACPI "disable_acpi"
|
||||
#define B_SAFEMODE_DISABLE_APM "disable_apm"
|
||||
#define B_SAFEMODE_DISABLE_SMP "disable_smp"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
SubDir HAIKU_TOP src add-ons kernel bus_managers pci ;
|
||||
|
||||
UsePrivateHeaders kernel shared ;
|
||||
UsePrivateKernelHeaders ;
|
||||
UsePrivateHeaders shared ;
|
||||
UsePrivateHeaders [ FDirName kernel util ] ;
|
||||
|
||||
KernelAddon pci :
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#include "pci_private.h"
|
||||
#include "pci.h"
|
||||
|
||||
// private header for configuring io interrupts to level triggered
|
||||
#include <arch/int.h>
|
||||
|
||||
#define TRACE_CAP(x...) dprintf(x)
|
||||
#define FLOW(x...)
|
||||
//#define FLOW(x...) dprintf(x)
|
||||
@@ -1005,6 +1008,12 @@ PCI::_ReadHeaderInfo(PCIDev *dev)
|
||||
dev->device, dev->function, PCI_min_grant, 1);
|
||||
dev->info.u.h0.max_latency = ReadConfig(dev->domain, dev->bus,
|
||||
dev->device, dev->function, PCI_max_latency, 1);
|
||||
|
||||
if (dev->info.u.h0.interrupt_line != 0
|
||||
&& dev->info.u.h0.interrupt_line != 0xff) {
|
||||
arch_int_configure_io_interrupt(dev->info.u.h0.interrupt_line,
|
||||
B_LEVEL_TRIGGERED | B_LOW_ACTIVE_POLARITY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -421,18 +421,20 @@ smp_init_other_cpus(void)
|
||||
unload_driver_settings(handle);
|
||||
}
|
||||
|
||||
if (gKernelArgs.num_cpus < 2)
|
||||
if (gKernelArgs.arch_args.apic_phys == 0)
|
||||
return;
|
||||
|
||||
TRACE(("smp: found %ld cpus\n", gKernelArgs.num_cpus));
|
||||
TRACE(("smp: found %ld cpu%s\n", gKernelArgs.num_cpus, gKernelArgs.num_cpus != 1 ? "s" : ""));
|
||||
TRACE(("smp: apic_phys = %p\n", (void *)gKernelArgs.arch_args.apic_phys));
|
||||
TRACE(("smp: ioapic_phys = %p\n", (void *)gKernelArgs.arch_args.ioapic_phys));
|
||||
|
||||
// map in the apic & ioapic
|
||||
// map in the apic & ioapic (if available)
|
||||
gKernelArgs.arch_args.apic = (uint32 *)mmu_map_physical_memory(
|
||||
gKernelArgs.arch_args.apic_phys, B_PAGE_SIZE, kDefaultPageFlags);
|
||||
gKernelArgs.arch_args.ioapic = (uint32 *)mmu_map_physical_memory(
|
||||
gKernelArgs.arch_args.ioapic_phys, B_PAGE_SIZE, kDefaultPageFlags);
|
||||
if (gKernelArgs.arch_args.ioapic_phys != 0) {
|
||||
gKernelArgs.arch_args.ioapic = (uint32 *)mmu_map_physical_memory(
|
||||
gKernelArgs.arch_args.ioapic_phys, B_PAGE_SIZE, kDefaultPageFlags);
|
||||
}
|
||||
|
||||
TRACE(("smp: apic = %p\n", gKernelArgs.arch_args.apic));
|
||||
TRACE(("smp: ioapic = %p\n", gKernelArgs.arch_args.ioapic));
|
||||
@@ -440,6 +442,9 @@ smp_init_other_cpus(void)
|
||||
// calculate how fast the apic timer is
|
||||
calculate_apic_timer_conversion_factor();
|
||||
|
||||
if (gKernelArgs.num_cpus < 2)
|
||||
return;
|
||||
|
||||
for (uint32 i = 1; i < gKernelArgs.num_cpus; i++) {
|
||||
// create a final stack the trampoline code will put the ap processor on
|
||||
gKernelArgs.cpu_kstack[i].start = (addr_t)mmu_allocate(NULL, KERNEL_STACK_SIZE);
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
#include <arch/user_debugger.h>
|
||||
#include <arch/vm.h>
|
||||
|
||||
#include <arch/x86/smp_apic.h>
|
||||
#include <arch/x86/descriptors.h>
|
||||
#include <arch/x86/vm86.h>
|
||||
|
||||
#include "interrupts.h"
|
||||
|
||||
#include <safemode.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -70,10 +72,78 @@
|
||||
|
||||
#define PIC_NON_SPECIFIC_EOI 0x20
|
||||
|
||||
#define PIC_INT_BASE ARCH_INTERRUPT_BASE
|
||||
#define PIC_SLAVE_INT_BASE (ARCH_INTERRUPT_BASE + 8)
|
||||
#define PIC_SLAVE_INT_BASE 8
|
||||
#define PIC_NUM_INTS 0x0f
|
||||
|
||||
|
||||
// Definitions for a 82093AA IO APIC controller
|
||||
#define IO_APIC_IDENTIFICATION 0x00
|
||||
#define IO_APIC_VERSION 0x01
|
||||
#define IO_APIC_ARBITRATION 0x02
|
||||
#define IO_APIC_REDIRECTION_TABLE 0x10 // entry = base + 2 * index
|
||||
|
||||
// Fields for the version register
|
||||
#define IO_APIC_VERSION_SHIFT 0
|
||||
#define IO_APIC_VERSION_MASK 0xff
|
||||
#define IO_APIC_MAX_REDIRECTION_ENTRY_SHIFT 16
|
||||
#define IO_APIC_MAX_REDIRECTION_ENTRY_MASK 0xff
|
||||
|
||||
// Fields of each redirection table entry
|
||||
#define IO_APIC_DESTINATION_FIELD_SHIFT 56
|
||||
#define IO_APIC_DESTINATION_FIELD_MASK 0x0f
|
||||
#define IO_APIC_INTERRUPT_MASK_SHIFT 16
|
||||
#define IO_APIC_INTERRUPT_MASKED 1
|
||||
#define IO_APIC_INTERRUPT_UNMASKED 0
|
||||
#define IO_APIC_TRIGGER_MODE_SHIFT 15
|
||||
#define IO_APIC_TRIGGER_MODE_EDGE 0
|
||||
#define IO_APIC_TRIGGER_MODE_LEVEL 1
|
||||
#define IO_APIC_REMOTE_IRR_SHIFT 14
|
||||
#define IO_APIC_PIN_POLARITY_SHIFT 13
|
||||
#define IO_APIC_PIN_POLARITY_HIGH_ACTIVE 0
|
||||
#define IO_APIC_PIN_POLARITY_LOW_ACTIVE 1
|
||||
#define IO_APIC_DELIVERY_STATUS_SHIFT 12
|
||||
#define IO_APIC_DELIVERY_STATUS_IDLE 0
|
||||
#define IO_APIC_DELIVERY_STATUS_PENDING 1
|
||||
#define IO_APIC_DESTINATION_MODE_SHIFT 11
|
||||
#define IO_APIC_DESTINATION_MODE_PHYSICAL 0
|
||||
#define IO_APIC_DESTINATION_MODE_LOGICAL 1
|
||||
#define IO_APIC_DELIVERY_MODE_SHIFT 8
|
||||
#define IO_APIC_DELIVERY_MODE_MASK 0x07
|
||||
#define IO_APIC_DELIVERY_MODE_FIXED 0
|
||||
#define IO_APIC_DELIVERY_MODE_LOWEST_PRIO 1
|
||||
#define IO_APIC_DELIVERY_MODE_SMI 2
|
||||
#define IO_APIC_DELIVERY_MODE_NMI 4
|
||||
#define IO_APIC_DELIVERY_MODE_INIT 5
|
||||
#define IO_APIC_DELIVERY_MODE_EXT_INT 7
|
||||
#define IO_APIC_INTERRUPT_VECTOR_SHIFT 0
|
||||
#define IO_APIC_INTERRUPT_VECTOR_MASK 0xff
|
||||
|
||||
typedef struct ioapic_s {
|
||||
volatile uint32 io_register_select;
|
||||
uint32 reserved[3];
|
||||
volatile uint32 io_window_register;
|
||||
} ioapic _PACKED;
|
||||
|
||||
static ioapic *sIOAPIC = NULL;
|
||||
static uint32 sIOAPICMaxRedirectionEntry = 23;
|
||||
static void *sLocalAPIC = NULL;
|
||||
|
||||
static uint32 sIRQToIOAPICPin[256];
|
||||
|
||||
bool gUsingIOAPIC = false;
|
||||
|
||||
typedef struct interrupt_controller_s {
|
||||
const char *name;
|
||||
void (*enable_io_interrupt)(int32 num);
|
||||
void (*disable_io_interrupt)(int32 num);
|
||||
void (*configure_io_interrupt)(int32 num, uint32 config);
|
||||
bool (*is_spurious_interrupt)(int32 num);
|
||||
void (*end_of_interrupt)(int32 num);
|
||||
} interrupt_controller;
|
||||
|
||||
static interrupt_controller *sCurrentPIC = NULL;
|
||||
|
||||
|
||||
static const char *kInterruptNames[] = {
|
||||
/* 0 */ "Divide Error Exception",
|
||||
/* 1 */ "Debug Exception",
|
||||
@@ -105,7 +175,7 @@ typedef struct {
|
||||
} desc_table;
|
||||
static desc_table *sIDT = NULL;
|
||||
|
||||
static uint16 sLevelTriggeredInterrupts;
|
||||
static uint32 sLevelTriggeredInterrupts = 0;
|
||||
// binary mask: 1 level, 0 edge
|
||||
|
||||
// table with functions handling respective interrupts
|
||||
@@ -156,7 +226,7 @@ x86_set_task_gate(int32 n, int32 segment)
|
||||
* it must assume it's a spurious interrupt.
|
||||
*/
|
||||
|
||||
static inline bool
|
||||
static bool
|
||||
pic_is_spurious_interrupt(int32 num)
|
||||
{
|
||||
int32 isr;
|
||||
@@ -184,34 +254,107 @@ pic_is_spurious_interrupt(int32 num)
|
||||
static void
|
||||
pic_end_of_interrupt(int32 num)
|
||||
{
|
||||
if (num >= PIC_INT_BASE && num <= PIC_INT_BASE + PIC_NUM_INTS) {
|
||||
// PIC 8259 controlled interrupt
|
||||
if (num >= PIC_SLAVE_INT_BASE)
|
||||
out8(PIC_NON_SPECIFIC_EOI, PIC_SLAVE_CONTROL);
|
||||
if (num < 0 || num > PIC_NUM_INTS)
|
||||
return;
|
||||
|
||||
// we always need to acknowledge the master PIC
|
||||
out8(PIC_NON_SPECIFIC_EOI, PIC_MASTER_CONTROL);
|
||||
}
|
||||
// PIC 8259 controlled interrupt
|
||||
if (num >= PIC_SLAVE_INT_BASE)
|
||||
out8(PIC_NON_SPECIFIC_EOI, PIC_SLAVE_CONTROL);
|
||||
|
||||
// we always need to acknowledge the master PIC
|
||||
out8(PIC_NON_SPECIFIC_EOI, PIC_MASTER_CONTROL);
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
pic_is_level_triggered(int32 num)
|
||||
static void
|
||||
pic_enable_io_interrupt(int32 num)
|
||||
{
|
||||
return sLevelTriggeredInterrupts & (1U << (num - PIC_INT_BASE));
|
||||
// interrupt is specified "normalized"
|
||||
if (num < 0 || num > PIC_NUM_INTS)
|
||||
return;
|
||||
|
||||
// enable PIC 8259 controlled interrupt
|
||||
|
||||
TRACE(("pic_enable_io_interrupt: irq %ld\n", num));
|
||||
|
||||
if (num < PIC_SLAVE_INT_BASE)
|
||||
out8(in8(PIC_MASTER_MASK) & ~(1 << num), PIC_MASTER_MASK);
|
||||
else
|
||||
out8(in8(PIC_SLAVE_MASK) & ~(1 << (num - PIC_SLAVE_INT_BASE)), PIC_SLAVE_MASK);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pic_disable_io_interrupt(int32 num)
|
||||
{
|
||||
// interrupt is specified "normalized"
|
||||
// never disable slave pic line IRQ 2
|
||||
if (num < 0 || num > PIC_NUM_INTS || num == 2)
|
||||
return;
|
||||
|
||||
// disable PIC 8259 controlled interrupt
|
||||
|
||||
TRACE(("pic_disable_io_interrupt: irq %ld\n", num));
|
||||
|
||||
if (num < PIC_SLAVE_INT_BASE)
|
||||
out8(in8(PIC_MASTER_MASK) | (1 << num), PIC_MASTER_MASK);
|
||||
else
|
||||
out8(in8(PIC_SLAVE_MASK) | (1 << (num - PIC_SLAVE_INT_BASE)), PIC_SLAVE_MASK);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pic_configure_io_interrupt(int32 num, uint32 config)
|
||||
{
|
||||
uint8 value;
|
||||
int32 localBit;
|
||||
if (num < 0 || num > PIC_NUM_INTS || num == 2)
|
||||
return;
|
||||
|
||||
TRACE(("pic_configure_io_interrupt: irq %ld; config 0x%08lx\n", num, config));
|
||||
|
||||
if (num < PIC_SLAVE_INT_BASE) {
|
||||
value = in8(PIC_MASTER_TRIGGER_MODE);
|
||||
localBit = num;
|
||||
} else {
|
||||
value = in8(PIC_SLAVE_TRIGGER_MODE);
|
||||
localBit = num - PIC_SLAVE_INT_BASE;
|
||||
}
|
||||
|
||||
if (config & B_LEVEL_TRIGGERED)
|
||||
value |= 1 << localBit;
|
||||
else
|
||||
value &= ~(1 << localBit);
|
||||
|
||||
if (num < PIC_SLAVE_INT_BASE)
|
||||
out8(value, PIC_MASTER_TRIGGER_MODE);
|
||||
else
|
||||
out8(value, PIC_SLAVE_TRIGGER_MODE);
|
||||
|
||||
sLevelTriggeredInterrupts = in8(PIC_MASTER_TRIGGER_MODE)
|
||||
| (in8(PIC_SLAVE_TRIGGER_MODE) << 8);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pic_init(void)
|
||||
{
|
||||
static interrupt_controller picController = {
|
||||
"8259 PIC",
|
||||
&pic_enable_io_interrupt,
|
||||
&pic_disable_io_interrupt,
|
||||
&pic_configure_io_interrupt,
|
||||
&pic_is_spurious_interrupt,
|
||||
&pic_end_of_interrupt
|
||||
};
|
||||
|
||||
// Start initialization sequence for the master and slave PICs
|
||||
out8(PIC_INIT1 | PIC_INIT1_SEND_INIT4, PIC_MASTER_INIT1);
|
||||
out8(PIC_INIT1 | PIC_INIT1_SEND_INIT4, PIC_SLAVE_INIT1);
|
||||
|
||||
// Set start of interrupts to 0x20 for master, 0x28 for slave
|
||||
out8(PIC_INT_BASE, PIC_MASTER_INIT2);
|
||||
out8(PIC_SLAVE_INT_BASE, PIC_SLAVE_INIT2);
|
||||
out8(ARCH_INTERRUPT_BASE, PIC_MASTER_INIT2);
|
||||
out8(ARCH_INTERRUPT_BASE + PIC_SLAVE_INT_BASE, PIC_SLAVE_INIT2);
|
||||
|
||||
// Specify cascading through interrupt 2
|
||||
out8(PIC_INIT3_IR2_IS_SLAVE, PIC_MASTER_INIT3);
|
||||
@@ -228,49 +371,277 @@ pic_init(void)
|
||||
|
||||
#if 0
|
||||
// should set everything possible to level triggered
|
||||
out8(PIC_MASTER_TRIGGER_MODE, 0xf8);
|
||||
out8(PIC_SLAVE_TRIGGER_MODE, 0xde);
|
||||
out8(0xf8, PIC_MASTER_TRIGGER_MODE);
|
||||
out8(0xde, PIC_SLAVE_TRIGGER_MODE);
|
||||
#endif
|
||||
|
||||
sLevelTriggeredInterrupts = in8(PIC_MASTER_TRIGGER_MODE)
|
||||
| (in8(PIC_SLAVE_TRIGGER_MODE) << 8);
|
||||
|
||||
TRACE(("PIC level trigger mode: %04x\n", sLevelTriggeredInterrupts));
|
||||
TRACE(("PIC level trigger mode: 0x%08lx\n", sLevelTriggeredInterrupts));
|
||||
|
||||
// make the pic controller the current one
|
||||
sCurrentPIC = &picController;
|
||||
gUsingIOAPIC = false;
|
||||
}
|
||||
|
||||
|
||||
static inline uint32
|
||||
ioapic_read_32(uint8 registerSelect)
|
||||
{
|
||||
sIOAPIC->io_register_select = registerSelect;
|
||||
return sIOAPIC->io_window_register;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
ioapic_write_32(uint8 registerSelect, uint32 value)
|
||||
{
|
||||
sIOAPIC->io_register_select = registerSelect;
|
||||
sIOAPIC->io_window_register = value;
|
||||
}
|
||||
|
||||
|
||||
static inline uint64
|
||||
ioapic_read_64(uint8 registerSelect)
|
||||
{
|
||||
uint64 result;
|
||||
sIOAPIC->io_register_select = registerSelect + 1;
|
||||
result = sIOAPIC->io_window_register;
|
||||
result <<= 32;
|
||||
sIOAPIC->io_register_select = registerSelect;
|
||||
result |= sIOAPIC->io_window_register;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
ioapic_write_64(uint8 registerSelect, uint64 value)
|
||||
{
|
||||
sIOAPIC->io_register_select = registerSelect;
|
||||
sIOAPIC->io_window_register = (uint32)value;
|
||||
sIOAPIC->io_register_select = registerSelect + 1;
|
||||
sIOAPIC->io_window_register = (uint32)(value >> 32);
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
ioapic_is_spurious_interrupt(int32 num)
|
||||
{
|
||||
// the spurious interrupt vector is initialized to the max value in smp
|
||||
return num == 0xff - ARCH_INTERRUPT_BASE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ioapic_end_of_interrupt(int32 num)
|
||||
{
|
||||
*(volatile uint32 *)((char *)sLocalAPIC + APIC_EOI) = 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ioapic_enable_io_interrupt(int32 num)
|
||||
{
|
||||
uint64 entry;
|
||||
int32 pin = sIRQToIOAPICPin[num];
|
||||
if (pin < 0 || pin > sIOAPICMaxRedirectionEntry)
|
||||
return;
|
||||
|
||||
TRACE(("ioapic_enable_io_interrupt: IRQ %ld -> pin %ld\n", num, pin));
|
||||
|
||||
entry = ioapic_read_64(IO_APIC_REDIRECTION_TABLE + pin * 2);
|
||||
entry &= ~(1 << IO_APIC_INTERRUPT_MASK_SHIFT);
|
||||
entry |= IO_APIC_INTERRUPT_UNMASKED << IO_APIC_INTERRUPT_MASK_SHIFT;
|
||||
ioapic_write_64(IO_APIC_REDIRECTION_TABLE + pin * 2, entry);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ioapic_disable_io_interrupt(int32 num)
|
||||
{
|
||||
uint64 entry;
|
||||
int32 pin = sIRQToIOAPICPin[num];
|
||||
if (pin < 0 || pin > sIOAPICMaxRedirectionEntry)
|
||||
return;
|
||||
|
||||
TRACE(("ioapic_disable_io_interrupt: IRQ %ld -> pin %ld\n", num, pin));
|
||||
|
||||
entry = ioapic_read_64(IO_APIC_REDIRECTION_TABLE + pin * 2);
|
||||
entry &= ~(1 << IO_APIC_INTERRUPT_MASK_SHIFT);
|
||||
entry |= IO_APIC_INTERRUPT_MASKED << IO_APIC_INTERRUPT_MASK_SHIFT;
|
||||
ioapic_write_64(IO_APIC_REDIRECTION_TABLE + pin * 2, entry);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ioapic_configure_io_interrupt(int32 num, uint32 config)
|
||||
{
|
||||
uint64 entry;
|
||||
int32 pin = sIRQToIOAPICPin[num];
|
||||
if (pin < 0 || pin > sIOAPICMaxRedirectionEntry)
|
||||
return;
|
||||
|
||||
TRACE(("ioapic_configure_io_interrupt: IRQ %ld -> pin %ld; config 0x%08lx\n",
|
||||
num, pin, config));
|
||||
|
||||
entry = ioapic_read_64(IO_APIC_REDIRECTION_TABLE + pin * 2);
|
||||
entry &= ~((1 << IO_APIC_TRIGGER_MODE_SHIFT)
|
||||
| (1 << IO_APIC_PIN_POLARITY_SHIFT)
|
||||
| (IO_APIC_INTERRUPT_VECTOR_MASK << IO_APIC_INTERRUPT_VECTOR_SHIFT));
|
||||
|
||||
if (config & B_LEVEL_TRIGGERED) {
|
||||
entry |= (IO_APIC_TRIGGER_MODE_LEVEL << IO_APIC_TRIGGER_MODE_SHIFT);
|
||||
sLevelTriggeredInterrupts |= (1 << num);
|
||||
} else {
|
||||
entry |= (IO_APIC_TRIGGER_MODE_EDGE << IO_APIC_TRIGGER_MODE_SHIFT);
|
||||
sLevelTriggeredInterrupts &= ~(1 << num);
|
||||
}
|
||||
|
||||
if (config & B_LOW_ACTIVE_POLARITY)
|
||||
entry |= (IO_APIC_PIN_POLARITY_LOW_ACTIVE << IO_APIC_PIN_POLARITY_SHIFT);
|
||||
else
|
||||
entry |= (IO_APIC_PIN_POLARITY_HIGH_ACTIVE << IO_APIC_PIN_POLARITY_SHIFT);
|
||||
|
||||
entry |= (num + ARCH_INTERRUPT_BASE) << IO_APIC_INTERRUPT_VECTOR_SHIFT;
|
||||
ioapic_write_64(IO_APIC_REDIRECTION_TABLE + pin * 2, entry);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ioapic_init(kernel_args *args)
|
||||
{
|
||||
uint32 i;
|
||||
uint32 version;
|
||||
uint64 targetAPIC;
|
||||
void *settings;
|
||||
|
||||
static interrupt_controller ioapicController = {
|
||||
"82093AA IOAPIC",
|
||||
&ioapic_enable_io_interrupt,
|
||||
&ioapic_disable_io_interrupt,
|
||||
&ioapic_configure_io_interrupt,
|
||||
&ioapic_is_spurious_interrupt,
|
||||
&ioapic_end_of_interrupt
|
||||
};
|
||||
|
||||
if (args->arch_args.apic == NULL) {
|
||||
dprintf("no local apic availabe\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// always map the local apic as it can be used for timers even if we
|
||||
// don't end up using the io apic
|
||||
sLocalAPIC = args->arch_args.apic;
|
||||
if (map_physical_memory("local apic", (void *)args->arch_args.apic_phys,
|
||||
B_PAGE_SIZE, B_EXACT_ADDRESS, B_KERNEL_READ_AREA
|
||||
| B_KERNEL_WRITE_AREA, &sLocalAPIC) < B_OK) {
|
||||
panic("mapping the local apic failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args->arch_args.ioapic == NULL) {
|
||||
dprintf("no ioapic available, not using ioapics for interrupt routing\n");
|
||||
return;
|
||||
}
|
||||
|
||||
settings = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS);
|
||||
if (settings != NULL && get_driver_boolean_parameter(settings,
|
||||
B_SAFEMODE_DISABLE_IOAPIC, false, false)) {
|
||||
dprintf("ioapic explicitly disabled, not using ioapics for interrupt routing\n");
|
||||
unload_driver_settings(settings);
|
||||
return;
|
||||
} else if (settings != NULL)
|
||||
unload_driver_settings(settings);
|
||||
|
||||
// TODO: remove when the PCI IRQ routing through ACPI is available below
|
||||
return;
|
||||
|
||||
// map in the ioapic
|
||||
sIOAPIC = (ioapic *)args->arch_args.ioapic;
|
||||
if (map_physical_memory("ioapic", (void *)args->arch_args.ioapic_phys,
|
||||
B_PAGE_SIZE, B_EXACT_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
|
||||
(void **)&sIOAPIC) < B_OK) {
|
||||
panic("mapping the ioapic failed");
|
||||
return;
|
||||
}
|
||||
|
||||
version = ioapic_read_32(IO_APIC_VERSION);
|
||||
if (version == 0xffffffff) {
|
||||
dprintf("ioapic seems inaccessible, not using it\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sLevelTriggeredInterrupts = 0;
|
||||
sIOAPICMaxRedirectionEntry
|
||||
= ((version >> IO_APIC_MAX_REDIRECTION_ENTRY_SHIFT)
|
||||
& IO_APIC_MAX_REDIRECTION_ENTRY_MASK);
|
||||
|
||||
// use the boot CPU as the target for all interrupts
|
||||
targetAPIC = args->arch_args.cpu_apic_id[0];
|
||||
|
||||
// program the interrupt vectors of the ioapic
|
||||
for (i = 0; i <= sIOAPICMaxRedirectionEntry; i++) {
|
||||
// initialize everything to deliver to the boot CPU in physical mode
|
||||
// and masked until explicitly enabled through enable_io_interrupt()
|
||||
uint64 entry = (targetAPIC << IO_APIC_DESTINATION_FIELD_SHIFT)
|
||||
| (IO_APIC_INTERRUPT_MASKED << IO_APIC_INTERRUPT_MASK_SHIFT)
|
||||
| (IO_APIC_DESTINATION_MODE_PHYSICAL << IO_APIC_DESTINATION_MODE_SHIFT)
|
||||
| ((i + ARCH_INTERRUPT_BASE) << IO_APIC_INTERRUPT_VECTOR_SHIFT);
|
||||
|
||||
if (i == 0) {
|
||||
// make redirection entry 0 into an external interrupt
|
||||
entry |= (IO_APIC_TRIGGER_MODE_EDGE << IO_APIC_TRIGGER_MODE_SHIFT)
|
||||
| (IO_APIC_PIN_POLARITY_HIGH_ACTIVE << IO_APIC_PIN_POLARITY_SHIFT)
|
||||
| (IO_APIC_DELIVERY_MODE_EXT_INT << IO_APIC_DELIVERY_MODE_SHIFT);
|
||||
} else if (i < 16) {
|
||||
// make 1-15 ISA interrupts
|
||||
entry |= (IO_APIC_TRIGGER_MODE_EDGE << IO_APIC_TRIGGER_MODE_SHIFT)
|
||||
| (IO_APIC_PIN_POLARITY_HIGH_ACTIVE << IO_APIC_PIN_POLARITY_SHIFT)
|
||||
| (IO_APIC_DELIVERY_MODE_FIXED << IO_APIC_DELIVERY_MODE_SHIFT);
|
||||
} else {
|
||||
// and the rest are PCI interrupts
|
||||
entry |= (IO_APIC_TRIGGER_MODE_LEVEL << IO_APIC_TRIGGER_MODE_SHIFT)
|
||||
| (IO_APIC_PIN_POLARITY_LOW_ACTIVE << IO_APIC_PIN_POLARITY_SHIFT)
|
||||
| (IO_APIC_DELIVERY_MODE_FIXED << IO_APIC_DELIVERY_MODE_SHIFT);
|
||||
sLevelTriggeredInterrupts |= (1 << i);
|
||||
}
|
||||
|
||||
ioapic_write_64(IO_APIC_REDIRECTION_TABLE + 2 * i, entry);
|
||||
}
|
||||
|
||||
// setup default 1:1 mapping
|
||||
for (i = 0; i < 256; i++)
|
||||
sIRQToIOAPICPin[i] = i;
|
||||
|
||||
// TODO: here ACPI needs to be used to properly set up the PCI IRQ
|
||||
// routing.
|
||||
|
||||
// prefer the ioapic over the normal pic
|
||||
dprintf("using ioapic for interrupt routing\n");
|
||||
sCurrentPIC = &ioapicController;
|
||||
gUsingIOAPIC = true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
arch_int_enable_io_interrupt(int irq)
|
||||
{
|
||||
// interrupt is specified "normalized"
|
||||
if (irq < 0 || irq > PIC_NUM_INTS)
|
||||
return;
|
||||
|
||||
// enable PIC 8259 controlled interrupt
|
||||
|
||||
TRACE(("arch_int_enable_io_interrupt: irq %d\n", irq));
|
||||
|
||||
if (irq < 8)
|
||||
out8(in8(PIC_MASTER_MASK) & ~(1 << irq), PIC_MASTER_MASK);
|
||||
else
|
||||
out8(in8(PIC_SLAVE_MASK) & ~(1 << (irq - 8)), PIC_SLAVE_MASK);
|
||||
sCurrentPIC->enable_io_interrupt(irq);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
arch_int_disable_io_interrupt(int irq)
|
||||
{
|
||||
// interrupt is specified "normalized"
|
||||
// never disable slave pic line IRQ 2
|
||||
if (irq < 0 || irq > PIC_NUM_INTS || irq == 2)
|
||||
return;
|
||||
sCurrentPIC->disable_io_interrupt(irq);
|
||||
}
|
||||
|
||||
// disable PIC 8259 controlled interrupt
|
||||
|
||||
if (irq < 8)
|
||||
out8(in8(PIC_MASTER_MASK) | (1 << irq), PIC_MASTER_MASK);
|
||||
else
|
||||
out8(in8(PIC_SLAVE_MASK) | (1 << (irq - 8)), PIC_SLAVE_MASK);
|
||||
void
|
||||
arch_int_configure_io_interrupt(int irq, uint32 config)
|
||||
{
|
||||
sCurrentPIC->configure_io_interrupt(irq, config);
|
||||
}
|
||||
|
||||
|
||||
@@ -504,24 +875,25 @@ page_fault_exception(struct iframe* frame)
|
||||
static void
|
||||
hardware_interrupt(struct iframe* frame)
|
||||
{
|
||||
bool levelTriggered = pic_is_level_triggered(frame->vector);
|
||||
int32 vector = frame->vector - ARCH_INTERRUPT_BASE;
|
||||
bool levelTriggered = false;
|
||||
int ret;
|
||||
|
||||
// This is a workaround for spurious assertions of interrupts 7/15
|
||||
// which seems to be an often seen problem on the PC platform
|
||||
if (pic_is_spurious_interrupt(frame->vector - ARCH_INTERRUPT_BASE)) {
|
||||
TRACE(("got spurious interrupt at vector %ld\n", frame->vector));
|
||||
if (sCurrentPIC->is_spurious_interrupt(vector)) {
|
||||
TRACE(("got spurious interrupt at vector %ld\n", vector));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!levelTriggered)
|
||||
pic_end_of_interrupt(frame->vector);
|
||||
if (vector < 32)
|
||||
levelTriggered = (sLevelTriggeredInterrupts & (1 << vector)) != 0;
|
||||
|
||||
ret = int_io_interrupt_handler(frame->vector - ARCH_INTERRUPT_BASE,
|
||||
levelTriggered);
|
||||
if (!levelTriggered)
|
||||
sCurrentPIC->end_of_interrupt(vector);
|
||||
|
||||
ret = int_io_interrupt_handler(vector, levelTriggered);
|
||||
|
||||
if (levelTriggered)
|
||||
pic_end_of_interrupt(frame->vector);
|
||||
sCurrentPIC->end_of_interrupt(vector);
|
||||
|
||||
if (ret == B_INVOKE_SCHEDULER) {
|
||||
cpu_status state = disable_interrupts();
|
||||
@@ -536,7 +908,7 @@ hardware_interrupt(struct iframe* frame)
|
||||
|
||||
|
||||
status_t
|
||||
arch_int_init(kernel_args *args)
|
||||
arch_int_init(struct kernel_args *args)
|
||||
{
|
||||
int i;
|
||||
interrupt_handler_function** table;
|
||||
@@ -544,7 +916,7 @@ arch_int_init(kernel_args *args)
|
||||
// set the global sIDT variable
|
||||
sIDT = (desc_table *)args->arch_args.vir_idt;
|
||||
|
||||
// setup the interrupt controller
|
||||
// setup the standard programmable interrupt controller
|
||||
pic_init();
|
||||
|
||||
set_intr_gate(0, &trap0);
|
||||
@@ -584,6 +956,14 @@ arch_int_init(kernel_args *args)
|
||||
set_intr_gate(45, &trap45);
|
||||
set_intr_gate(46, &trap46);
|
||||
set_intr_gate(47, &trap47);
|
||||
set_intr_gate(48, &trap48);
|
||||
set_intr_gate(49, &trap49);
|
||||
set_intr_gate(50, &trap50);
|
||||
set_intr_gate(51, &trap51);
|
||||
set_intr_gate(52, &trap52);
|
||||
set_intr_gate(53, &trap53);
|
||||
set_intr_gate(54, &trap54);
|
||||
set_intr_gate(55, &trap55);
|
||||
|
||||
set_system_gate(98, &trap98); // for performance testing only
|
||||
set_system_gate(99, &trap99);
|
||||
@@ -628,10 +1008,12 @@ arch_int_init(kernel_args *args)
|
||||
|
||||
|
||||
status_t
|
||||
arch_int_init_post_vm(kernel_args *args)
|
||||
arch_int_init_post_vm(struct kernel_args *args)
|
||||
{
|
||||
area_id area;
|
||||
|
||||
ioapic_init(args);
|
||||
|
||||
sIDT = (desc_table *)args->arch_args.vir_idt;
|
||||
area = create_area("idt", (void *)&sIDT, B_EXACT_ADDRESS, B_PAGE_SIZE, B_ALREADY_WIRED,
|
||||
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
|
||||
|
||||
@@ -231,6 +231,14 @@ TRAP(trap44, 44)
|
||||
TRAP(trap45, 45)
|
||||
TRAP(trap46, 46)
|
||||
TRAP(trap47, 47)
|
||||
TRAP(trap48, 48)
|
||||
TRAP(trap49, 49)
|
||||
TRAP(trap50, 50)
|
||||
TRAP(trap51, 51)
|
||||
TRAP(trap52, 52)
|
||||
TRAP(trap53, 53)
|
||||
TRAP(trap54, 54)
|
||||
TRAP(trap55, 55)
|
||||
|
||||
TRAP(trap251, 251)
|
||||
TRAP(trap252, 252)
|
||||
|
||||
@@ -34,65 +34,24 @@
|
||||
# define TRACE(x) ;
|
||||
#endif
|
||||
|
||||
//#define TRACE_ARCH_SMP_TIMER
|
||||
#ifdef TRACE_ARCH_SMP_TIMER
|
||||
# define TRACE_TIMER(x) dprintf x
|
||||
#else
|
||||
# define TRACE_TIMER(x) ;
|
||||
#endif
|
||||
|
||||
extern timer_info gAPICTimer;
|
||||
|
||||
static void *apic = NULL;
|
||||
static uint32 cpu_apic_id[B_MAX_CPU_COUNT] = {0, 0};
|
||||
static uint32 cpu_os_id[B_MAX_CPU_COUNT] = {0, 0};
|
||||
static uint32 cpu_apic_version[B_MAX_CPU_COUNT] = {0, 0};
|
||||
static void *ioapic = NULL;
|
||||
|
||||
static int32
|
||||
i386_ici_interrupt(void *data)
|
||||
{
|
||||
// genuine inter-cpu interrupt
|
||||
TRACE(("inter-cpu interrupt on cpu %ld\n", smp_get_current_cpu()));
|
||||
arch_smp_ack_interrupt();
|
||||
|
||||
return smp_intercpu_int_handler();
|
||||
}
|
||||
|
||||
|
||||
static int32
|
||||
i386_spurious_interrupt(void *data)
|
||||
{
|
||||
// spurious interrupt
|
||||
TRACE(("spurious interrupt on cpu %ld\n", smp_get_current_cpu()));
|
||||
arch_smp_ack_interrupt();
|
||||
|
||||
return B_HANDLED_INTERRUPT;
|
||||
}
|
||||
|
||||
|
||||
static int32
|
||||
i386_smp_error_interrupt(void *data)
|
||||
{
|
||||
// smp error interrupt
|
||||
TRACE(("smp error interrupt on cpu %ld\n", smp_get_current_cpu()));
|
||||
arch_smp_ack_interrupt();
|
||||
|
||||
return B_HANDLED_INTERRUPT;
|
||||
}
|
||||
static void *sLocalAPIC = NULL;
|
||||
static uint32 sCPUAPICIds[B_MAX_CPU_COUNT];
|
||||
static uint32 sCPUOSIds[B_MAX_CPU_COUNT];
|
||||
static uint32 sAPICVersions[B_MAX_CPU_COUNT];
|
||||
|
||||
extern bool gUsingIOAPIC;
|
||||
|
||||
static uint32
|
||||
apic_read(uint32 offset)
|
||||
{
|
||||
return *(volatile uint32 *)((char *)apic + offset);
|
||||
return *(volatile uint32 *)((char *)sLocalAPIC + offset);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
apic_write(uint32 offset, uint32 data)
|
||||
{
|
||||
*(volatile uint32 *)((char *)apic + offset) = data;
|
||||
*(volatile uint32 *)((char *)sLocalAPIC + offset) = data;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,33 +115,74 @@ setup_apic(kernel_args *args, int32 cpu)
|
||||
}
|
||||
|
||||
|
||||
static int32
|
||||
i386_ici_interrupt(void *data)
|
||||
{
|
||||
// genuine inter-cpu interrupt
|
||||
TRACE(("inter-cpu interrupt on cpu %ld\n", smp_get_current_cpu()));
|
||||
|
||||
// if we are not using the IO APIC we need to acknowledge the
|
||||
// interrupt ourselfs
|
||||
if (!gUsingIOAPIC)
|
||||
apic_write(APIC_EOI, 0);
|
||||
|
||||
return smp_intercpu_int_handler();
|
||||
}
|
||||
|
||||
|
||||
static int32
|
||||
i386_spurious_interrupt(void *data)
|
||||
{
|
||||
// spurious interrupt
|
||||
TRACE(("spurious interrupt on cpu %ld\n", smp_get_current_cpu()));
|
||||
|
||||
// spurious interrupts must not be acknowledged as it does not expect
|
||||
// a end of interrupt - if we still do it we would loose the next best
|
||||
// interrupt
|
||||
return B_HANDLED_INTERRUPT;
|
||||
}
|
||||
|
||||
|
||||
static int32
|
||||
i386_smp_error_interrupt(void *data)
|
||||
{
|
||||
// smp error interrupt
|
||||
TRACE(("smp error interrupt on cpu %ld\n", smp_get_current_cpu()));
|
||||
|
||||
// if we are not using the IO APIC we need to acknowledge the
|
||||
// interrupt ourselfs
|
||||
if (!gUsingIOAPIC)
|
||||
apic_write(APIC_EOI, 0);
|
||||
|
||||
return B_HANDLED_INTERRUPT;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
arch_smp_init(kernel_args *args)
|
||||
{
|
||||
TRACE(("arch_smp_init: entry\n"));
|
||||
|
||||
if (args->arch_args.apic == NULL)
|
||||
return B_OK;
|
||||
|
||||
sLocalAPIC = args->arch_args.apic;
|
||||
|
||||
// setup some globals
|
||||
memcpy(sCPUAPICIds, args->arch_args.cpu_apic_id, sizeof(args->arch_args.cpu_apic_id));
|
||||
memcpy(sCPUOSIds, args->arch_args.cpu_os_id, sizeof(args->arch_args.cpu_os_id));
|
||||
memcpy(sAPICVersions, args->arch_args.cpu_apic_version, sizeof(args->arch_args.cpu_apic_version));
|
||||
|
||||
// set up the local apic on the boot cpu
|
||||
arch_smp_per_cpu_init(args, 0);
|
||||
|
||||
if (args->num_cpus > 1) {
|
||||
// setup some globals
|
||||
apic = (void *)args->arch_args.apic;
|
||||
ioapic = (void *)args->arch_args.ioapic;
|
||||
memcpy(cpu_apic_id, args->arch_args.cpu_apic_id, sizeof(args->arch_args.cpu_apic_id));
|
||||
memcpy(cpu_os_id, args->arch_args.cpu_os_id, sizeof(args->arch_args.cpu_os_id));
|
||||
memcpy(cpu_apic_version, args->arch_args.cpu_apic_version, sizeof(args->arch_args.cpu_apic_version));
|
||||
|
||||
// setup regions that represent the apic & ioapic
|
||||
map_physical_memory("local apic", (void *)args->arch_args.apic_phys, B_PAGE_SIZE,
|
||||
B_EXACT_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, &apic);
|
||||
map_physical_memory("ioapic", (void *)args->arch_args.ioapic_phys, B_PAGE_SIZE,
|
||||
B_EXACT_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, &ioapic);
|
||||
|
||||
// set up the local apic on the boot cpu
|
||||
arch_smp_per_cpu_init(args, 0);
|
||||
|
||||
// I/O interrupts start at ARCH_INTERRUPT_BASE, so all interrupts are shifted
|
||||
install_io_interrupt_handler(0xfd - ARCH_INTERRUPT_BASE, &i386_ici_interrupt, NULL, B_NO_LOCK_VECTOR);
|
||||
install_io_interrupt_handler(0xfe - ARCH_INTERRUPT_BASE, &i386_smp_error_interrupt, NULL, B_NO_LOCK_VECTOR);
|
||||
install_io_interrupt_handler(0xff - ARCH_INTERRUPT_BASE, &i386_spurious_interrupt, NULL, B_NO_LOCK_VECTOR);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ arch_smp_send_ici(int32 target_cpu)
|
||||
state = disable_interrupts();
|
||||
|
||||
config = apic_read(APIC_INTR_COMMAND_2) & APIC_INTR_COMMAND_2_MASK;
|
||||
apic_write(APIC_INTR_COMMAND_2, config | cpu_apic_id[target_cpu] << 24);
|
||||
apic_write(APIC_INTR_COMMAND_2, config | sCPUAPICIds[target_cpu] << 24);
|
||||
|
||||
config = apic_read(APIC_INTR_COMMAND_1) & APIC_INTR_COMMAND_1_MASK;
|
||||
apic_write(APIC_INTR_COMMAND_1, config | 0xfd | APIC_DELIVERY_MODE_FIXED
|
||||
@@ -242,10 +242,3 @@ arch_smp_send_ici(int32 target_cpu)
|
||||
|
||||
restore_interrupts(state);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
arch_smp_ack_interrupt(void)
|
||||
{
|
||||
apic_write(APIC_EOI, 0);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ void trap12();void trap13();void trap14();void trap16();void trap17();void trap1
|
||||
void trap19();
|
||||
void trap32();void trap33();void trap34();void trap35();void trap36();void trap37();
|
||||
void trap38();void trap39();void trap40();void trap41();void trap42();void trap43();
|
||||
void trap44();void trap45();void trap46();void trap47();
|
||||
void trap44();void trap45();void trap46();void trap47();void trap48();void trap49();
|
||||
void trap50();void trap51();void trap52();void trap53();void trap54();void trap55();
|
||||
|
||||
void double_fault(); // int 8
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
static void *sApicPtr = NULL;
|
||||
static uint32 sApicTicsPerSec = 0;
|
||||
|
||||
extern bool gUsingIOAPIC;
|
||||
|
||||
struct timer_info gAPICTimer = {
|
||||
"APIC",
|
||||
&apic_get_prio,
|
||||
@@ -51,17 +53,14 @@ _apic_write(uint32 offset, uint32 data)
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
_apic_acknowledge_interrupt(void)
|
||||
{
|
||||
_apic_write(APIC_EOI, 0);
|
||||
}
|
||||
|
||||
|
||||
static int32
|
||||
apic_timer_interrupt(void *data)
|
||||
{
|
||||
_apic_acknowledge_interrupt();
|
||||
// if we are not using the IO APIC we need to acknowledge the
|
||||
// interrupt ourselfs
|
||||
if (!gUsingIOAPIC)
|
||||
_apic_write(APIC_EOI, 0);
|
||||
|
||||
return timer_interrupt();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user