add device platform module with ACPI support

This commit is contained in:
Daniel Samson
2026-07-08 09:23:41 +01:00
parent 53a33a7332
commit 2ee898a91e
21 changed files with 3545 additions and 5 deletions
+23
View File
@@ -11,6 +11,7 @@ const idt = @import("idt.zig");
const paging = @import("paging.zig");
const serial = @import("serial.zig");
const apic = @import("apic.zig");
const io = @import("io.zig");
/// The saved register/trap frame passed to a fault handler.
pub const CpuState = idt.CpuState;
@@ -177,6 +178,28 @@ pub fn vectorName(vector: u64) []const u8 {
return idt.vectorName(vector);
}
/// Read `width` bytes (1/2/4) from an I/O port. The generic device layer drives
/// ACPI registers through this rather than naming x86 port instructions; on an
/// MMIO-only architecture this would be implemented differently.
pub fn pioRead(width: u8, port: u16) u32 {
return switch (width) {
1 => io.inb(port),
2 => io.inw(port),
4 => io.inl(port),
else => 0,
};
}
/// Write `width` bytes (1/2/4) to an I/O port.
pub fn pioWrite(width: u8, port: u16, value: u32) void {
switch (width) {
1 => io.outb(port, @truncate(value)),
2 => io.outw(port, @truncate(value)),
4 => io.outl(port, value),
else => {},
}
}
/// CR2 holds the faulting linear address after a page fault (#PF, vector 14).
pub fn readCr2() u64 {
return asm volatile ("mov %%cr2, %[out]"
+30
View File
@@ -16,6 +16,36 @@ pub fn inb(port: u16) u8 {
);
}
pub fn outw(port: u16, value: u16) void {
asm volatile ("outw %[value], %[port]"
:
: [value] "{ax}" (value),
[port] "{dx}" (port),
);
}
pub fn inw(port: u16) u16 {
return asm volatile ("inw %[port], %[value]"
: [value] "={ax}" (-> u16),
: [port] "{dx}" (port),
);
}
pub fn outl(port: u16, value: u32) void {
asm volatile ("outl %[value], %[port]"
:
: [value] "{eax}" (value),
[port] "{dx}" (port),
);
}
pub fn inl(port: u16) u32 {
return asm volatile ("inl %[port], %[value]"
: [value] "={eax}" (-> u32),
: [port] "{dx}" (port),
);
}
/// Read a model-specific register (returns edx:eax combined).
pub fn rdmsr(msr: u32) u64 {
var low: u32 = undefined;
+33
View File
@@ -5,6 +5,7 @@ const console = @import("console.zig");
const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const scheduler = @import("scheduler.zig");
const platform = @import("platform");
const tests = @import("tests.zig");
const build_options = @import("build_options");
const BootInfo = danos.BootInfo;
@@ -102,6 +103,38 @@ fn kmain(boot_info: *const BootInfo) noreturn {
const s2 = pmm.stats();
serial0.debugPrint(" Kernel footprint: {d} KiB\n", .{kib(s1.free_frames - s2.free_frames)});
// Enumerate hardware from the firmware tables (ACPI here) into a generic
// device tree, then list it. Discovery walks ACPI memory directly (identity-
// mapped) and maps PCIe config space on demand via the VMM. A failure here is
// not fatal yet — log it and carry on.
const hal = platform.Hal{
.mapMmio = arch.mapPage,
.pioRead = arch.pioRead,
.pioWrite = arch.pioWrite,
};
if (platform.discover(boot_info, heap.allocator(), hal)) |devtree| {
var dt = devtree;
serial0.debugWrite("\ndanos: device discovery online\n");
dt.dump(console.SerialConsole.debugWrite);
// Power register map extracted from the FADT + AML, for confidence it parsed.
const pw = platform.powerInfo();
serial0.debugWrite("danos: power\n");
serial0.debugPrint(" pm1a_cnt : {s} 0x{x} (width {d})\n", .{ if (pw.pm1a_cnt.mmio) "mmio" else "io", pw.pm1a_cnt.address, pw.pm1a_cnt.width });
if (pw.s5) |s| {
serial0.debugPrint(" S5 slp_typ : a={d} b={d}\n", .{ s.slp_typ_a, s.slp_typ_b });
} else {
serial0.debugWrite(" S5 slp_typ : (not found)\n");
}
serial0.debugPrint(" reset : supported={} {s} 0x{x} val 0x{x}\n", .{ pw.reset_supported, if (pw.reset.mmio) "mmio" else "io", pw.reset.address, pw.reset_value });
// AML namespace parse integrity: consumed should equal total.
const am = platform.amlStats();
serial0.debugPrint(" aml : {d} namespace nodes, parsed {d}/{d} bytes\n", .{ am.nodes, am.consumed, am.total });
} else |err| {
serial0.debugPrint("\ndanos: device discovery failed: {s}\n", .{@errorName(err)});
}
// Register the current context as the first task before enabling preemption.
scheduler.init(4);
serial0.debugWrite("\ndanos: scheduler online\n");
+29
View File
@@ -12,6 +12,7 @@
const std = @import("std");
const danos = @import("danos");
const arch = @import("arch");
const platform = @import("platform");
const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const sched = @import("scheduler.zig");
@@ -77,11 +78,39 @@ pub fn run(case: []const u8, boot_info: *const BootInfo) void {
faultNoExecute();
} else if (eql(case, "fault-null")) {
faultNull();
} else if (eql(case, "poweroff")) {
powerTest(.off);
} else if (eql(case, "reboot")) {
powerTest(.reboot);
} else {
log("DANOS-TEST-RESULT: FAIL (unknown case '{s}')\n", .{case});
}
}
fn platformHal() platform.Hal {
return .{
.mapMmio = arch.mapPage,
.pioRead = arch.pioRead,
.pioWrite = arch.pioWrite,
};
}
/// Drive an ACPI power transition. On success the machine powers off or resets,
/// so QEMU exits — the harness observes the process exit. If control returns, the
/// transition failed and we emit a FAIL result.
fn powerTest(comptime action: enum { off, reboot }) void {
const name = if (action == .off) "poweroff" else "reboot";
log("DANOS-TEST-BEGIN: {s}\n", .{name});
const hal = platformHal();
log("DANOS-POWER: attempting {s}\n", .{name});
switch (action) {
.off => platform.shutdown(hal),
.reboot => platform.reboot(hal),
}
check("power transition took effect", false);
result();
}
const BootInfo = danos.BootInfo;
fn eql(a: []const u8, b: []const u8) bool {