@@ -0,0 +1,841 @@
//! ACPI discovery backend.
//!
//! Walks the ACPI tables the firmware left in memory (starting from the RSDP the
//! bootloader handed us) and translates the static tables into the generic
//! `device` model, so the kernel enumerates hardware without knowing ACPI is the
//! source. This is deliberately the *static-table* path, and **only** that: MADT
//! (CPUs / interrupt controllers), MCFG (PCIe ECAM -> PCI enumeration), HPET
//! (timer), and FADT (power register map). The DSDT/SSDT bytecode is *not*
//! interpreted here — the kernel collects the blobs and publishes them on the
//! acpi-tables node for the ring-3 acpi service to parse (device enumeration and
//! soft-off). Keeping the ~0.5 MB AML interpretation out of kernel init keeps it
//! off the single-core critical path (nothing else runs alongside it there).
//!
//! ACPI tables live in `.acpi_tables` / `.acpi_nvs` memory, which the kernel
//! identity-maps, so table addresses are dereferenced directly. PCIe ECAM is MMIO
//! and is *not* mapped up front, so configuration-space pages are mapped on demand via
//! the `Hal.mapMmio` callback the caller supplies (the architecture VMM's map primitive).
const std = @import ( " std " ) ;
const boot_handoff = @import ( " boot-handoff " ) ;
const abi = @import ( " abi " ) ;
const parameters = @import ( " parameters " ) ;
const device_model = @import ( " device-model.zig " ) ;
const DeviceTree = device_model . DeviceTree ;
const Hal = device_model . Hal ;
/// A hardware register located either in MMIO or I/O-port space, as ACPI's
/// Generic Address Structure describes. `address == 0` means "not present".
pub const RegisterAccess = struct {
/// true = system memory (MMIO), false = system I/O port space.
mmio : bool = false ,
address : u64 = 0 ,
/// Access width in bytes.
width : u8 = 0 ,
pub fn present ( self : RegisterAccess ) bool {
return self . address != 0 ;
}
} ;
/// The power register map, extracted from the FADT during discovery. Populated by
/// `discover`, read by `power` (kernel reboot). The **sleep-state (`_Sx`) values
/// live in AML**, which the kernel no longer parses — soft-off (S5) is owned by the
/// ring-3 acpi service (it re-parses the blobs on the published acpi-tables node and
/// writes the PM1 control register itself). So this holds only the FADT scalars.
pub const PowerInformation = struct {
/// The System Control Interrupt's GSI (FADT SCI_INT) — the line ACPI events
/// (power button, GPEs) arrive on. Published to the acpi service for M21.
sci_interrupt : u16 = 0 ,
/// The SMM command port and the value that switches the platform into ACPI mode.
smi_cmd : u16 = 0 ,
acpi_enable : u8 = 0 ,
acpi_disable : u8 = 0 ,
/// PM1 control registers — writing SLP_TYP|SLP_EN here enters a sleep state.
pm1a_cnt : RegisterAccess = . { } ,
pm1b_cnt : RegisterAccess = . { } ,
/// The FADT reset register and the value to write to it.
reset : RegisterAccess = . { } ,
reset_value : u8 = 0 ,
reset_supported : bool = false ,
} ;
/// Filled in by `discover`; the power service reads it to reboot/shutdown.
pub var power_information : PowerInformation = . { } ;
/// A legacy ISA IRQ remapped to a different global system interrupt (GSI), from a
/// MADT Interrupt Source Override. `flags` are the MPS INTI polarity/trigger bits.
pub const IsoEntry = struct {
source : u8 ,
gsi : u32 ,
flags : u16 ,
} ;
/// Firmware facts the architecture layer needs to avoid legacy assumptions (so danos boots
/// on legacy-free UEFI Class 3 machines). MMIO device *addresses* (HPET, IOAPIC)
/// come from the device tree instead; this holds the scalar facts that have no
/// natural device node.
pub const PlatformInformation = struct {
/// Whether the legacy 8259 PIC is present (MADT flags bit 0, PCAT_COMPAT). When
/// false, the PIC must not be programmed (it may not exist).
pic_present : bool = false ,
/// Local APIC MMIO base (MADT, honouring a type-5 address override).
lapic_base : u64 = 0xFEE00000 ,
/// The ACPI power-management timer — a fixed 3.579545 MHz counter usable as a
/// calibration reference when no HPET is present.
pm_timer : RegisterAccess = . { } ,
/// true = 32-bit PM timer counter, false = 24-bit (FADT flag TMR_VALUE_EXT).
pm_timer_32bit : bool = false ,
/// The console UART the firmware points at (SPCR), if any — MMIO or I/O port.
spcr_uart : ? RegisterAccess = null ,
/// SPCR interface type (0/1 = 16550/16450, …).
spcr_kind : u8 = 0 ,
/// ISA-IRQ-to-GSI remappings from the MADT (for future IOAPIC routing).
overrides : [ 16 ] IsoEntry = undefined ,
override_count : usize = 0 ,
/// Whether an IOMMU (VT-d DMA-remapping unit) was found in the ACPI DMAR table.
/// When false, `device_claim` on a DMA-capable device is equivalent to granting
/// ring 0 — a device can DMA to any physical address (docs/driver-model.md M16).
/// Detection is the first step; per-device domain enforcement lands with the first
/// DMA driver.
iommu_present : bool = false ,
/// MMIO base of the first DMA-remapping hardware unit (DMAR DRHD), when present.
iommu_base : u64 = 0 ,
/// The unit's Version register (offset 0x00) — its low byte is major.minor;
/// reading it back nonzero confirms a real, mappable VT-d unit.
iommu_version : u32 = 0 ,
/// The unit's Capability register (offset 0x08): supported address widths, number
/// of domains, etc. Recorded now; consumed when enforcement is built.
iommu_capabilities : u64 = 0 ,
} ;
/// Filled in by `discover`; the architecture layer reads it during bring-up.
pub var platform_information : PlatformInformation = . { } ;
/// One usable logical processor, from a MADT type-0 (Local APIC) record. The
/// `apic_id` is the Local APIC ID that SMP bring-up targets to wake this core
/// (INIT– SIPI– SIPI); `processor_id` is the ACPI namespace handle. Only processors
/// the firmware marks *enabled* are recorded — a disabled one can't be started.
pub const Cpu = struct {
processor_id : u8 ,
apic_id : u8 ,
/// MADT flags bit 1: usable but firmware-started offline (hot-plug / deferred
/// bring-up), as opposed to already available. Informational for now.
online_capable : bool ,
} ;
/// The set of usable logical processors the MADT listed — the hardware's degree of
/// parallelism. Includes the bootstrap processor danos already runs on; the rest
/// are the application processors SMP bring-up would start (see docs/smp.md).
pub const CpuInformation = struct {
/// A static pool sized well above any danos target (a desktop, two 4-core Pis).
/// If the MADT ever lists more, the surplus is dropped and counted in `dropped`
/// so the truncation is never silent.
cpus : [ maximum_cpus ] Cpu = undefined ,
count : usize = 0 ,
dropped : usize = 0 ,
} ;
const maximum_cpus = parameters . maximum_cpus ;
/// Filled in by `discover` (from the MADT); SMP bring-up reads it to wake the APs.
pub var cpu_information : CpuInformation = . { } ;
/// Physical address of the DSDT the FADT points at, or 0.
pub var dsdt_physical : u64 = 0 ;
/// The FADT itself (physical + length), published on the acpi-tables node so
/// the ring-3 acpi service can read the PM1 event and GPE blocks it needs for
/// the event side (docs/acpi.md — ACPI events). Distinguished from the AML
/// blob resources by its intact "FACP" header — the blobs are header-stripped.
var fadt_physical : u64 = 0 ;
var fadt_length : u64 = 0 ;
// AML blocks (DSDT + any SSDTs) collected during the table walk, as physical
// address + length of each table's post-header bytecode. The kernel does not
// interpret them — it publishes them on the acpi-tables node for the ring-3 acpi
// service to parse (device enumeration + soft-off). See publishAcpiTablesNode.
var aml_block_physical : [ 32 ] u64 = undefined ;
var aml_block_len : [ 32 ] usize = undefined ;
var aml_block_count : usize = 0 ;
fn addAmlBlock ( sdt_physical : u64 ) void {
if ( aml_block_count >= aml_block_physical . len or sdt_physical == 0 ) return ;
const h : * const SystemDescriptorTableHeader = @ptrFromInt ( boot_handoff . physicalToVirtual ( sdt_physical ) ) ;
if ( h . length <= @sizeOf ( SystemDescriptorTableHeader ) ) return ;
aml_block_physical [ aml_block_count ] = sdt_physical + @sizeOf ( SystemDescriptorTableHeader ) ;
aml_block_len [ aml_block_count ] = h . length - @sizeOf ( SystemDescriptorTableHeader ) ;
aml_block_count += 1 ;
}
/// RSDP structure for revision 0 (version 1.0)
const RootSystemDescriptionPointer = extern struct {
/// An 8 byte magic number used for locating the RSDP, containing RSD PTR.
signature : [ 8 ] u8 ,
/// A byte used to verify the first 20 bytes of the RSDP
checksum : u8 ,
/// An OEM-supplied string that identified the OEM.
oem_id : [ 6 ] u8 ,
/// The RSDP revision, used for determining which fields are available.
revision : u8 ,
/// A 32-bit physical address pointing to the RSDT.
root_system_description_table_address : u32 align ( 1 ) ,
} ;
/// XSDP structure for revision 2 (version 2.0+)
const ExtendedSystemDescriptorPointer = extern struct {
/// An 8 byte magic number used for locating the RSDP, containing RSD PTR.
signature : [ 8 ] u8 ,
/// A byte used to verify the first 20 bytes of the RSDP
checksum : u8 ,
/// An OEM-supplied string that identified the OEM.
oem_id : [ 6 ] u8 ,
/// The RSDP revision, used for determining which fields are available.
revision : u8 ,
/// deprecated since version 2.0. A 32-bit physical address pointing to the RSDT.
root_system_description_table_address : u32 align ( 1 ) ,
/// The size of the RSDP.
length : u32 align ( 1 ) ,
/// A 64-bit physical address pointing to the XSDT. If the revision is at least 2, the XSDT
/// should be used regardless of architecture, as the RSDT was deprecated.
extended_system_descriptor_table_address : u64 align ( 1 ) ,
/// A checksum used for the entire table.
extended_checksum : u8 ,
reserved : [ 3 ] u8 ,
} ;
/// Multiple APIC Description Table (MADT)
const APIC : [ 4 ] u8 = " APIC " .* ;
/// Boot Error Record Table (BERT)
const BERT : [ 4 ] u8 = " BERT " .* ;
/// Corrected Platform Error Polling Table (CPEP)
const CPEP : [ 4 ] u8 = " CPEP " .* ;
/// Differentiated System Description Table (DSDT)
const DSDT : [ 4 ] u8 = " DSDT " .* ;
/// Embedded Controller Boot Resources Table (ECDT)
const ECDT : [ 4 ] u8 = " ECDT " .* ;
/// Error Injection Table (EINJ)
const EINJ : [ 4 ] u8 = " EINJ " .* ;
/// Error Record Serialization Table (ERST)
const ERST : [ 4 ] u8 = " ERST " .* ;
/// Fixed ACPI Description Table (FADT)
const FACP : [ 4 ] u8 = " FACP " .* ;
/// Firmware ACPI Control Structure (FACS)
const FACS : [ 4 ] u8 = " FACS " .* ;
/// Hardware Error Source Table (HEST)
const HEST : [ 4 ] u8 = " HEST " .* ;
/// High Precision Event Timer table (HPET)
const HPET : [ 4 ] u8 = " HPET " .* ;
/// PCI Express memory-mapped configuration space table (MCFG)
const MCFG : [ 4 ] u8 = " MCFG " .* ;
/// Maximum System Characteristics Table (MSCT)
const MSCT : [ 4 ] u8 = " MSCT " .* ;
/// Memory Power State Table (MPST)
const MPST : [ 4 ] u8 = " MPST " .* ;
// Platform Memory Topology Table (PMTT)
const PMTT : [ 4 ] u8 = " PMTT " .* ;
/// Persistent System Description Table (PSDT)
const PSDT : [ 4 ] u8 = " PSDT " .* ;
/// ACPI RAS Feature Table (RASF)
const RASF : [ 4 ] u8 = " RASF " .* ;
/// Root System Description Table
const RSDT : [ 4 ] u8 = " RSDT " .* ;
/// Smart Battery Specification Table (SBST)
const SBST : [ 4 ] u8 = " SBST " .* ;
/// System Locality System Information Table (SLIT)
const SLIT : [ 4 ] u8 = " SLIT " .* ;
/// System Resource Affinity Table (SRAT)
const SRAT : [ 4 ] u8 = " SRAT " .* ;
/// Secondary System Description Table (SSDT)
const DMAR : [ 4 ] u8 = " DMAR " .* ;
const SSDT : [ 4 ] u8 = " SSDT " .* ;
/// Serial Port Console Redirection table (SPCR) — the firmware's console UART.
const SPCR : [ 4 ] u8 = " SPCR " .* ;
/// Extended System Description Table (XSDT; 64-bit version of the RSDT)
const XSDT : [ 4 ] u8 = " XSDT " .* ;
/// The header every system descriptor table (RSDT/XSDT and each SDT) begins with.
const SystemDescriptorTableHeader = extern struct {
/// A 4 byte signature used for identification (e.g. "RSDT", "APIC").
signature : [ 4 ] u8 ,
/// The length of the entire table, including the header.
length : u32 align ( 1 ) ,
/// The revision of the ACPI spec this table conforms to.
revision : u8 ,
/// An 8-bit checksum field for the whole table, inclusive of the header.
checksum : u8 ,
/// An OEM-supplied string that identified the OEM.
oem_id : [ 6 ] u8 ,
oem_table_id : [ 8 ] u8 ,
oem_revision : u32 align ( 1 ) ,
creator_id : u32 align ( 1 ) ,
creator_revision : u32 align ( 1 ) ,
} ;
// --- MADT: Multiple APIC Description Table (signature "APIC") ---------------
const Madt = extern struct {
header : SystemDescriptorTableHeader ,
local_apic_address : u32 align ( 1 ) ,
flags : u32 align ( 1 ) ,
// Followed by a variable-length run of interrupt-controller records, each a
// MadtRecordHeader plus a type-specific body.
} ;
const MadtRecordHeader = extern struct {
type : u8 ,
length : u8 ,
} ;
/// MADT record type 0: a processor's Local APIC.
const MadtLocalApic = extern struct {
record : MadtRecordHeader ,
processor_id : u8 ,
apic_id : u8 ,
/// bit 0 = enabled, bit 1 = online-capable.
flags : u32 align ( 1 ) ,
} ;
/// MADT record type 1: an I/O APIC.
const MadtIoApic = extern struct {
record : MadtRecordHeader ,
io_apic_id : u8 ,
reserved : u8 ,
address : u32 align ( 1 ) ,
/// First global system interrupt this I/O APIC handles.
gsi_base : u32 align ( 1 ) ,
} ;
/// MADT record type 2: an Interrupt Source Override (ISA IRQ -> GSI remap).
const MadtIso = extern struct {
record : MadtRecordHeader ,
bus : u8 ,
source : u8 ,
gsi : u32 align ( 1 ) ,
flags : u16 align ( 1 ) ,
} ;
/// MADT record type 5: Local APIC Address Override (64-bit MMIO base).
const MadtLapicOverride = extern struct {
record : MadtRecordHeader ,
reserved : u16 align ( 1 ) ,
address : u64 align ( 1 ) ,
} ;
// --- MCFG: PCIe ECAM configuration space (signature "MCFG") -----------------
const Mcfg = extern struct {
header : SystemDescriptorTableHeader ,
reserved : u64 align ( 1 ) ,
// Followed by one or more McfgAllocation entries.
} ;
const McfgAllocation = extern struct {
/// Physical base of this segment group's ECAM window.
base_address : u64 align ( 1 ) ,
segment_group : u16 align ( 1 ) ,
start_bus : u8 ,
end_bus : u8 ,
reserved : u32 align ( 1 ) ,
} ;
// --- HPET (signature "HPET") ------------------------------------------------
const Hpet = extern struct {
header : SystemDescriptorTableHeader ,
hardware_rev_id : u8 ,
flags : u8 ,
pci_vendor_id : u16 align ( 1 ) ,
// Generic Address Structure describing the register block.
address_space_id : u8 ,
register_bit_width : u8 ,
register_bit_offset : u8 ,
gas_reserved : u8 ,
address : u64 align ( 1 ) ,
hpet_number : u8 ,
minimum_tick : u16 align ( 1 ) ,
page_protection : u8 ,
} ;
// --- Entry point ------------------------------------------------------------
/// Discover hardware from the ACPI tables rooted at `rsdp_physical` and populate
/// `device_tree`. `hal` provides MMIO mapping (for PCIe ECAM) and port I/O. Also parses the
/// FADT into `power_information`, and publishes the AML blobs for the ring-3 acpi service.
pub fn discover ( rsdp_physical : u64 , memory_regions : [ ] const boot_handoff . MemoryRegion , device_tree : * DeviceTree , hal : Hal ) ! void {
if ( rsdp_physical == 0 ) return error . NoRsdp ;
boot_memory_regions = memory_regions ;
// Start clean so a re-run doesn't accumulate stale state.
power_information = . { } ;
fadt_physical = 0 ;
fadt_length = 0 ;
platform_information = . { } ;
dsdt_physical = 0 ;
aml_block_count = 0 ;
const rsdp : * const RootSystemDescriptionPointer = @ptrFromInt ( boot_handoff . physicalToVirtual ( rsdp_physical ) ) ;
if ( ! std . mem . eql ( u8 , & rsdp . signature , " RSD PTR " ) ) return error . BadRsdpSignature ;
// Revision 0 checksums only the first 20 bytes (the v1.0 RSDP).
if ( ! checksumOk ( @ptrFromInt ( boot_handoff . physicalToVirtual ( rsdp_physical ) ) , 20 ) ) return error . BadRsdpChecksum ;
if ( rsdp . revision >= 2 ) {
const xsdp : * const ExtendedSystemDescriptorPointer = @ptrFromInt ( boot_handoff . physicalToVirtual ( rsdp_physical ) ) ;
if ( ! checksumOk ( @ptrFromInt ( boot_handoff . physicalToVirtual ( rsdp_physical ) ) , xsdp . length ) ) return error . BadXsdpChecksum ;
try walkRoot ( u64 , xsdp . extended_system_descriptor_table_address , device_tree , hal ) ;
} else {
try walkRoot ( u32 , rsdp . root_system_description_table_address , device_tree , hal ) ;
}
// The kernel does **not** interpret the DSDT/SSDTs. Static-table discovery
// above (MADT/HPET/FADT/MCFG) is all the kernel needs — CPUs, timers, PCIe,
// and the power register map. The AML bytecode (device enumeration and the
// sleep-state `_Sx` values for soft-off) is entirely the ring-3 acpi service's
// job: it claims the acpi-tables node published below, parses the same blobs,
// and both registers the `_HID` devices and owns S5. Not parsing ~0.5 MB of
// AML in the kernel keeps boot latency off the critical, single-core path.
// Publish the acpi-tables node (docs/discovery.md): the AML blobs as
// memory resources for the acpi service to map and parse in ring 3, a broad
// io_port grant for the OperationRegion access its interpreter needs, and
// the SCI for the events track (M21). Exactly one node, one trusted
// claimant — the sole path by which AML (devices + soft-off) reaches ring 3,
// now that the kernel keeps only the *static* tables for itself.
publishAcpiTablesNode ( device_tree ) catch { } ;
}
/// Build the acpi-tables node (see the call site in discover). Best-effort: a
/// failure here leaves the kernel-seeded tree working, only the ring-3 service
/// finds nothing to claim.
fn publishAcpiTablesNode ( device_tree : * DeviceTree ) ! void {
const node = try device_tree . addChild ( device_tree . root , . acpi_tables , " acpi-tables " ) ;
// One memory resource per AML block — page-aligned base down, length padded
// up to cover the bytecode, so mmio_map hands the service a pointer into it.
var i : usize = 0 ;
while ( i < aml_block_count and i < device_model . maximum_resources - 2 ) : ( i += 1 ) {
// mmio_map preserves the sub-page offset, so the service maps this and
// gets a pointer straight to the bytecode.
_ = node . addResource ( . memory , aml_block_physical [ i ] , aml_block_len [ i ] ) ;
}
// The broad I/O grant: OperationRegions name whatever ports the firmware
// chose (EC, PM1, GPE, SMBus); which ports cannot be known before the AML
// that names them is parsed, so the grant is the whole space — the honest
// trust boundary of docs/discovery.md (the acpi service's one trusted node).
_ = node . addResource ( . io_port , 0 , 1 << 16 ) ;
// A broad interrupt window: ACPI _CRS names legacy ISA IRQs (the PS/2 lines
// 1 and 12, the RTC, …), and the service registers those devices under this
// node, so it must own a superset. The range [0, 256) covers every GSI; the
// SCI (recorded first, len 1) stays distinct so M21 can pick it out.
if ( power_information . sci_interrupt != 0 ) _ = node . addResource ( . irq , power_information . sci_interrupt , 1 ) ;
_ = node . addResource ( . irq , 0 , 256 ) ;
// The FADT rides along (M21): the service reads the PM1 event / GPE blocks
// from its own copy, telling it apart from the AML blobs by signature.
if ( fadt_physical != 0 ) _ = node . addResource ( . memory , fadt_physical , fadt_length ) ;
}
/// Walk the RSDT (Entry = u32) or XSDT (Entry = u64): validate it, then dispatch
/// each SDT it points at. A bad individual table is skipped, not fatal.
fn walkRoot ( comptime Entry : type , root_physical : u64 , device_tree : * DeviceTree , hal : Hal ) ! void {
const header : * const SystemDescriptorTableHeader = @ptrFromInt ( boot_handoff . physicalToVirtual ( root_physical ) ) ;
if ( ! checksumOk ( @ptrFromInt ( boot_handoff . physicalToVirtual ( root_physical ) ) , header . length ) ) return error . BadRootChecksum ;
const count = ( header . length - @sizeOf ( SystemDescriptorTableHeader ) ) / @sizeOf ( Entry ) ;
const base : [ * ] const u8 = @ptrFromInt ( boot_handoff . physicalToVirtual ( root_physical ) ) ;
const entries : [ * ] align ( 1 ) const Entry = @ptrCast ( base + @sizeOf ( SystemDescriptorTableHeader ) ) ;
for ( entries [ 0 .. count ] ) | ent | {
const sdt_physical : u64 = ent ; // u32 entries widen; u64 pass through
handleTable ( device_tree , hal , sdt_physical ) catch continue ;
}
}
/// Dispatch a single SDT on its signature.
fn handleTable ( device_tree : * DeviceTree , hal : Hal , sdt_physical : u64 ) ! void {
const header : * const SystemDescriptorTableHeader = @ptrFromInt ( boot_handoff . physicalToVirtual ( sdt_physical ) ) ;
const sig = header . signature ;
if ( std . mem . eql ( u8 , & sig , & APIC ) ) {
try parseMadt ( device_tree , header ) ;
} else if ( std . mem . eql ( u8 , & sig , & MCFG ) ) {
try parseMcfg ( device_tree , header ) ;
} else if ( std . mem . eql ( u8 , & sig , & HPET ) ) {
try parseHpet ( device_tree , hal , header ) ;
} else if ( std . mem . eql ( u8 , & sig , & FACP ) ) {
fadt_physical = sdt_physical ;
fadt_length = header . length ;
parseFadt ( header ) ;
} else if ( std . mem . eql ( u8 , & sig , & SPCR ) ) {
parseSpcr ( header ) ;
} else if ( std . mem . eql ( u8 , & sig , & DMAR ) ) {
parseDmar ( hal , header ) ;
} else if ( std . mem . eql ( u8 , & sig , & SSDT ) ) {
// Secondary namespace bytecode — collect it to publish for the ring-3 parse.
addAmlBlock ( sdt_physical ) ;
}
// Any other signature is recognised but left opaque for now.
}
/// MADT -> one processor node per Local APIC, one interrupt_controller per I/O APIC.
fn parseMadt ( device_tree : * DeviceTree , header : * const SystemDescriptorTableHeader ) ! void {
const madt : * const Madt = @ptrCast ( header ) ;
const total : usize = header . length ;
const base : [ * ] const u8 = @ptrCast ( header ) ;
var ioapic_index : usize = 0 ;
// MADT header: local APIC base + flags (bit 0 = 8259 PIC present).
platform_information . lapic_base = madt . local_apic_address ;
platform_information . pic_present = madt . flags & 1 != 0 ;
var off : usize = @sizeOf ( Madt ) ;
while ( off + @sizeOf ( MadtRecordHeader ) <= total ) {
const rec : * const MadtRecordHeader = @ptrCast ( base + off ) ;
if ( rec . length < @sizeOf ( MadtRecordHeader ) ) break ; // malformed; avoid a spin
switch ( rec . type ) {
0 = > {
const la : * const MadtLocalApic = @ptrCast ( base + off ) ;
// bit 0 = enabled: skip processors the firmware marks unusable.
if ( la . flags & 1 != 0 ) {
var nb : [ 24 ] u8 = undefined ;
const nm = std . fmt . bufPrint ( & nb , " cpu{d} " , . { la . processor_id } ) catch " cpu " ;
_ = try device_tree . addChild ( device_tree . root , . processor , nm ) ;
// Also record it as a schedulable core (with the APIC ID an AP
// wake needs, which the device node name doesn't preserve).
if ( cpu_information . count < cpu_information . cpus . len ) {
cpu_information . cpus [ cpu_information . count ] = . {
. processor_id = la . processor_id ,
. apic_id = la . apic_id ,
. online_capable = la . flags & 2 != 0 ,
} ;
cpu_information . count += 1 ;
} else {
cpu_information . dropped += 1 ;
}
}
} ,
1 = > {
const io : * const MadtIoApic = @ptrCast ( base + off ) ;
var nb : [ 24 ] u8 = undefined ;
const nm = std . fmt . bufPrint ( & nb , " ioapic{d} " , . { ioapic_index } ) catch " ioapic " ;
ioapic_index += 1 ;
const d = try device_tree . addChild ( device_tree . root , . interrupt_controller , nm ) ;
_ = d . addResource ( . memory , io . address , 0x20 ) ;
// The GSI range this I/O APIC handles, starting at gsi_base.
_ = d . addResource ( . irq , io . gsi_base , 0 ) ;
} ,
2 = > {
const iso : * const MadtIso = @ptrCast ( base + off ) ;
if ( platform_information . override_count < platform_information . overrides . len ) {
platform_information . overrides [ platform_information . override_count ] = . {
. source = iso . source ,
. gsi = iso . gsi ,
. flags = iso . flags ,
} ;
platform_information . override_count += 1 ;
}
} ,
5 = > {
const ovr : * const MadtLapicOverride = @ptrCast ( base + off ) ;
platform_information . lapic_base = ovr . address ;
} ,
else = > { } ,
}
off += rec . length ;
}
}
/// MCFG -> a pci_host_bridge per ECAM segment, then a PCI enumeration underneath.
fn parseMcfg ( device_tree : * DeviceTree , header : * const SystemDescriptorTableHeader ) ! void {
const total : usize = header . length ;
const base : [ * ] const u8 = @ptrCast ( header ) ;
var off : usize = @sizeOf ( Mcfg ) ;
while ( off + @sizeOf ( McfgAllocation ) <= total ) : ( off += @sizeOf ( McfgAllocation ) ) {
const alloc : * const McfgAllocation = @ptrCast ( base + off ) ;
const bus_count : u64 = @as ( u64 , alloc . end_bus - alloc . start_bus ) + 1 ;
var nb : [ 24 ] u8 = undefined ;
const nm = std . fmt . bufPrint ( & nb , " pci{d} " , . { alloc . segment_group } ) catch " pci " ;
const bridge = try device_tree . addChild ( device_tree . root , . pci_host_bridge , nm ) ;
// ECAM window: 1 MiB of configuration space per bus.
_ = bridge . addResource ( . memory , alloc . base_address , bus_count << 20 ) ;
_ = bridge . addResource ( . bus_range , alloc . start_bus , bus_count ) ;
addBridgeApertures ( bridge ) ;
// The bridge decodes the whole 16-bit I/O space toward its bus — the
// window functions' I/O BARs must register-contain within (M19.2).
_ = bridge . addResource ( . io_port , 0 , 1 << 16 ) ;
// The function walk itself retired to ring 3 (M19.3): the pci-bus
// driver claims this bridge, repeats the scan through its ECAM grant,
// and device_registers what it finds — the kernel seeds only the
// bridge. The scan's equivalence was proven before the hand-off
// (pci-scan), and the walk's history is in git if archaeology calls.
}
}
/// The boot memory map, stored at discover() entry for the aperture derivation
/// below (and, in M20, for the acpi-tables node's containment windows).
var boot_memory_regions : [ ] const boot_handoff . MemoryRegion = & . { } ;
/// The bridge's MMIO apertures, derived from the boot memory map's holes
/// (docs/discovery.md — apertures from the memory map): registered PCI functions carry BAR
/// resources, and `device_register` containment demands the bridge own windows
/// that cover them. Everything the firmware described is "not hole"; the low
/// aperture runs from the end of the described space below 4 GiB up to the
/// I/O-APIC region, the high one from 4 GiB (or the end of RAM above it) to
/// the 46-bit line. Coarse, mechanical, and AML-free — available at boot no
/// matter what later moved to user space.
fn addBridgeApertures ( bridge : * device_model . Device ) void {
// Below 4 GiB the described regions are sparse (RAM low, firmware flash
// and tables high), so the holes are the *gaps between* them — a single
// "after the last region" rule dies on OVMF's flash at the very top.
// Sort-merge the described ranges, then keep the three largest gaps
// (resource slots are bounded at 8 per device; ECAM + bus range + 3 + the
// high aperture fits). Above 4 GiB one aperture runs from the end of the
// described space to the 46-bit line.
const Range = struct { base : u64 , end : u64 } ;
var below : [ 64 ] Range = undefined ;
var below_count : usize = 0 ;
var high_end : u64 = 1 << 32 ;
for ( boot_memory_regions ) | region | {
const end = region . base + region . pages * 4096 ;
// Above 4 GiB only *usable RAM* blocks the aperture: OVMF describes
// its own 64-bit PCI window as a reserved region and then programs
// BARs inside it — honoring reserved there would exclude the very
// space BARs live in. Below 4 GiB every described region blocks (the
// kernel image, the tables, the ramdisk all live there). Bring-up
// trust: only the bridge's claimant can register into the aperture.
if ( region . kind == . usable and end > high_end ) high_end = end ;
if ( region . base >= ( 1 << 32 ) or below_count == below . len ) continue ;
below [ below_count ] = . { . base = region . base , . end = @min ( end , 1 << 32 ) } ;
below_count += 1 ;
}
// Insertion sort by base (the map is small and this runs once at boot).
for ( 1 .. below_count ) | i | {
const key = below [ i ] ;
var j = i ;
while ( j > 0 and below [ j - 1 ] . base > key . base ) : ( j -= 1 ) below [ j ] = below [ j - 1 ] ;
below [ j ] = key ;
}
// Walk the sorted ranges, collecting inter-region gaps of at least 1 MiB.
var gaps : [ 3 ] Range = . { Range { . base = 0 , . end = 0 } } ** 3 ;
var cursor : u64 = 0 ;
var index : usize = 0 ;
while ( index <= below_count ) : ( index += 1 ) {
const gap_end = if ( index == below_count ) ( 1 << 32 ) else below [ index ] . base ;
if ( gap_end > cursor and gap_end - cursor >= ( 1 << 20 ) ) {
// Keep the three largest, replacing the smallest kept so far.
var smallest : usize = 0 ;
for ( gaps , 0 .. ) | gap , gi | {
if ( gap . end - gap . base < gaps [ smallest ] . end - gaps [ smallest ] . base ) smallest = gi ;
}
if ( gap_end - cursor > gaps [ smallest ] . end - gaps [ smallest ] . base ) {
gaps [ smallest ] = . { . base = cursor , . end = gap_end } ;
}
}
if ( index < below_count and below [ index ] . end > cursor ) cursor = below [ index ] . end ;
}
for ( gaps ) | gap | {
if ( gap . end > gap . base ) _ = bridge . addResource ( . memory , gap . base , gap . end - gap . base ) ;
}
_ = bridge . addResource ( . memory , high_end , ( @as ( u64 , 1 ) << 46 ) - high_end ) ;
}
/// HPET -> a timer node with its register block as an MMIO resource, plus the GSI
/// its comparators can raise.
///
/// Unlike a PCI device or an ACPI `_CRS` node, the HPET table carries **no interrupt
/// number**: which I/O APIC inputs a comparator may drive is advertised at runtime,
/// as a bitmask in `Tn_INT_ROUTE_CAP` (bits 63:32 of the Timer 0 configuration register).
/// So discovery maps the register block, reads the mask, and records one concrete
/// `irq` resource — the GSI a driver is entitled to bind. The driver commits to it
/// by writing `Tn_INT_ROUTE_CNF`; the kernel checks the binding against this
/// resource (see process.ownedGsi), which is what keeps `irq_bind` a capability
/// rather than a request for an arbitrary interrupt line.
fn parseHpet ( device_tree : * DeviceTree , hal : Hal , header : * const SystemDescriptorTableHeader ) ! void {
const hpet : * const Hpet = @ptrCast ( header ) ;
const d = try device_tree . addChild ( device_tree . root , . timer , " hpet " ) ;
// The GAS tag must say System Memory (0) before we treat `address` as a physical
// address. The HPET spec mandates it, but firmware is not a thing to trust: a
// System I/O (1) tag here would have us map an arbitrary page and read a bogus
// route-capability mask out of it.
if ( hpet . address_space_id != gas_system_memory ) return ;
_ = d . addResource ( . memory , hpet . address , 0x400 ) ;
const regs = hal . mapMmio ( hpet . address , 0x400 , true ) ;
const t0_configuration : * const volatile u64 = @ptrFromInt ( regs + 0x100 ) ;
const route_cap : u32 = @truncate ( t0_configuration .* >> 32 ) ;
if ( hpetGsi ( route_cap ) ) | gsi | _ = d . addResource ( . irq , gsi , 1 ) ;
}
/// ACPI Generic Address Structure address-space ids we care about.
const gas_system_memory : u8 = 0 ;
/// Pick a GSI for the HPET out of its route-capability mask. Prefer an input at or
/// above 16: the low ones overlap the legacy ISA lines (2 = cascaded PIT, 8 = RTC),
/// which the MADT may separately override, whereas 16+ are the free upper inputs on
/// every I/O APIC we care about. Falls back to the lowest bit set if there are none.
fn hpetGsi ( route_cap : u32 ) ? u32 {
if ( route_cap == 0 ) return null ;
var gsi : u32 = 16 ;
while ( gsi < 32 ) : ( gsi += 1 ) {
if ( route_cap & ( @as ( u32 , 1 ) << @intCast ( gsi ) ) != 0 ) return gsi ;
}
return @ctz ( route_cap ) ;
}
// FADT field offsets (bytes from the table start). The FADT grew across ACPI
// revisions, so every field is read through `fadt()` with a length guard rather
// than a fixed struct — an older/shorter FADT simply lacks the later (X_) fields.
const fadt_dsdt = 40 ; // u32
const fadt_smi_cmd = 48 ; // u32 (an I/O port)
const fadt_acpi_enable = 52 ; // u8
const fadt_acpi_disable = 53 ; // u8
const fadt_pm1a_cnt_blk = 64 ; // u32 (I/O port)
const fadt_pm1b_cnt_blk = 68 ; // u32 (I/O port)
const fadt_pm_tmr_blk = 76 ; // u32 (I/O port) — the PM timer counter
const fadt_pm1_cnt_len = 89 ; // u8 (bytes)
const fadt_sci_int = 46 ; // u16 (the SCI's GSI)
const fadt_flags = 112 ; // u32
const fadt_reset_register = 116 ; // GAS (12 bytes)
const fadt_reset_value = 128 ; // u8
const fadt_x_dsdt = 140 ; // u64
const fadt_x_pm1a_cnt_blk = 172 ; // GAS
const fadt_x_pm1b_cnt_blk = 184 ; // GAS
const fadt_x_pm_tmr_blk = 208 ; // GAS
const flag_reset_register_supported = 1 << 10 ;
const flag_tmr_value_ext = 1 << 8 ; // PM timer counter is 32-bit (else 24-bit)
/// FADT -> the power register map (into `power_information`) and the DSDT address,
/// whose bytecode is collected for the ring-3 parse. No AML interpretation here.
fn parseFadt ( header : * const SystemDescriptorTableHeader ) void {
const base : [ * ] align ( 1 ) const u8 = @ptrCast ( header ) ;
const len : usize = header . length ;
const pi = & power_information ;
pi . sci_interrupt = @truncate ( fadt ( u16 , base , len , fadt_sci_int ) orelse 0 ) ;
pi . smi_cmd = @truncate ( fadt ( u32 , base , len , fadt_smi_cmd ) orelse 0 ) ;
pi . acpi_enable = fadt ( u8 , base , len , fadt_acpi_enable ) orelse 0 ;
pi . acpi_disable = fadt ( u8 , base , len , fadt_acpi_disable ) orelse 0 ;
const cnt_width = fadt ( u8 , base , len , fadt_pm1_cnt_len ) orelse 2 ;
pi . pm1a_cnt = readCntRegister ( base , len , fadt_x_pm1a_cnt_blk , fadt_pm1a_cnt_blk , cnt_width ) ;
pi . pm1b_cnt = readCntRegister ( base , len , fadt_x_pm1b_cnt_blk , fadt_pm1b_cnt_blk , cnt_width ) ;
const flags = fadt ( u32 , base , len , fadt_flags ) orelse 0 ;
pi . reset_supported = flags & flag_reset_register_supported != 0 ;
pi . reset = readGas ( base , len , fadt_reset_register ) orelse . { } ;
pi . reset_value = fadt ( u8 , base , len , fadt_reset_value ) orelse 0 ;
// The PM timer — a fixed-rate counter used as a calibration reference when no
// HPET is present. Prefer the 64-bit-capable X_ GAS, fall back to the port.
platform_information . pm_timer = readCntRegister ( base , len , fadt_x_pm_tmr_blk , fadt_pm_tmr_blk , 4 ) ;
platform_information . pm_timer_32bit = flags & flag_tmr_value_ext != 0 ;
var dsdt : u64 = fadt ( u32 , base , len , fadt_dsdt ) orelse 0 ;
if ( fadt ( u64 , base , len , fadt_x_dsdt ) ) | x | {
if ( x != 0 ) dsdt = x ;
}
dsdt_physical = dsdt ;
addAmlBlock ( dsdt ) ;
}
// SPCR field offsets (bytes from the table start).
const spcr_interface_type = 36 ; // u8
const spcr_base_address = 40 ; // GAS (12 bytes)
/// SPCR -> the console UART's address + interface type, so serial can target the
/// firmware's actual debug port instead of assuming legacy COM1.
fn parseSpcr ( header : * const SystemDescriptorTableHeader ) void {
const base : [ * ] align ( 1 ) const u8 = @ptrCast ( header ) ;
const len : usize = header . length ;
const gas = readGas ( base , len , spcr_base_address ) orelse return ;
if ( gas . address == 0 ) return ;
platform_information . spcr_uart = gas ;
platform_information . spcr_kind = fadt ( u8 , base , len , spcr_interface_type ) orelse 0 ;
}
// DMAR remapping-structure layout (Intel VT-d spec §8): the DMAR-specific header is 12
// bytes (host-address-width, flags, 10 reserved), then a list of {type u16, length u16}
// structures. Type 0 is a DRHD (DMA Remapping Hardware Unit Definition), whose 64-bit
// register base sits at offset 8 within it.
const dmar_structures_offset = 48 ; // 36-byte ACPI header + 12-byte DMAR header
const dmar_type_drhd : u16 = 0 ;
const drhd_register_base_offset = 8 ;
/// DMAR -> detect the IOMMU. Find the first DMA-remapping hardware unit, map its
/// register block, and record its version and capabilities. This is *detection only*:
/// it tells the system an IOMMU exists (so `device_claim` on a DMA device could one day
/// be gated by a per-device translation domain), but no domains are programmed yet —
/// enforcement is built with the first DMA driver, which is what there is to protect and
/// test against. See docs/driver-model.md (M16), the honest caveat.
fn parseDmar ( hal : Hal , header : * const SystemDescriptorTableHeader ) void {
const base : [ * ] align ( 1 ) const u8 = @ptrCast ( header ) ;
const total : usize = header . length ;
var off : usize = dmar_structures_offset ;
while ( off + 4 <= total ) {
const kind = fadt ( u16 , base , total , off ) orelse break ;
const length = fadt ( u16 , base , total , off + 2 ) orelse break ;
if ( length < 4 or off + length > total ) break ; // malformed; stop rather than loop
if ( kind == dmar_type_drhd ) {
const register_base = fadt ( u64 , base , total , off + drhd_register_base_offset ) orelse 0 ;
if ( register_base != 0 ) {
const regs = hal . mapMmio ( register_base , abi . page_size , true ) ;
platform_information . iommu_present = true ;
platform_information . iommu_base = register_base ;
platform_information . iommu_version = @as ( * const volatile u32 , @ptrFromInt ( regs + 0x00 ) ) .* ;
platform_information . iommu_capabilities = @as ( * const volatile u64 , @ptrFromInt ( regs + 0x08 ) ) .* ;
return ; // first unit is enough for detection; multi-unit is future
}
}
off += length ;
}
}
// --- helpers ----------------------------------------------------------------
/// Sum `len` bytes; an ACPI table/pointer is valid when the low 8 bits are zero.
fn checksumOk ( bytes : [ * ] const u8 , len : usize ) bool {
var sum : u8 = 0 ;
for ( 0 .. len ) | i | sum +%= bytes [ i ] ;
return sum == 0 ;
}
/// Read a FADT field of type `T` at `off`, or null if the table is too short to
/// contain it (a legal state for older FADT revisions).
fn fadt ( comptime T : type , base : [ * ] align ( 1 ) const u8 , len : usize , off : usize ) ? T {
if ( off + @sizeOf ( T ) > len ) return null ;
return rd ( T , base , off ) ;
}
/// Decode a Generic Address Structure at `off` into a `RegisterAccess`. GAS layout:
/// address_space(u8), bit_width(u8), bit_offset(u8), access_size(u8), address(u64).
fn readGas ( base : [ * ] align ( 1 ) const u8 , len : usize , off : usize ) ? RegisterAccess {
if ( off + 12 > len ) return null ;
const address_space = rd ( u8 , base , off ) ;
const bit_width = rd ( u8 , base , off + 1 ) ;
const address = rd ( u64 , base , off + 4 ) ;
return . {
. mmio = address_space == 0 , // 0 = system memory, 1 = system I/O
. address = address ,
. width = bit_width / 8 ,
} ;
}
/// A PM1 control register: prefer the 64-bit-capable X_ GAS form; fall back to the
/// legacy 32-bit I/O-port field. Width comes from PM1_CNT_LEN either way.
fn readCntRegister ( base : [ * ] align ( 1 ) const u8 , len : usize , xoff : usize , legacy_off : usize , width : u8 ) RegisterAccess {
if ( readGas ( base , len , xoff ) ) | g | {
if ( g . address != 0 ) return . { . mmio = g . mmio , . address = g . address , . width = width } ;
}
const port = fadt ( u32 , base , len , legacy_off ) orelse 0 ;
return . { . mmio = false , . address = port , . width = width } ;
}
/// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer.
/// x86 is little-endian and native, so an unaligned load suffices.
fn rd ( comptime T : type , bytes : [ * ] align ( 1 ) const u8 , off : usize ) T {
const p : * align ( 1 ) const T = @ptrCast ( bytes + off ) ;
return p .* ;
}