hos/src/hulk/apic.d
Josh Holtrop 7264a31ff3 Rework scanning MADT table entries
Get I/O APIC address from MADT entry.
Add DEBUG flag for APIC.
Show number of CPUs found.
2023-10-31 13:29:23 -04:00

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();
}
}