Get I/O APIC address from MADT entry. Add DEBUG flag for APIC. Show number of CPUs found.
371 lines
11 KiB
D
371 lines
11 KiB
D
/**
|
|
* APIC (Advanced Programmable Interrupt Controller) functionality.
|
|
*/
|
|
module hulk.apic;
|
|
|
|
import hulk.hurl;
|
|
import hulk.acpi;
|
|
import hulk.idt;
|
|
import hulk.rtc;
|
|
import hulk.pit;
|
|
import hulk.klog;
|
|
import hulk.memory;
|
|
|
|
struct Apic
|
|
{
|
|
private enum DEBUG = false;
|
|
private enum DEFAULT_IO_APIC_ADDRESS = 0xFEC0_0000u;
|
|
private enum uint PERIODIC_MODE = 0x2_0000u;
|
|
|
|
private enum ulong IRQ_PIT = 2u;
|
|
private enum ulong IRQ_RTC = 8u;
|
|
|
|
/**
|
|
* MADT ACPI table structure.
|
|
*/
|
|
static struct MADT
|
|
{
|
|
/**
|
|
* MADT table entry header.
|
|
*/
|
|
static struct Entry
|
|
{
|
|
enum PROCESSOR_LOCAL_APIC = 0;
|
|
enum IO_APIC = 1;
|
|
enum IO_APIC_INTERRUPT_SOURCE_OVERRIDE = 2;
|
|
enum IO_APIC_NMI_SOURCE = 3;
|
|
enum LOCAL_APIC_NMI = 4;
|
|
enum LOCAL_APIC_ADDRESS_OVERRIDE = 5;
|
|
enum PROCESSOR_LOCAL_X2APIC = 9;
|
|
|
|
ubyte type;
|
|
ubyte length;
|
|
}
|
|
|
|
/**
|
|
* Processor Local APIC.
|
|
*/
|
|
static struct Entry0
|
|
{
|
|
Entry header;
|
|
ubyte acpi_processor_id;
|
|
ubyte apic_id;
|
|
uint flags;
|
|
}
|
|
static assert(Entry0.sizeof == 8);
|
|
|
|
/**
|
|
* I/O APIC.
|
|
*/
|
|
static struct Entry1
|
|
{
|
|
Entry header;
|
|
ubyte io_apic_id;
|
|
ubyte _reserved;
|
|
uint io_apic_address;
|
|
uint global_system_interrupt_base;
|
|
}
|
|
static assert(Entry1.sizeof == 12);
|
|
|
|
/**
|
|
* IO/APIC Interrupt Source Override.
|
|
*/
|
|
static struct Entry2
|
|
{
|
|
Entry header;
|
|
ubyte bus_source;
|
|
ubyte irq_source;
|
|
uint global_system_interrupt;
|
|
ushort flags;
|
|
ushort _reserved;
|
|
}
|
|
static assert(Entry2.sizeof == 12);
|
|
|
|
/**
|
|
* IO/APIC Non-maskable interrupt source.
|
|
*/
|
|
static struct Entry3
|
|
{
|
|
Entry header;
|
|
ubyte nmi_source;
|
|
ubyte _reserved;
|
|
ushort flags;
|
|
ubyte[4] global_system_interrupt;
|
|
}
|
|
static assert(Entry3.sizeof == 10);
|
|
|
|
/**
|
|
* Local APIC Non-maskable interrupts.
|
|
*/
|
|
static struct Entry4
|
|
{
|
|
Entry header;
|
|
ubyte acpi_processor_id;
|
|
ubyte[2] flags;
|
|
ubyte lint_nr;
|
|
}
|
|
static assert(Entry4.sizeof == 6);
|
|
|
|
/**
|
|
* Local APIC Address Override.
|
|
*/
|
|
static struct Entry5
|
|
{
|
|
Entry header;
|
|
ushort _reserved;
|
|
ubyte[8] local_apic_address;
|
|
}
|
|
static assert(Entry5.sizeof == 12);
|
|
|
|
/**
|
|
* Processor Local x2APIC.
|
|
*/
|
|
static struct Entry9
|
|
{
|
|
Entry header;
|
|
ushort _reserved;
|
|
uint local_x2apic_id;
|
|
uint flags;
|
|
uint acpi_id;
|
|
}
|
|
static assert(Entry9.sizeof == 16);
|
|
|
|
Acpi.Header header;
|
|
uint local_apic_address;
|
|
uint flags;
|
|
|
|
static assert(MADT.sizeof == 0x2C);
|
|
|
|
/**
|
|
* Scan the MADT table.
|
|
*/
|
|
public void scan()
|
|
{
|
|
apic_registers = cast(ApicRegisters *)local_apic_address;
|
|
const(void) * end = cast(const(void) *)(&this) + header.length;
|
|
const(Entry) * entry = cast(const(Entry) *)(cast(ulong)&this + MADT.sizeof);
|
|
bool first_io_apic = true;
|
|
size_t n_cpus = 0u;
|
|
while (entry < end)
|
|
{
|
|
if (DEBUG)
|
|
{
|
|
Klog.writefln("MADT entry type %u, length %u", entry.type, entry.length);
|
|
}
|
|
switch (entry.type)
|
|
{
|
|
case Entry.PROCESSOR_LOCAL_APIC:
|
|
Entry0 * e = cast(Entry0 *)entry;
|
|
n_cpus++;
|
|
if (DEBUG)
|
|
{
|
|
Klog.writefln(" ACPI processor ID: %u, APIC ID: %u, Flags: 0x%x",
|
|
e.acpi_processor_id, e.apic_id, e.flags);
|
|
}
|
|
break;
|
|
|
|
case Entry.IO_APIC:
|
|
Entry1 * e = cast(Entry1 *)entry;
|
|
if (first_io_apic)
|
|
{
|
|
io_apic_registers = cast(IoApicRegisters *)e.io_apic_address;
|
|
first_io_apic = false;
|
|
}
|
|
if (DEBUG)
|
|
{
|
|
Klog.writefln(" I/O APIC ID: %u, I/O APIC Address: 0x%x, GSIB: %u",
|
|
e.io_apic_id, e.io_apic_address, e.global_system_interrupt_base);
|
|
}
|
|
break;
|
|
|
|
case Entry.IO_APIC_INTERRUPT_SOURCE_OVERRIDE:
|
|
Entry2 * e = cast(Entry2 *)entry;
|
|
if (DEBUG)
|
|
{
|
|
Klog.writefln(" Bus %u IRQ %u GSI %u Flags: 0x%x",
|
|
e.bus_source, e.irq_source, e.global_system_interrupt, e.flags);
|
|
}
|
|
break;
|
|
|
|
case Entry.IO_APIC_NMI_SOURCE:
|
|
Entry3 * e = cast(Entry3 *)entry;
|
|
if (DEBUG)
|
|
{
|
|
uint global_system_interrupt;
|
|
memcpy(&global_system_interrupt, &e.global_system_interrupt, 4u);
|
|
Klog.writefln(" NMI Source: %u, Flags: 0x%x, GSI: %u",
|
|
e.nmi_source, e.flags, global_system_interrupt);
|
|
}
|
|
break;
|
|
|
|
case Entry.LOCAL_APIC_NMI:
|
|
Entry4 * e = cast(Entry4 *)entry;
|
|
if (DEBUG)
|
|
{
|
|
ushort flags;
|
|
memcpy(&flags, &e.flags, 2u);
|
|
Klog.writefln(" ACPI Processor ID: %u, Flags: 0x%x, LINT#: %u",
|
|
e.acpi_processor_id, flags, e.lint_nr);
|
|
}
|
|
break;
|
|
|
|
case Entry.LOCAL_APIC_ADDRESS_OVERRIDE:
|
|
Entry5 * e = cast(Entry5 *)entry;
|
|
if (DEBUG)
|
|
{
|
|
memcpy(&apic_registers, &e.local_apic_address, 8u);
|
|
Klog.writefln(" Local APIC Address: 0x%x", apic_registers);
|
|
}
|
|
break;
|
|
|
|
case Entry.PROCESSOR_LOCAL_X2APIC:
|
|
Entry9 * e = cast(Entry9 *)entry;
|
|
if (DEBUG)
|
|
{
|
|
Klog.writefln(" x2APIC ID: %u, Flags: 0x%x, ACPI ID: %u",
|
|
e.local_x2apic_id, e.flags, e.acpi_id);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Move to next entry. */
|
|
entry = cast(const(Entry) *)(cast(size_t)entry + entry.length);
|
|
}
|
|
if (n_cpus > 0u)
|
|
{
|
|
Klog.writefln("%u CPU(s) found", n_cpus);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* APIC register structure.
|
|
*/
|
|
static struct ApicRegister
|
|
{
|
|
public uint value;
|
|
alias value this;
|
|
|
|
private ubyte[12] _padding;
|
|
}
|
|
|
|
/**
|
|
* APIC registers.
|
|
*/
|
|
static struct ApicRegisters
|
|
{
|
|
ApicRegister[2] _reserved0;
|
|
ApicRegister lapic_id;
|
|
ApicRegister lapic_version;
|
|
ApicRegister[4] _reserved1;
|
|
ApicRegister task_priority;
|
|
ApicRegister arbitration_priority;
|
|
ApicRegister processor_priority;
|
|
ApicRegister eoi;
|
|
ApicRegister remote_read;
|
|
ApicRegister logical_destination;
|
|
ApicRegister destination_format;
|
|
ApicRegister spurious_interrupt_vector;
|
|
ApicRegister[8] in_service;
|
|
ApicRegister[8] trigger_mode;
|
|
ApicRegister[8] interrupt_request;
|
|
ApicRegister error_status;
|
|
ApicRegister[6] _reserved2;
|
|
ApicRegister lvt_cmci;
|
|
ApicRegister[2] interrupt_command;
|
|
ApicRegister lvt_timer;
|
|
ApicRegister lvt_thermal_sensor;
|
|
ApicRegister lvt_performance_monitoring_counters;
|
|
ApicRegister[2] lvt_lint;
|
|
ApicRegister lvt_error;
|
|
ApicRegister initial_count;
|
|
ApicRegister current_count;
|
|
ApicRegister[4] _reserved3;
|
|
ApicRegister divide_configuration;
|
|
}
|
|
|
|
/**
|
|
* I/O APIC registers.
|
|
*/
|
|
static struct IoApicRegisters
|
|
{
|
|
ApicRegister address;
|
|
ApicRegister data;
|
|
}
|
|
|
|
private static __gshared ApicRegisters * apic_registers;
|
|
private static __gshared IoApicRegisters * io_apic_registers;
|
|
|
|
/**
|
|
* Initialize APIC.
|
|
*/
|
|
public static void initialize()
|
|
{
|
|
Klog.writefln("\a3Initializing APIC");
|
|
io_apic_registers = cast(IoApicRegisters *)DEFAULT_IO_APIC_ADDRESS;
|
|
MADT * madt = cast(MADT *)Acpi.get_table("APIC");
|
|
madt.scan();
|
|
Klog.writefln("APIC found at %p", apic_registers);
|
|
Klog.writefln("I/O APIC found at %p", io_apic_registers);
|
|
Hurl.map(cast(ulong)apic_registers, cast(ulong)apic_registers,
|
|
PT_WRITABLE | PT_WRITE_THROUGH | PT_DISABLE_CACHE | PT_NO_EXECUTE);
|
|
Hurl.map(cast(ulong)io_apic_registers, cast(ulong)io_apic_registers,
|
|
PT_WRITABLE | PT_WRITE_THROUGH | PT_DISABLE_CACHE | PT_NO_EXECUTE);
|
|
/* Enable local APIC to receive interrupts and set spurious interrupt
|
|
* vector to 0xFF. */
|
|
apic_registers.spurious_interrupt_vector.value = 0x100u | Idt.INT_APIC_SPURIOUS;
|
|
apic_registers.lvt_timer.value = Idt.INT_APIC_TIMER;
|
|
apic_registers.lvt_lint[0].value = Idt.INT_APIC_LINT0;
|
|
apic_registers.lvt_lint[1].value = Idt.INT_APIC_LINT1;
|
|
/* Enable PIT interrupt. */
|
|
configure_io_apic_irq(IRQ_PIT, Idt.INT_APIC_BASE + IRQ_PIT);
|
|
/* Enable RTC interrupt. */
|
|
configure_io_apic_irq(IRQ_RTC, Idt.INT_APIC_BASE + IRQ_RTC);
|
|
}
|
|
|
|
/**
|
|
* Configure routing for an I/O APIC IRQ.
|
|
*/
|
|
private static void configure_io_apic_irq(size_t io_apic_irq, size_t interrupt_id)
|
|
{
|
|
ulong entry = interrupt_id;
|
|
io_apic_registers.address.value = cast(uint)(0x10u + io_apic_irq * 2u);
|
|
io_apic_registers.data.value = entry & 0xFFFF_FFFFu;
|
|
io_apic_registers.address.value = cast(uint)(0x10u + io_apic_irq * 2u + 1u);
|
|
io_apic_registers.data.value = entry >> 32u;
|
|
}
|
|
|
|
/**
|
|
* Signal APIC end of interrupt.
|
|
*/
|
|
public static void eoi()
|
|
{
|
|
apic_registers.eoi.value = 0u;
|
|
}
|
|
|
|
/**
|
|
* APIC interrupt handler.
|
|
*/
|
|
public static void isr(ulong vector)
|
|
{
|
|
switch (vector)
|
|
{
|
|
case Idt.INT_APIC_BASE + IRQ_PIT:
|
|
Pit.isr();
|
|
break;
|
|
|
|
case Idt.INT_APIC_BASE + IRQ_RTC:
|
|
Rtc.isr();
|
|
break;
|
|
|
|
default:
|
|
Klog.writefln("Unhandled APIC ISR %u", vector);
|
|
break;
|
|
}
|
|
eoi();
|
|
}
|
|
}
|