acpi: stop parsing AML in the kernel; power management is userspace's
Kernel discovery interpreted the whole DSDT/SSDTs (~0.5 MB -> ~9700 nodes) solely to extract the _S5 sleep type for a kernel-side soft-off — ~1-2s of work on the single-core, pre-scheduler critical path, with nothing to overlap it. The ring-3 acpi service already parses the same blobs and owns S5 end-to-end (reads pm1a_cnt from the FADT, writes SLP_TYP itself; orderly-shutdown tests it). So drop the kernel parse entirely. - all *static*-table parsing stays (MADT/HPET/FADT/MCFG): CPUs, timers, PCIe, and the power register map are still detected from ACPI, not legacy/compat addresses (timer still calibrates via HPET/PM-timer/CPUID, PIT only as last resort) - kernel keeps reboot (FADT reset register + legacy fallbacks — no AML); soft-off is userspace-only now - removed PowerInformation.s5/s3, the kernel AML namespace, aml_stats, amlDeviceCount, and the aml import; power.shutdown/enable/sleepS3 gone - tests: poweroff case retired (S5 covered by orderly-shutdown); discovery drops its S5/AML checks; acpi-parse self-verifies against a device-count floor since there's no kernel count to match
This commit is contained in:
+28
-62
@@ -3,11 +3,13 @@
|
|||||||
//! Walks the ACPI tables the firmware left in memory (starting from the RSDP the
|
//! 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
|
//! bootloader handed us) and translates the static tables into the generic
|
||||||
//! `device` model, so the kernel enumerates hardware without knowing ACPI is the
|
//! `device` model, so the kernel enumerates hardware without knowing ACPI is the
|
||||||
//! source. This is deliberately the *static-table* path: MADT (CPUs / interrupt
|
//! source. This is deliberately the *static-table* path, and **only** that: MADT
|
||||||
//! controllers), MCFG (PCIe ECAM -> PCI enumeration), HPET (timer), and FADT
|
//! (CPUs / interrupt controllers), MCFG (PCIe ECAM -> PCI enumeration), HPET
|
||||||
//! (power register map). The DSDT/SSDT bytecode is handed to the `aml` submodule
|
//! (timer), and FADT (power register map). The DSDT/SSDT bytecode is *not*
|
||||||
//! only to extract the sleep-state (`_Sx`) values for power management; full AML namespace
|
//! interpreted here — the kernel collects the blobs and publishes them on the
|
||||||
//! interpretation is a separate, larger subproject.
|
//! 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
|
//! ACPI tables live in `.acpi_tables` / `.acpi_nvs` memory, which the kernel
|
||||||
//! identity-maps, so table addresses are dereferenced directly. PCIe ECAM is MMIO
|
//! identity-maps, so table addresses are dereferenced directly. PCIe ECAM is MMIO
|
||||||
@@ -19,7 +21,6 @@ const boot_handoff = @import("boot-handoff");
|
|||||||
const abi = @import("abi");
|
const abi = @import("abi");
|
||||||
const parameters = @import("parameters");
|
const parameters = @import("parameters");
|
||||||
const device_model = @import("device-model.zig");
|
const device_model = @import("device-model.zig");
|
||||||
const aml = @import("aml/aml.zig");
|
|
||||||
const DeviceTree = device_model.DeviceTree;
|
const DeviceTree = device_model.DeviceTree;
|
||||||
const Hal = device_model.Hal;
|
const Hal = device_model.Hal;
|
||||||
|
|
||||||
@@ -37,8 +38,11 @@ pub const RegisterAccess = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Everything the power subsystem needs, extracted from the FADT and the AML
|
/// The power register map, extracted from the FADT during discovery. Populated by
|
||||||
/// sleep packages during discovery. Populated by `discover`, read by `power`.
|
/// `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 {
|
pub const PowerInformation = struct {
|
||||||
/// The System Control Interrupt's GSI (FADT SCI_INT) — the line ACPI events
|
/// 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.
|
/// (power button, GPEs) arrive on. Published to the acpi service for M21.
|
||||||
@@ -54,10 +58,6 @@ pub const PowerInformation = struct {
|
|||||||
reset: RegisterAccess = .{},
|
reset: RegisterAccess = .{},
|
||||||
reset_value: u8 = 0,
|
reset_value: u8 = 0,
|
||||||
reset_supported: bool = false,
|
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.
|
/// Filled in by `discover`; the power service reads it to reboot/shutdown.
|
||||||
@@ -141,19 +141,6 @@ const maximum_cpus = parameters.maximum_cpus;
|
|||||||
/// Filled in by `discover` (from the MADT); SMP bring-up reads it to wake the APs.
|
/// Filled in by `discover` (from the MADT); SMP bring-up reads it to wake the APs.
|
||||||
pub var cpu_information: CpuInformation = .{};
|
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.
|
/// Physical address of the DSDT the FADT points at, or 0.
|
||||||
pub var dsdt_physical: u64 = 0;
|
pub var dsdt_physical: u64 = 0;
|
||||||
|
|
||||||
@@ -165,8 +152,9 @@ var fadt_physical: u64 = 0;
|
|||||||
var fadt_length: u64 = 0;
|
var fadt_length: u64 = 0;
|
||||||
|
|
||||||
// AML blocks (DSDT + any SSDTs) collected during the table walk, as physical
|
// 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
|
// address + length of each table's post-header bytecode. The kernel does not
|
||||||
// for the sleep-state (`_Sx`) packages.
|
// 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_physical: [32]u64 = undefined;
|
||||||
var aml_block_len: [32]usize = undefined;
|
var aml_block_len: [32]usize = undefined;
|
||||||
var aml_block_count: usize = 0;
|
var aml_block_count: usize = 0;
|
||||||
@@ -373,7 +361,7 @@ const Hpet = extern struct {
|
|||||||
|
|
||||||
/// Discover hardware from the ACPI tables rooted at `rsdp_physical` and populate
|
/// 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
|
/// `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.
|
/// 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 {
|
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;
|
if (rsdp_physical == 0) return error.NoRsdp;
|
||||||
boot_memory_regions = memory_regions;
|
boot_memory_regions = memory_regions;
|
||||||
@@ -383,8 +371,6 @@ pub fn discover(rsdp_physical: u64, memory_regions: []const boot_handoff.MemoryR
|
|||||||
fadt_physical = 0;
|
fadt_physical = 0;
|
||||||
fadt_length = 0;
|
fadt_length = 0;
|
||||||
platform_information = .{};
|
platform_information = .{};
|
||||||
aml_stats = .{};
|
|
||||||
namespace = null;
|
|
||||||
dsdt_physical = 0;
|
dsdt_physical = 0;
|
||||||
aml_block_count = 0;
|
aml_block_count = 0;
|
||||||
|
|
||||||
@@ -401,34 +387,20 @@ pub fn discover(rsdp_physical: u64, memory_regions: []const boot_handoff.MemoryR
|
|||||||
try walkRoot(u32, rsdp.root_system_description_table_address, device_tree, hal);
|
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
|
// The kernel does **not** interpret the DSDT/SSDTs. Static-table discovery
|
||||||
// read the sleep types from it.
|
// above (MADT/HPET/FADT/MCFG) is all the kernel needs — CPUs, timers, PCIe,
|
||||||
var blocks: [aml_block_physical.len][]const u8 = undefined;
|
// and the power register map. The AML bytecode (device enumeration and the
|
||||||
for (0..aml_block_count) |i| {
|
// sleep-state `_Sx` values for soft-off) is entirely the ring-3 acpi service's
|
||||||
blocks[i] = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(aml_block_physical[i])))[0..aml_block_len[i]];
|
// 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
|
||||||
const active = blocks[0..aml_block_count];
|
// AML in the kernel keeps boot latency off the critical, single-core path.
|
||||||
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);
|
|
||||||
// The namespace's Device objects are no longer folded into the kernel
|
|
||||||
// tree (M20.3): the ring-3 acpi service claims the acpi-tables node
|
|
||||||
// (published below), re-parses the same blobs, and registers + reports
|
|
||||||
// the _HID devices itself. The kernel keeps the namespace only for the
|
|
||||||
// \_S5 sleep type above.
|
|
||||||
} else |_| {
|
|
||||||
// AML parse failed (e.g. out of memory); power stays best-effort with
|
|
||||||
// whatever the FADT alone provided.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish the acpi-tables node (docs/discovery.md): the AML blobs as
|
// 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
|
// 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
|
// io_port grant for the OperationRegion access its interpreter needs, and
|
||||||
// the SCI for the events track (M21). Exactly one node, one trusted
|
// the SCI for the events track (M21). Exactly one node, one trusted
|
||||||
// claimant. Kept even when the kernel-side device building (above) retires
|
// claimant — the sole path by which AML (devices + soft-off) reaches ring 3,
|
||||||
// in M20.3 — the kernel still owns the *static* tables and \_S5.
|
// now that the kernel keeps only the *static* tables for itself.
|
||||||
publishAcpiTablesNode(device_tree) catch {};
|
publishAcpiTablesNode(device_tree) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,12 +433,6 @@ fn publishAcpiTablesNode(device_tree: *DeviceTree) !void {
|
|||||||
if (fadt_physical != 0) _ = node.addResource(.memory, fadt_physical, fadt_length);
|
if (fadt_physical != 0) _ = node.addResource(.memory, fadt_physical, fadt_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of Device objects in the namespace built during discovery, or 0.
|
|
||||||
pub fn amlDeviceCount() usize {
|
|
||||||
if (namespace) |*ns| return aml.deviceCount(ns);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk the RSDT (Entry = u32) or XSDT (Entry = u64): validate it, then dispatch
|
/// 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.
|
/// 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 {
|
fn walkRoot(comptime Entry: type, root_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
|
||||||
@@ -502,7 +468,7 @@ fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
|
|||||||
} else if (std.mem.eql(u8, &sig, &DMAR)) {
|
} else if (std.mem.eql(u8, &sig, &DMAR)) {
|
||||||
parseDmar(hal, header);
|
parseDmar(hal, header);
|
||||||
} else if (std.mem.eql(u8, &sig, &SSDT)) {
|
} else if (std.mem.eql(u8, &sig, &SSDT)) {
|
||||||
// Secondary namespace bytecode — collect for the sleep-state (`_Sx`) scan.
|
// Secondary namespace bytecode — collect it to publish for the ring-3 parse.
|
||||||
addAmlBlock(sdt_physical);
|
addAmlBlock(sdt_physical);
|
||||||
}
|
}
|
||||||
// Any other signature is recognised but left opaque for now.
|
// Any other signature is recognised but left opaque for now.
|
||||||
@@ -740,8 +706,8 @@ const fadt_x_pm_tmr_blk = 208; // GAS
|
|||||||
const flag_reset_register_supported = 1 << 10;
|
const flag_reset_register_supported = 1 << 10;
|
||||||
const flag_tmr_value_ext = 1 << 8; // PM timer counter is 32-bit (else 24-bit)
|
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
|
/// FADT -> the power register map (into `power_information`) and the DSDT address,
|
||||||
/// is queued for the AML sleep-state (`_Sx`) scan. No AML interpretation happens here.
|
/// whose bytecode is collected for the ring-3 parse. No AML interpretation here.
|
||||||
fn parseFadt(header: *const SystemDescriptorTableHeader) void {
|
fn parseFadt(header: *const SystemDescriptorTableHeader) void {
|
||||||
const base: [*]align(1) const u8 = @ptrCast(header);
|
const base: [*]align(1) const u8 = @ptrCast(header);
|
||||||
const len: usize = header.length;
|
const len: usize = header.length;
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ pub const Resource = device_model.Resource;
|
|||||||
pub const ResourceKind = device_model.ResourceKind;
|
pub const ResourceKind = device_model.ResourceKind;
|
||||||
pub const Hal = device_model.Hal;
|
pub const Hal = device_model.Hal;
|
||||||
pub const PowerInformation = acpi.PowerInformation;
|
pub const PowerInformation = acpi.PowerInformation;
|
||||||
pub const AmlStats = acpi.AmlStats;
|
|
||||||
pub const PlatformInformation = acpi.PlatformInformation;
|
pub const PlatformInformation = acpi.PlatformInformation;
|
||||||
pub const RegisterAccess = acpi.RegisterAccess;
|
pub const RegisterAccess = acpi.RegisterAccess;
|
||||||
pub const IsoEntry = acpi.IsoEntry;
|
pub const IsoEntry = acpi.IsoEntry;
|
||||||
pub const Cpu = acpi.Cpu;
|
pub const Cpu = acpi.Cpu;
|
||||||
|
|
||||||
/// The register map + sleep types discovery extracted, for logging/diagnostics.
|
/// The FADT power register map discovery extracted (PM1 control, reset register),
|
||||||
|
/// for kernel reboot and diagnostics. Sleep-state values are userspace's (S5 is
|
||||||
|
/// owned by the ring-3 acpi service), so they are not here.
|
||||||
pub fn powerInformation() PowerInformation {
|
pub fn powerInformation() PowerInformation {
|
||||||
return acpi.power_information;
|
return acpi.power_information;
|
||||||
}
|
}
|
||||||
@@ -39,18 +40,6 @@ pub fn platformInformation() PlatformInformation {
|
|||||||
return acpi.platform_information;
|
return acpi.platform_information;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AML parse integrity/diagnostics (namespace node count, bytes consumed).
|
|
||||||
/// The number of Device objects in the kernel's own AML namespace, or 0 if the
|
|
||||||
/// parse produced none — the `acpi-parse` test compares the ring-3 service's
|
|
||||||
/// count against this.
|
|
||||||
pub fn amlDeviceCount() usize {
|
|
||||||
return acpi.amlDeviceCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn amlStats() AmlStats {
|
|
||||||
return acpi.aml_stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The usable logical processors discovered during enumeration — one entry per
|
/// The usable logical processors discovered during enumeration — one entry per
|
||||||
/// core danos may schedule on, each carrying the Local APIC ID an SMP wake targets.
|
/// core danos may schedule on, each carrying the Local APIC ID an SMP wake targets.
|
||||||
/// `len` is the hardware's degree of parallelism: how many tasks *could* run at the
|
/// `len` is the hardware's degree of parallelism: how many tasks *could* run at the
|
||||||
@@ -92,12 +81,8 @@ pub fn discover(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Restart the machine. Never returns on success; returns only if no reset method
|
/// Restart the machine. Never returns on success; returns only if no reset method
|
||||||
/// worked (extremely unlikely). Backend-agnostic entry the kernel calls.
|
/// worked (extremely unlikely). Backend-agnostic entry the kernel calls. Soft-off
|
||||||
|
/// (S5) is not a kernel operation — the ring-3 acpi service owns it (docs/power.md).
|
||||||
pub fn reboot(hal: Hal) void {
|
pub fn reboot(hal: Hal) void {
|
||||||
power.reboot(hal);
|
power.reboot(hal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Power the machine off (ACPI S5). Never returns on success.
|
|
||||||
pub fn shutdown(hal: Hal) void {
|
|
||||||
power.shutdown(hal);
|
|
||||||
}
|
|
||||||
|
|||||||
+10
-63
@@ -1,34 +1,17 @@
|
|||||||
//! Machine power control: enter ACPI mode, reboot, and power off (ACPI S5).
|
//! Machine reboot: restart via the FADT reset register, with legacy fallbacks.
|
||||||
//!
|
//!
|
||||||
//! Built entirely on the register map `acpi` extracted from the FADT plus the
|
//! Built on the register map `acpi` extracted from the FADT, driven through the
|
||||||
//! sleep-state (`_Sx`) types the AML submodule pulled from the DSDT, driven through the
|
//! injected `Hal` (port I/O and MMIO). Soft-off (ACPI S5) and suspend (S3) are
|
||||||
//! injected `Hal` (port I/O and MMIO). Nothing here is x86-specific beyond the
|
//! **not** here: they need the AML sleep-state (`_Sx`) values, which the kernel no
|
||||||
//! well-known legacy reset fallbacks, which are guarded behind the ACPI methods.
|
//! longer parses — the ring-3 acpi service owns power management (it re-parses the
|
||||||
//!
|
//! blobs and writes the PM1 control register itself). See docs/power.md. Reboot
|
||||||
//! S3 (suspend-to-RAM) is stubbed: it needs a wake trampoline and device
|
//! stays in the kernel because it needs no AML — only the FADT reset register and
|
||||||
//! re-initialisation, a milestone of its own.
|
//! the well-known legacy fallbacks — so it survives as a last-resort restart.
|
||||||
|
|
||||||
const acpi = @import("acpi.zig");
|
const acpi = @import("acpi.zig");
|
||||||
const device_model = @import("device-model.zig");
|
const device_model = @import("device-model.zig");
|
||||||
const Hal = device_model.Hal;
|
const Hal = device_model.Hal;
|
||||||
|
|
||||||
const slp_en: u32 = 1 << 13; // SLP_EN: writing 1 triggers the sleep transition
|
|
||||||
const sci_en: u32 = 1 << 0; // SCI_EN in PM1 control: set once ACPI mode is active
|
|
||||||
|
|
||||||
/// Switch the platform into ACPI mode if it isn't already, so the PM1 control
|
|
||||||
/// register is live. A no-op when the firmware exposes no SMI command port (ACPI
|
|
||||||
/// already enabled, as under QEMU/OVMF) — we still verify SCI_EN first.
|
|
||||||
pub fn enable(hal: Hal) void {
|
|
||||||
const pi = acpi.power_information;
|
|
||||||
if (!pi.pm1a_cnt.present()) return;
|
|
||||||
if (readRegister(hal, pi.pm1a_cnt) & sci_en != 0) return; // already in ACPI mode
|
|
||||||
if (pi.smi_cmd == 0 or pi.acpi_enable == 0) return; // no way to switch; assume fine
|
|
||||||
|
|
||||||
hal.pioWrite(1, pi.smi_cmd, pi.acpi_enable);
|
|
||||||
var spins: usize = 0;
|
|
||||||
while (readRegister(hal, pi.pm1a_cnt) & sci_en == 0 and spins < 1_000_000) : (spins += 1) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restart the machine. Tries the ACPI reset register first, then the two legacy
|
/// Restart the machine. Tries the ACPI reset register first, then the two legacy
|
||||||
/// fallbacks. Returns only if every method failed (very unlikely).
|
/// fallbacks. Returns only if every method failed (very unlikely).
|
||||||
pub fn reboot(hal: Hal) void {
|
pub fn reboot(hal: Hal) void {
|
||||||
@@ -48,42 +31,6 @@ pub fn reboot(hal: Hal) void {
|
|||||||
delay();
|
delay();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Power the machine off via ACPI S5. Requires the soft-off (`_S5`) sleep type; if
|
|
||||||
/// it wasn't found in the AML, there is nothing safe to do and this returns.
|
|
||||||
pub fn shutdown(hal: Hal) void {
|
|
||||||
enable(hal);
|
|
||||||
const pi = acpi.power_information;
|
|
||||||
const s5 = pi.s5 orelse return;
|
|
||||||
|
|
||||||
if (pi.pm1a_cnt.present()) {
|
|
||||||
writeRegister(hal, pi.pm1a_cnt, sleepValue(s5.slp_typ_a));
|
|
||||||
}
|
|
||||||
if (pi.pm1b_cnt.present()) {
|
|
||||||
writeRegister(hal, pi.pm1b_cnt, sleepValue(s5.slp_typ_b));
|
|
||||||
}
|
|
||||||
delay();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// S3 suspend-to-RAM — not implemented (needs a wake path + device re-init).
|
|
||||||
pub fn sleepS3(hal: Hal) error{Unsupported}!void {
|
|
||||||
_ = hal;
|
|
||||||
return error.Unsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The PM1 control write that requests sleep type `slp_typ`: SLP_TYP in bits
|
|
||||||
/// [12:10], SLP_EN in bit 13.
|
|
||||||
fn sleepValue(slp_typ: u8) u32 {
|
|
||||||
return (@as(u32, slp_typ & 0x7) << 10) | slp_en;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readRegister(hal: Hal, register: acpi.RegisterAccess) u32 {
|
|
||||||
if (register.mmio) {
|
|
||||||
const p: *align(1) volatile u32 = @ptrFromInt(hal.mapMmio(register.address, 4, true));
|
|
||||||
return p.*;
|
|
||||||
}
|
|
||||||
return hal.pioRead(register.width, @intCast(register.address));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn writeRegister(hal: Hal, register: acpi.RegisterAccess, value: u32) void {
|
fn writeRegister(hal: Hal, register: acpi.RegisterAccess, value: u32) void {
|
||||||
if (register.mmio) {
|
if (register.mmio) {
|
||||||
const p: *align(1) volatile u32 = @ptrFromInt(hal.mapMmio(register.address, 4, true));
|
const p: *align(1) volatile u32 = @ptrFromInt(hal.mapMmio(register.address, 4, true));
|
||||||
@@ -93,8 +40,8 @@ fn writeRegister(hal: Hal, register: acpi.RegisterAccess, value: u32) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A short busy-wait so a reset/power-off takes effect before we fall through to
|
/// A short busy-wait so a reset takes effect before we fall through to the next
|
||||||
/// the next method. The empty asm is an architecture-neutral barrier that keeps the loop
|
/// method. The empty asm is an architecture-neutral barrier that keeps the loop
|
||||||
/// from being optimised away.
|
/// from being optimised away.
|
||||||
fn delay() void {
|
fn delay() void {
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
|
|||||||
@@ -199,21 +199,13 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
// land on. Every line stays masked until something binds it (ioapic.init).
|
// land on. Every line stays masked until something binds it (ioapic.init).
|
||||||
irq.init();
|
irq.init();
|
||||||
|
|
||||||
// Power register map extracted from the FADT + AML, for confidence it parsed.
|
// Power register map, from the FADT (the SLP_TYP sleep values live in AML,
|
||||||
|
// which the kernel doesn't parse — the ring-3 acpi service owns soft-off).
|
||||||
const pw = platform.powerInformation();
|
const pw = platform.powerInformation();
|
||||||
log.write("/system/kernel: power\n");
|
log.write("/system/kernel: power\n");
|
||||||
log.print(" pm1a_cnt : {s} 0x{x} (width {d})\n", .{ if (pw.pm1a_cnt.mmio) "mmio" else "io", pw.pm1a_cnt.address, pw.pm1a_cnt.width });
|
log.print(" 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| {
|
|
||||||
log.print(" S5 slp_typ : a={d} b={d}\n", .{ s.slp_typ_a, s.slp_typ_b });
|
|
||||||
} else {
|
|
||||||
log.write(" S5 slp_typ : (not found)\n");
|
|
||||||
}
|
|
||||||
log.print(" 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 });
|
log.print(" 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();
|
|
||||||
log.print(" aml : {d} namespace nodes, parsed {d}/{d} bytes\n", .{ am.nodes, am.consumed, am.total });
|
|
||||||
|
|
||||||
// Feed the architecture layer the discovered addresses/facts so it makes no legacy
|
// Feed the architecture layer the discovered addresses/facts so it makes no legacy
|
||||||
// assumptions — the point of all this on UEFI Class 3 firmware. MMIO bases
|
// assumptions — the point of all this on UEFI Class 3 firmware. MMIO bases
|
||||||
// (HPET, I/O APIC) come from the device tree; scalar facts from ACPI.
|
// (HPET, I/O APIC) come from the device tree; scalar facts from ACPI.
|
||||||
|
|||||||
+23
-34
@@ -181,10 +181,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
|||||||
containmentTest();
|
containmentTest();
|
||||||
} else if (eql(case, "device-manager")) {
|
} else if (eql(case, "device-manager")) {
|
||||||
deviceManagerTest(boot_information);
|
deviceManagerTest(boot_information);
|
||||||
} else if (eql(case, "poweroff")) {
|
|
||||||
powerTest(.off);
|
|
||||||
} else if (eql(case, "reboot")) {
|
} else if (eql(case, "reboot")) {
|
||||||
powerTest(.reboot);
|
rebootTest();
|
||||||
} else {
|
} else {
|
||||||
log("DANOS-TEST-RESULT: FAIL (unknown case '{s}')\n", .{case});
|
log("DANOS-TEST-RESULT: FAIL (unknown case '{s}')\n", .{case});
|
||||||
}
|
}
|
||||||
@@ -201,15 +199,14 @@ fn platformHal() platform.Hal {
|
|||||||
/// Drive an ACPI power transition. On success the machine powers off or resets,
|
/// 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
|
/// so QEMU exits — the harness observes the process exit. If control returns, the
|
||||||
/// transition failed and we emit a FAIL result.
|
/// transition failed and we emit a FAIL result.
|
||||||
fn powerTest(comptime action: enum { off, reboot }) void {
|
// Soft-off (S5) is no longer a kernel operation — the ring-3 acpi service owns it
|
||||||
const name = if (action == .off) "poweroff" else "reboot";
|
// (exercised end-to-end by `orderly-shutdown`). Reboot stays in the kernel (FADT
|
||||||
log("DANOS-TEST-BEGIN: {s}\n", .{name});
|
// reset register, no AML), so it keeps its own case.
|
||||||
|
fn rebootTest() void {
|
||||||
|
log("DANOS-TEST-BEGIN: reboot\n", .{});
|
||||||
const hal = platformHal();
|
const hal = platformHal();
|
||||||
log("DANOS-POWER: attempting {s}\n", .{name});
|
log("DANOS-POWER: attempting reboot\n", .{});
|
||||||
switch (action) {
|
platform.reboot(hal);
|
||||||
.off => platform.shutdown(hal),
|
|
||||||
.reboot => platform.reboot(hal),
|
|
||||||
}
|
|
||||||
check("power transition took effect", false);
|
check("power transition took effect", false);
|
||||||
result();
|
result();
|
||||||
}
|
}
|
||||||
@@ -276,22 +273,19 @@ fn timer() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verify device discovery populated the platform facts the rest of the kernel
|
/// Verify device discovery populated the platform facts the rest of the kernel
|
||||||
/// depends on — the results ACPI parsing stashed in globals at boot. These are
|
/// depends on — the results the static ACPI tables stashed in globals at boot.
|
||||||
/// stable for the QEMU q35 + OVMF machine the harness runs, and span the tables:
|
/// These are stable for the QEMU q35 + OVMF machine the harness runs, and span the
|
||||||
/// MADT (LAPIC base, CPU count), FADT (PM/reset registers), and the AML parse
|
/// tables: MADT (LAPIC base, CPU count) and FADT (PM/reset registers). The kernel
|
||||||
/// (the sleep type, plus the integrity check that every byte was consumed).
|
/// no longer interprets AML — sleep types are the ring-3 acpi service's concern.
|
||||||
fn discoveryTest() void {
|
fn discoveryTest() void {
|
||||||
log("DANOS-TEST-BEGIN: discovery\n", .{});
|
log("DANOS-TEST-BEGIN: discovery\n", .{});
|
||||||
const pinfo = platform.platformInformation();
|
const pinfo = platform.platformInformation();
|
||||||
const pw = platform.powerInformation();
|
const pw = platform.powerInformation();
|
||||||
const am = platform.amlStats();
|
|
||||||
|
|
||||||
check("LAPIC base discovered (MADT)", pinfo.lapic_base == 0xFEE00000);
|
check("LAPIC base discovered (MADT)", pinfo.lapic_base == 0xFEE00000);
|
||||||
check("ACPI PM timer found (FADT)", pinfo.pm_timer.present());
|
check("ACPI PM timer found (FADT)", pinfo.pm_timer.present());
|
||||||
check("PM1a control register found (FADT)", pw.pm1a_cnt.present());
|
check("PM1a control register found (FADT)", pw.pm1a_cnt.present());
|
||||||
check("reset register supported (FADT)", pw.reset_supported);
|
check("reset register supported (FADT)", pw.reset_supported);
|
||||||
check("S5 sleep type found (AML)", pw.s5 != null);
|
|
||||||
check("AML parsed completely (consumed == total)", am.total > 0 and am.consumed == am.total);
|
|
||||||
check("at least one CPU enumerated (MADT)", platform.cpus().len >= 1);
|
check("at least one CPU enumerated (MADT)", platform.cpus().len >= 1);
|
||||||
|
|
||||||
// M15: every PCI function now carries its own 4 KiB ECAM configuration space as
|
// M15: every PCI function now carries its own 4 KiB ECAM configuration space as
|
||||||
@@ -2095,11 +2089,11 @@ fn acpiReportTest(boot_information: *const BootInformation) void {
|
|||||||
result();
|
result();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// M20.1: the ring-3 AML parse agrees with the kernel's. The manager spawns
|
/// M20.1: the ring-3 AML parse works. The manager spawns the discovery service
|
||||||
/// the discovery service (the acpi build variant); it claims the acpi-tables
|
/// (the acpi build variant); it claims the acpi-tables node, maps the blobs,
|
||||||
/// node, maps the blobs, parses them, and logs its Device count — which must
|
/// parses them, and self-verifies it found at least a floor of Device objects,
|
||||||
/// equal what the kernel's own parse produced (the equivalence that licenses
|
/// printing "acpi-parse: ok". The kernel no longer parses AML, so there is no
|
||||||
/// retiring the kernel's device build in M20.3).
|
/// kernel count to compare against — the ring-3 parse is now the only one.
|
||||||
fn acpiParseTest(boot_information: *const BootInformation) void {
|
fn acpiParseTest(boot_information: *const BootInformation) void {
|
||||||
log("DANOS-TEST-BEGIN: acpi-parse\n", .{});
|
log("DANOS-TEST-BEGIN: acpi-parse\n", .{});
|
||||||
if (boot_information.initial_ramdisk_len == 0) {
|
if (boot_information.initial_ramdisk_len == 0) {
|
||||||
@@ -2114,23 +2108,18 @@ fn acpiParseTest(boot_information: *const BootInformation) void {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The kernel's own count, from the namespace it already built for \_S5.
|
// Spawn the discovery service directly with a device-count *floor* as argv:
|
||||||
const kernel_devices = platform.amlDeviceCount();
|
// it parses the blobs in ring 3 and self-verifies it found at least that many
|
||||||
check("the kernel namespace has devices to compare against", kernel_devices >= 1);
|
// Device objects, printing "acpi-parse: ok". A floor of 1 just proves the
|
||||||
|
// parser ran and produced a namespace (the QEMU q35 DSDT has dozens). The
|
||||||
// Spawn the discovery service directly with that count as argv: it parses
|
// marker is deterministic — no racing the shared serial buffer.
|
||||||
// the same blobs in ring 3 and self-verifies, printing "acpi-parse: ok" iff
|
|
||||||
// the counts match. The harness's expect regex is that marker — deterministic,
|
|
||||||
// no racing the shared serial buffer.
|
|
||||||
process.setInitialRamdisk(image);
|
process.setInitialRamdisk(image);
|
||||||
var count_text: [16]u8 = undefined;
|
|
||||||
const count_arg = std.fmt.bufPrint(&count_text, "{d}", .{kernel_devices}) catch "0";
|
|
||||||
var spawned = false;
|
var spawned = false;
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < rd.count) : (i += 1) {
|
while (i < rd.count) : (i += 1) {
|
||||||
const item = rd.entry(i) orelse continue;
|
const item = rd.entry(i) orelse continue;
|
||||||
if (!eql(item.name, "discovery")) continue;
|
if (!eql(item.name, "discovery")) continue;
|
||||||
_ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", count_arg }, scheduler.currentId(), null) catch 0;
|
_ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", "1" }, scheduler.currentId(), null) catch 0;
|
||||||
spawned = true;
|
spawned = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,9 +103,11 @@ fn findTablesNode(buffer: []device.DeviceDescriptor) ?device.DeviceDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main(init: runtime.process.Init) void {
|
pub fn main(init: runtime.process.Init) void {
|
||||||
// When the acpi-parse scenario spawns this directly, argv[1] is the kernel's
|
// When the acpi-parse scenario spawns this directly, argv[1] is a device-count
|
||||||
// own device count to self-verify against — deterministic, no log-scraping.
|
// *floor* to self-verify against. The kernel no longer parses AML, so there is
|
||||||
const expected: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null;
|
// no exact count to match — proving the ring-3 parse found at least a floor of
|
||||||
|
// devices is the check. Deterministic, no log-scraping.
|
||||||
|
const floor: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null;
|
||||||
|
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("/system/services/acpi: out of memory\n");
|
_ = runtime.system.write("/system/services/acpi: out of memory\n");
|
||||||
@@ -162,11 +164,11 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
var namespace = result.namespace;
|
var namespace = result.namespace;
|
||||||
const devices = aml.deviceCount(&namespace);
|
const devices = aml.deviceCount(&namespace);
|
||||||
writeLine("/system/services/acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices });
|
writeLine("/system/services/acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices });
|
||||||
if (expected) |want| {
|
if (floor) |minimum| {
|
||||||
if (devices == want) {
|
if (devices >= minimum) {
|
||||||
_ = runtime.system.write("acpi-parse: ok\n");
|
_ = runtime.system.write("acpi-parse: ok\n");
|
||||||
} else {
|
} else {
|
||||||
writeLine("acpi-parse: mismatch (ring-3 {d} vs kernel {d})\n", .{ devices, want });
|
writeLine("acpi-parse: too few (ring-3 {d} < floor {d})\n", .{ devices, minimum });
|
||||||
}
|
}
|
||||||
// Self-verify mode is standalone (no manager); stop before reporting.
|
// Self-verify mode is standalone (no manager); stop before reporting.
|
||||||
while (true) runtime.system.sleep(1000);
|
while (true) runtime.system.sleep(1000);
|
||||||
|
|||||||
+3
-4
@@ -365,7 +365,7 @@ CASES = [
|
|||||||
"smp": 4,
|
"smp": 4,
|
||||||
"timeout": 60,
|
"timeout": 60,
|
||||||
"expect": r"acpi-parse: ok",
|
"expect": r"acpi-parse: ok",
|
||||||
"fail": r"acpi-parse: mismatch|DANOS-TEST-RESULT: FAIL"},
|
"fail": r"acpi-parse: too few|DANOS-TEST-RESULT: FAIL"},
|
||||||
# M20.3: the flip — ps2-bus now comes up from the acpi service's report, not
|
# M20.3: the flip — ps2-bus now comes up from the acpi service's report, not
|
||||||
# a kernel-built node. Ordered: report -> spawn -> the driver attaches its
|
# a kernel-built node. Ordered: report -> spawn -> the driver attaches its
|
||||||
# keyboard, proving discovery runs entirely in ring 3 (docs/discovery.md).
|
# keyboard, proving discovery runs entirely in ring 3 (docs/discovery.md).
|
||||||
@@ -492,9 +492,8 @@ CASES = [
|
|||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# The ACPI power path succeeds by QEMU *exiting* (S5 off / reset), so match the
|
# The ACPI power path succeeds by QEMU *exiting* (S5 off / reset), so match the
|
||||||
# pre-transition marker; the FAIL line only appears if the transition didn't take.
|
# pre-transition marker; the FAIL line only appears if the transition didn't take.
|
||||||
{"name": "poweroff",
|
# Soft-off (S5) is owned by the ring-3 acpi service now (see orderly-shutdown);
|
||||||
"expect": r"DANOS-POWER: attempting poweroff",
|
# the kernel keeps only reboot (FADT reset register, no AML).
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
|
||||||
{"name": "reboot",
|
{"name": "reboot",
|
||||||
"expect": r"DANOS-POWER: attempting reboot",
|
"expect": r"DANOS-POWER: attempting reboot",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
|
|||||||
Reference in New Issue
Block a user