//! 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: MADT (CPUs / interrupt //! controllers), MCFG (PCIe ECAM -> PCI enumeration), HPET (timer), and FADT //! (power register map). The DSDT/SSDT bytecode is handed to the `aml` submodule //! only to extract the sleep-state (`_Sx`) values for power management; full AML namespace //! interpretation is a separate, larger subproject. //! //! 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 acpi_ids = @import("acpi-ids"); const parameters = @import("parameters"); const device_model = @import("device-model.zig"); const aml = @import("aml/aml.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; } }; /// Everything the power subsystem needs, extracted from the FADT and the AML /// sleep packages during discovery. Populated by `discover`, read by `power`. pub const PowerInformation = struct { /// 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, /// SLP_TYP values for S5 (soft off) and S3 (suspend), from the AML sleep-state (`_Sx`) /// packages. s5: ?aml.SleepType = null, s3: ?aml.SleepType = null, }; /// 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 = .{}; /// Integrity/diagnostics for the AML parse. `consumed == total` means the parser /// walked every byte of the DSDT/SSDTs without desyncing. pub const AmlStats = struct { nodes: usize = 0, consumed: usize = 0, total: usize = 0, }; pub var aml_stats: AmlStats = .{}; /// The ACPI namespace built from the DSDT/SSDTs, kept for sleep-state (`_Sx`) lookup now and /// device enumeration later. Null until `discover` runs successfully. pub var namespace: ?aml.Namespace = null; /// Physical address of the DSDT the FADT points at, or 0. pub var dsdt_physical: u64 = 0; // AML blocks (DSDT + any SSDTs) collected during the table walk, as physical // address + length of each table's post-header bytecode. Scanned after the walk // for the sleep-state (`_Sx`) packages. 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 and the AML sleep-state (`_Sx`) packages into `power_information` for the power 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 = .{}; platform_information = .{}; aml_stats = .{}; namespace = null; 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); } // Now that the DSDT and any SSDTs are collected, build the AML namespace and // read the sleep types from it. var blocks: [aml_block_physical.len][]const u8 = undefined; for (0..aml_block_count) |i| { blocks[i] = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(aml_block_physical[i])))[0..aml_block_len[i]]; } const active = blocks[0..aml_block_count]; if (aml.parse(device_tree.allocator, active)) |pr| { namespace = pr.namespace; aml_stats = .{ .nodes = namespace.?.nodeCount(), .consumed = pr.consumed, .total = pr.total }; power_information.s5 = aml.sleepState(&namespace.?, 5); power_information.s3 = aml.sleepState(&namespace.?, 3); // Fold the namespace's Device objects into the generic tree. wireAcpiDevices(device_tree, &namespace.?, hal) catch {}; } else |_| { // AML parse failed (e.g. out of memory); power stays best-effort with // whatever the FADT alone provided. } } /// 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)) { 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 for the sleep-state (`_Sx`) scan. 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/m19-m20-plan.md decision 2): 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_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, which /// is queued for the AML sleep-state (`_Sx`) scan. No AML interpretation happens 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.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; } } // --- AML namespace -> generic device tree ----------------------------------- /// The PCI bus context while descending the ACPI namespace: the generic host /// bridge whose children ACPI address (`_ADR`) devices resolve against, and the bus number. const PciContext = struct { bridge: *device_model.Device, bus: u8 }; /// Mirror the ACPI namespace's Device objects into the generic tree, *merging* /// them with the PCI-enumerated nodes: a PCI root bridge (`PNP0A03`/`PNP0A08`) /// folds onto the existing `pci_host_bridge`, and each addressed (`_ADR`) device folds onto /// the matching PCI function (annotating it with the ACPI hardware ID (`_HID`) and nesting the /// ACPI-only children — keyboard, RTC, … — beneath it). Namespace devices with no /// PCI match land under a synthetic `acpi` node. fn wireAcpiDevices(device_tree: *DeviceTree, aml_namespace: *aml.Namespace, hal: Hal) !void { var arena = std.heap.ArenaAllocator.init(device_tree.allocator); defer arena.deinit(); var interpreter = aml.Interpreter.init(aml_namespace, .{ .mapMmio = hal.mapMmio, .pioRead = hal.pioRead, .pioWrite = hal.pioWrite, }, arena.allocator()); const acpi_root = try device_tree.addChild(device_tree.root, .unknown, "acpi"); try mirrorDevices(device_tree, aml_namespace.root, acpi_root, null, &interpreter); } fn mirrorDevices(device_tree: *DeviceTree, node: *aml.Node, parent_device: *device_model.Device, context: ?PciContext, interpreter: *aml.Interpreter) (error{OutOfMemory})!void { var child = node.first_child; while (child) |c| : (child = c.next_sibling) { if (c.kind != .device) { // A scope — the System Bus (\_SB), General Purpose Events (\_GPE), … — // descend without adding a node. try mirrorDevices(device_tree, c, parent_device, context, interpreter); continue; } // Skip devices the firmware reports as not present (via a device-status (`_STA`) method), // along with their whole subtree — per the ACPI rules. if (!devicePresent(interpreter, c)) continue; var mirrored_device: *device_model.Device = undefined; var child_context = context; if (isPciRootNode(c)) { // The PCI root bridge folds onto the generic host bridge. mirrored_device = matchHostBridge(device_tree) orelse try device_tree.addChild(parent_device, .acpi_device, &c.segment); child_context = .{ .bridge = mirrored_device, .bus = 0 }; } else { // An addressed device folds onto its matching PCI function; anything // else becomes a fresh node under the current parent. mirrored_device = pick: { if (context) |pc| { if (readAdr(c)) |adr| { if (findPciNode(pc.bridge, pc.bus, adr)) |pnode| break :pick pnode; } } break :pick try device_tree.addChild(parent_device, .acpi_device, &c.segment); }; } applyHid(mirrored_device, c, interpreter); applyCrs(mirrored_device, c, interpreter); try mirrorDevices(device_tree, c, mirrored_device, child_context, interpreter); } } /// Evaluate a device's status (`_STA`) to decide if it is present. An absent status /// (`_STA`) means present by default; an evaluation failure is treated as present too (we'd /// rather over-report than hide a device we couldn't introspect). fn devicePresent(interpreter: *aml.Interpreter, node: *aml.Node) bool { const sta = aml.Namespace.childOf(node, seg4("_STA")) orelse return true; const obj = interpreter.evaluate(sta, &.{}) catch return true; const status = obj.asInteger() catch return true; return (status & 0x01) != 0; // bit 0 = present } /// The first PCI host bridge in the generic tree (segment 0). fn matchHostBridge(device_tree: *DeviceTree) ?*device_model.Device { var c = device_tree.root.first_child; while (c) |ch| : (c = ch.next_sibling) { if (ch.class == .pci_host_bridge) return ch; } return null; } /// The PCI function node under `bridge` at the address the device's address object /// (`_ADR`) names (device/function on /// `bus`), or null. fn findPciNode(bridge: *device_model.Device, bus: u8, adr: u32) ?*device_model.Device { const device: u16 = @truncate((adr >> 16) & 0x1F); const function: u16 = @truncate(adr & 0x7); const target: u16 = (@as(u16, bus) << 8) | (device << 3) | function; var c = bridge.first_child; while (c) |ch| : (c = ch.next_sibling) { if (ch.ids.pci_bdf) |bdf| { if (bdf == target) return ch; } } return null; } /// A device's address (`_ADR`) — a static integer Name — or null. fn readAdr(node: *aml.Node) ?u32 { const n = aml.Namespace.childOf(node, seg4("_ADR")) orelse return null; if (n.kind != .name) return null; var p: usize = 0; return @truncate(readIntObj(n.value, &p) orelse return null); } /// Whether a `_HID` string names a PCI(e) host bridge. fn isPciRootHid(hid: []const u8) bool { const id = acpi_ids.HardwareId.fromHid(hid) orelse return false; return id == .pci_bus or id == .pci_express_root_bridge; } /// Whether a namespace device is a PCI(e) host bridge. A packed EISA id is decoded /// to its string form first, so both encodings answer through the one registry. fn isPciRootNode(node: *aml.Node) bool { const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return false; if (hid.kind != .name or hid.value.len == 0) return false; switch (hid.value[0]) { 0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => { var p: usize = 0; const n = readIntObj(hid.value, &p) orelse return false; var buffer: [8]u8 = undefined; return isPciRootHid(eisaIdToStr(@truncate(n), &buffer)); }, 0x0D => return isPciRootHid(cstr(hid.value[1..])), else => return false, } } /// Read a device's hardware ID (`_HID`) into the generic device: an integer decodes as an EISA /// id ("PNP0A03"), a string is taken verbatim. Handles both the common static /// Name form and a Method form (evaluated). fn applyHid(device: *device_model.Device, node: *aml.Node, interpreter: *aml.Interpreter) void { const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return; if (hid.kind == .method) { const obj = interpreter.evaluate(hid, &.{}) catch return; switch (obj) { .integer => |n| setEisaHid(device, @truncate(n)), .string => |s| device.setHid(s), else => {}, } return; } if (hid.kind != .name or hid.value.len == 0) return; const v = hid.value; switch (v[0]) { 0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => { var p: usize = 0; const n = readIntObj(v, &p) orelse return; setEisaHid(device, @truncate(n)); }, 0x0D => device.setHid(cstr(v[1..])), // StringPrefix else => {}, } } fn setEisaHid(device: *device_model.Device, id: u32) void { device.ids.acpi_hid = id; var buffer: [8]u8 = undefined; device.setHid(eisaIdToStr(id, &buffer)); } /// Parse a device's current resource settings (`_CRS`). The evaluator handles both the static /// `Buffer` form (a `Name`) and the method form uniformly, yielding the /// ResourceTemplate bytes we then decode. fn applyCrs(device: *device_model.Device, node: *aml.Node, interpreter: *aml.Interpreter) void { const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return; const obj = interpreter.evaluate(crs, &.{}) catch return; const buffer = switch (obj) { .buffer => |b| b, else => return, }; parseResourceTemplate(device, buffer); } /// Walk a ResourceTemplate byte list, adding recognised descriptors as resources. fn parseResourceTemplate(device: *device_model.Device, bytes: []const u8) void { var i: usize = 0; while (i < bytes.len) { const tag = bytes[i]; if (tag & 0x80 == 0) { // Small descriptor: length in low 3 bits, type in bits [6:3]. const len: usize = tag & 0x07; const body = i + 1; if (body + len > bytes.len) break; switch ((tag >> 3) & 0x0F) { 0x04 => if (len >= 2) { // IRQ: a 16-bit mask, one resource per set bit const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8); var b: usize = 0; while (b < 16) : (b += 1) { if (mask & (@as(u16, 1) << @intCast(b)) != 0) _ = device.addResource(.irq, b, 1); } }, 0x08 => if (len >= 7) { // IO port: minimum at +1, length at +6 _ = device.addResource(.io_port, rd16(bytes, body + 1), bytes[body + 6]); }, 0x09 => if (len >= 3) { // Fixed IO: base at +0, length at +2 _ = device.addResource(.io_port, rd16(bytes, body), bytes[body + 2]); }, 0x0F => break, // EndTag else => {}, } i = body + len; } else { // Large descriptor: 16-bit length follows the tag. if (i + 3 > bytes.len) break; const len: usize = @intCast(rd16(bytes, i + 1)); const body = i + 3; if (body + len > bytes.len) break; switch (tag) { 0x85 => if (len >= 17) { // Memory32: minimum at +1, length at +13 _ = device.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 13)); }, 0x86 => if (len >= 9) { // Memory32Fixed: base at +1, length at +5 _ = device.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 5)); }, 0x89 => if (len >= 2) { // Extended IRQ: count at +1, then count u32s const count = bytes[body + 1]; var k: usize = 0; while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) { _ = device.addResource(.irq, rd32(bytes, body + 2 + k * 4), 1); } }, 0x87, 0x88, 0x8A => parseAddressSpace(device, tag, bytes[body .. body + len]), else => {}, } i = body + len; } } } /// Word/DWord/QWord address-space descriptors: resource type at [0], then /// granularity/minimum/maximum/translation/length, each of width `w`. fn parseAddressSpace(device: *device_model.Device, tag: u8, body: []const u8) void { const w: usize = switch (tag) { 0x88 => 2, // Word 0x87 => 4, // DWord else => 8, // QWord (0x8A) }; if (body.len < 3 + 5 * w) return; const minimum = readN(body, 3 + w, w); const length = readN(body, 3 + 4 * w, w); const kind: device_model.ResourceKind = switch (body[0]) { 0 => .memory, 1 => .io_port, else => .bus_range, }; _ = device.addResource(kind, minimum, length); } /// Decode a packed EISA id into its 7-char string (e.g. 0x030AD041 -> "PNP0A03"). fn eisaIdToStr(id: u32, buffer: *[8]u8) []const u8 { const b0: u16 = @intCast(id & 0xFF); const b1: u16 = @intCast((id >> 8) & 0xFF); const b2: u8 = @truncate(id >> 16); const b3: u8 = @truncate(id >> 24); const mfg = (b0 << 8) | b1; buffer[0] = '@' + @as(u8, @intCast((mfg >> 10) & 0x1F)); buffer[1] = '@' + @as(u8, @intCast((mfg >> 5) & 0x1F)); buffer[2] = '@' + @as(u8, @intCast(mfg & 0x1F)); buffer[3] = hexDigit((b2 >> 4) & 0xF); buffer[4] = hexDigit(b2 & 0xF); buffer[5] = hexDigit((b3 >> 4) & 0xF); buffer[6] = hexDigit(b3 & 0xF); return buffer[0..7]; } fn hexDigit(n: u8) u8 { return if (n < 10) '0' + n else 'A' + (n - 10); } fn seg4(comptime s: *const [4:0]u8) [4]u8 { return s[0..4].*; } fn cstr(bytes: []const u8) []const u8 { const index = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len; return bytes[0..index]; } const PkgLen = struct { value: usize, size: usize }; fn packageLength(bytes: []const u8, p: usize) ?PkgLen { if (p >= bytes.len) return null; const lead = bytes[p]; const follow: usize = lead >> 6; if (p + 1 + follow > bytes.len) return null; if (follow == 0) return .{ .value = lead & 0x3F, .size = 1 }; var value: usize = lead & 0x0F; var i: usize = 0; while (i < follow) : (i += 1) value |= @as(usize, bytes[p + 1 + i]) << @intCast(4 + i * 8); return .{ .value = value, .size = 1 + follow }; } /// Read an AML integer object at `p`, advancing `p` past it. fn readIntObj(bytes: []const u8, p: *usize) ?u64 { if (p.* >= bytes.len) return null; const opcode = bytes[p.*]; p.* += 1; return switch (opcode) { 0x00 => 0, 0x01 => 1, 0xFF => 0xFF, 0x0A => readLE(bytes, p, 1), 0x0B => readLE(bytes, p, 2), 0x0C => readLE(bytes, p, 4), 0x0E => readLE(bytes, p, 8), else => null, }; } fn readLE(bytes: []const u8, p: *usize, n: usize) ?u64 { if (p.* + n > bytes.len) return null; const v = readN(bytes, p.*, n); p.* += n; return v; } fn readN(bytes: []const u8, off: usize, n: usize) u64 { var v: u64 = 0; var k: usize = 0; while (k < n and off + k < bytes.len) : (k += 1) v |= @as(u64, bytes[off + k]) << @intCast(k * 8); return v; } fn rd16(bytes: []const u8, off: usize) u64 { return readN(bytes, off, 2); } fn rd32(bytes: []const u8, off: usize) u64 { return readN(bytes, off, 4); } // --- 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 }; } /// The mapped configuration space of one PCI function (its 4 KiB ECAM page). Mapped /// writable so BAR sizing can probe it; reads and writes both go through here. /// 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.*; } // --- tests ------------------------------------------------------------------ test "eisaIdToStr decodes a packed EISA id" { var buffer: [8]u8 = undefined; // 0x030AD041 is the well-known encoding of "PNP0A03" (PCI root bridge). try std.testing.expectEqualStrings("PNP0A03", eisaIdToStr(0x030AD041, &buffer)); } test "parseResourceTemplate extracts IO, IRQ, and fixed memory" { // ResourceTemplate { IO(minimum 0x60, len 8), IRQ(4), Memory32Fixed(0xFED00000, 0x1000) } const runtime = [_]u8{ 0x47, 0x01, 0x60, 0x00, 0x60, 0x00, 0x01, 0x08, // small IO descriptor 0x22, 0x10, 0x00, // small IRQ descriptor (mask bit 4 -> IRQ 4) 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0xD0, 0xFE, 0x00, 0x10, 0x00, 0x00, // Memory32Fixed 0x79, 0x00, // EndTag }; var device = device_model.Device{}; parseResourceTemplate(&device, &runtime); try std.testing.expectEqual(@as(u8, 3), device.resource_count); const rs = device.resources[0..device.resource_count]; try std.testing.expectEqual(device_model.ResourceKind.io_port, rs[0].kind); try std.testing.expectEqual(@as(u64, 0x60), rs[0].start); try std.testing.expectEqual(@as(u64, 8), rs[0].len); try std.testing.expectEqual(device_model.ResourceKind.irq, rs[1].kind); try std.testing.expectEqual(@as(u64, 4), rs[1].start); try std.testing.expectEqual(device_model.ResourceKind.memory, rs[2].kind); try std.testing.expectEqual(@as(u64, 0xFED00000), rs[2].start); try std.testing.expectEqual(@as(u64, 0x1000), rs[2].len); }