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