refactor kernel to use device platform discovery

This commit is contained in:
Daniel Samson
2026-07-08 10:06:46 +01:00
parent 2ee898a91e
commit 7d3417fe86
9 changed files with 572 additions and 43 deletions
+99
View File
@@ -56,6 +56,41 @@ pub const PowerInfo = struct {
/// Filled in by `discover`; the power service reads it to reboot/shutdown.
pub var power_info: PowerInfo = .{};
/// 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 arch 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 PlatformInfo = 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: RegAccess = .{},
/// true = 32-bit PM timer counter, false = 24-bit (FADT flag TMR_VAL_EXT).
pm_timer_32bit: bool = false,
/// The console UART the firmware points at (SPCR), if any — MMIO or I/O port.
spcr_uart: ?RegAccess = 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,
};
/// Filled in by `discover`; the arch layer reads it during bring-up.
pub var platform_info: PlatformInfo = .{};
/// Integrity/diagnostics for the AML parse. `consumed == total` means the parser
/// walked every byte of the DSDT/SSDTs without desyncing.
pub const AmlStats = struct {
@@ -167,6 +202,8 @@ const SLIT: [4]u8 = "SLIT".*;
const SRAT: [4]u8 = "SRAT".*;
/// Secondary System Description Table (SSDT)
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".*;
@@ -222,6 +259,22 @@ const MadtIoApic = extern struct {
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 {
@@ -286,6 +339,7 @@ pub fn discover(rsdp_phys: u64, dt: *DeviceTree, hal: Hal) !void {
// Start clean so a re-run doesn't accumulate stale state.
power_info = .{};
platform_info = .{};
aml_stats = .{};
namespace = null;
dsdt_phys = 0;
@@ -352,6 +406,8 @@ fn handleTable(dt: *DeviceTree, hal: Hal, sdt_phys: u64) !void {
try parseHpet(dt, 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, &SSDT)) {
// Secondary namespace bytecode — collect for the sleep-state (`_Sx`) scan.
addAmlBlock(sdt_phys);
@@ -361,10 +417,15 @@ fn handleTable(dt: *DeviceTree, hal: Hal, sdt_phys: u64) !void {
/// MADT -> one processor node per Local APIC, one interrupt_controller per I/O APIC.
fn parseMadt(dt: *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_info.lapic_base = madt.local_apic_address;
platform_info.pic_present = madt.flags & 1 != 0;
var off: usize = @sizeOf(Madt);
while (off + @sizeOf(MadtRecordHeader) <= total) {
const rec: *const MadtRecordHeader = @ptrCast(base + off);
@@ -389,6 +450,21 @@ fn parseMadt(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void
// 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_info.override_count < platform_info.overrides.len) {
platform_info.overrides[platform_info.override_count] = .{
.source = iso.source,
.gsi = iso.gsi,
.flags = iso.flags,
};
platform_info.override_count += 1;
}
},
5 => {
const ovr: *const MadtLapicOverride = @ptrCast(base + off);
platform_info.lapic_base = ovr.address;
},
else => {},
}
off += rec.length;
@@ -523,6 +599,7 @@ 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_reg = 116; // GAS (12 bytes)
@@ -530,7 +607,9 @@ 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_reg_supported = 1 << 10;
const flag_tmr_val_ext = 1 << 8; // PM timer counter is 32-bit (else 24-bit)
/// FADT -> the power register map (into `power_info`) and the DSDT address, which
/// is queued for the AML sleep-state (`_Sx`) scan. No AML interpretation happens here.
@@ -552,6 +631,11 @@ fn parseFadt(header: *const SystemDescriptorTableHeader) void {
pi.reset = readGas(base, len, fadt_reset_reg) 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_info.pm_timer = readCntReg(base, len, fadt_x_pm_tmr_blk, fadt_pm_tmr_blk, 4);
platform_info.pm_timer_32bit = flags & flag_tmr_val_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;
@@ -560,6 +644,21 @@ fn parseFadt(header: *const SystemDescriptorTableHeader) void {
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_info.spcr_uart = gas;
platform_info.spcr_kind = fadt(u8, base, len, spcr_interface_type) orelse 0;
}
// --- AML namespace -> generic device tree -----------------------------------
/// The PCI bus context while descending the ACPI namespace: the generic host
+23
View File
@@ -131,6 +131,14 @@ pub const Device = struct {
self.resource_count += 1;
return true;
}
/// The device's first resource of `kind`, or null — e.g. a timer's MMIO base.
pub fn firstResource(self: *const Device, kind: ResourceKind) ?Resource {
for (self.resources[0..self.resource_count]) |r| {
if (r.kind == kind) return r;
}
return null;
}
};
/// Owns the discovered device tree and the allocator its nodes came from.
@@ -146,6 +154,12 @@ pub const DeviceTree = struct {
return .{ .allocator = allocator, .root = root };
}
/// The first device of `class` anywhere in the tree (depth-first), or null —
/// how the kernel pulls e.g. the HPET or IOAPIC MMIO base out of discovery.
pub fn firstOfClass(self: *const DeviceTree, class: DeviceClass) ?*Device {
return firstOfClassIn(self.root, class);
}
/// Allocate a device and append it under `parent`, returning it so the caller
/// can attach resources/ids. Appended at the tail so a dump reads in the order
/// devices were discovered.
@@ -176,6 +190,15 @@ pub const DeviceTree = struct {
}
};
fn firstOfClassIn(node: *Device, class: DeviceClass) ?*Device {
var child = node.first_child;
while (child) |c| : (child = c.next_sibling) {
if (c.class == class) return c;
if (firstOfClassIn(c, class)) |found| return found;
}
return null;
}
fn dumpNode(dev: *const Device, depth: usize, emit: *const fn ([]const u8) void) void {
const indent = @min(depth * 2, 40);
+9
View File
@@ -21,12 +21,21 @@ pub const DeviceClass = device.DeviceClass;
pub const Hal = device.Hal;
pub const PowerInfo = acpi.PowerInfo;
pub const AmlStats = acpi.AmlStats;
pub const PlatformInfo = acpi.PlatformInfo;
pub const RegAccess = acpi.RegAccess;
pub const IsoEntry = acpi.IsoEntry;
/// The register map + sleep types discovery extracted, for logging/diagnostics.
pub fn powerInfo() PowerInfo {
return acpi.power_info;
}
/// The scalar firmware facts the arch layer needs to avoid legacy assumptions
/// (8259 presence, LAPIC base, PM timer, SPCR UART, IRQ overrides).
pub fn platformInfo() PlatformInfo {
return acpi.platform_info;
}
/// AML parse integrity/diagnostics (namespace node count, bytes consumed).
pub fn amlStats() AmlStats {
return acpi.aml_stats;