The AML interpreter runs in ring 3: the acpi service parses (M20.1)
The AML module becomes a build module compiled into both the kernel (for the \_S5 sleep state it still needs) and the new acpi service — one source, two builds, no fork. The kernel publishes a single acpi-tables node: the DSDT/SSDT blobs as memory resources, a broad io_port grant (the honest trust boundary — firmware AML names whatever ports it chose, known only after parsing), and the SCI for the M21 event track. The acpi service claims the node, maps each blob through the ordinary mmio grant (which preserves the sub-page offset onto the bytecode), and runs the same parser the kernel does. It self-verifies its namespace Device count against the kernel's — 34 = 34 — deterministically via an argv the acpi-parse test passes, so no racing the shared serial buffer. Parse-only touches no hardware; OperationRegion evaluation waits for _CRS/_STA in M20.2. The manager spawns 'discovery' (the neutral ramdisk name) at startup. Suite 56/56.
This commit is contained in:
@@ -41,6 +41,9 @@ pub const RegisterAccess = struct {
|
||||
/// 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 System Control Interrupt's GSI (FADT SCI_INT) — the line ACPI events
|
||||
/// (power button, GPEs) arrive on. Published to the acpi service for M21.
|
||||
sci_interrupt: u16 = 0,
|
||||
/// The SMM command port and the value that switches the platform into ACPI mode.
|
||||
smi_cmd: u16 = 0,
|
||||
acpi_enable: u8 = 0,
|
||||
@@ -408,6 +411,42 @@ pub fn discover(rsdp_physical: u64, memory_regions: []const boot_handoff.MemoryR
|
||||
// AML parse failed (e.g. out of memory); power stays best-effort with
|
||||
// whatever the FADT alone provided.
|
||||
}
|
||||
|
||||
// Publish the acpi-tables node (docs/m19-m20-plan.md M20): the AML blobs as
|
||||
// memory resources for the acpi service to map and parse in ring 3, a broad
|
||||
// io_port grant for the OperationRegion access its interpreter needs, and
|
||||
// the SCI for the events track (M21). Exactly one node, one trusted
|
||||
// claimant. Kept even when the kernel-side device building (above) retires
|
||||
// in M20.3 — the kernel still owns the *static* tables and \_S5.
|
||||
publishAcpiTablesNode(device_tree) catch {};
|
||||
}
|
||||
|
||||
/// Build the acpi-tables node (see the call site in discover). Best-effort: a
|
||||
/// failure here leaves the kernel-seeded tree working, only the ring-3 service
|
||||
/// finds nothing to claim.
|
||||
fn publishAcpiTablesNode(device_tree: *DeviceTree) !void {
|
||||
const node = try device_tree.addChild(device_tree.root, .acpi_tables, "acpi-tables");
|
||||
// One memory resource per AML block — page-aligned base down, length padded
|
||||
// up to cover the bytecode, so mmio_map hands the service a pointer into it.
|
||||
var i: usize = 0;
|
||||
while (i < aml_block_count and i < device_model.maximum_resources - 2) : (i += 1) {
|
||||
// mmio_map preserves the sub-page offset, so the service maps this and
|
||||
// gets a pointer straight to the bytecode.
|
||||
_ = node.addResource(.memory, aml_block_physical[i], aml_block_len[i]);
|
||||
}
|
||||
// The broad I/O grant: OperationRegions name whatever ports the firmware
|
||||
// chose (EC, PM1, GPE, SMBus); which ports cannot be known before the AML
|
||||
// that names them is parsed, so the grant is the whole space — the honest
|
||||
// trust boundary of docs/m19-m20-plan.md decision 5.
|
||||
_ = node.addResource(.io_port, 0, 1 << 16);
|
||||
// The SCI (M21 events); harmless to record now.
|
||||
if (power_information.sci_interrupt != 0) _ = node.addResource(.irq, power_information.sci_interrupt, 1);
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -670,6 +709,7 @@ const fadt_pm1a_cnt_blk = 64; // u32 (I/O port)
|
||||
const fadt_pm1b_cnt_blk = 68; // u32 (I/O port)
|
||||
const fadt_pm_tmr_blk = 76; // u32 (I/O port) — the PM timer counter
|
||||
const fadt_pm1_cnt_len = 89; // u8 (bytes)
|
||||
const fadt_sci_int = 46; // u16 (the SCI's GSI)
|
||||
const fadt_flags = 112; // u32
|
||||
const fadt_reset_register = 116; // GAS (12 bytes)
|
||||
const fadt_reset_value = 128; // u8
|
||||
@@ -687,6 +727,7 @@ fn parseFadt(header: *const SystemDescriptorTableHeader) void {
|
||||
const len: usize = header.length;
|
||||
const pi = &power_information;
|
||||
|
||||
pi.sci_interrupt = @truncate(fadt(u16, base, len, fadt_sci_int) orelse 0);
|
||||
pi.smi_cmd = @truncate(fadt(u32, base, len, fadt_smi_cmd) orelse 0);
|
||||
pi.acpi_enable = fadt(u8, base, len, fadt_acpi_enable) orelse 0;
|
||||
pi.acpi_disable = fadt(u8, base, len, fadt_acpi_disable) orelse 0;
|
||||
|
||||
@@ -50,6 +50,20 @@ pub fn parse(allocator: std.mem.Allocator, blocks: []const []const u8) !ParseRes
|
||||
return .{ .namespace = namespace, .consumed = consumed, .total = total };
|
||||
}
|
||||
|
||||
/// Count the Device objects in a parsed namespace — what the acpi service
|
||||
/// (docs/m19-m20-plan.md M20) reports, and what the kernel's own parse counts
|
||||
/// so the two can be checked equal across the ring-3 move.
|
||||
pub fn deviceCount(namespace: *const Namespace) usize {
|
||||
return countKind(namespace.root, .device);
|
||||
}
|
||||
|
||||
fn countKind(node: *const Node, kind: NodeKind) usize {
|
||||
var n: usize = if (node.kind == kind) 1 else 0;
|
||||
var c = node.first_child;
|
||||
while (c) |child| : (c = child.next_sibling) n += countKind(child, kind);
|
||||
return n;
|
||||
}
|
||||
|
||||
/// Look up the `\_S{state}` sleep package in a parsed namespace and return its
|
||||
/// first two integer elements (SLP_TYP for PM1a / PM1b), or null if absent.
|
||||
pub fn sleepState(namespace: *Namespace, state: u8) ?SleepType {
|
||||
|
||||
@@ -28,6 +28,11 @@ pub const DeviceClass = enum(u32) {
|
||||
/// A device named in the ACPI namespace (from the DSDT/SSDT), carrying a
|
||||
/// hardware ID (`_HID`) and, where static, current resource settings (`_CRS`).
|
||||
acpi_device,
|
||||
/// The ACPI tables themselves, published as one node for the user-space acpi
|
||||
/// service (docs/m19-m20-plan.md M20): memory resources over the AML blobs,
|
||||
/// a broad io_port grant for OperationRegion access, and the SCI interrupt.
|
||||
/// The one node whose claimant is trusted to run firmware bytecode.
|
||||
acpi_tables,
|
||||
unknown,
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,13 @@ pub fn platformInformation() PlatformInformation {
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
deviceListTest(boot_information);
|
||||
} else if (eql(case, "pci-scan")) {
|
||||
pciScanTest(boot_information);
|
||||
} else if (eql(case, "acpi-parse")) {
|
||||
acpiParseTest(boot_information);
|
||||
} else if (eql(case, "initial-ramdisk")) {
|
||||
initialRamdiskTest(boot_information);
|
||||
} else if (eql(case, "vfs")) {
|
||||
@@ -1912,6 +1914,49 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// M20.1: the ring-3 AML parse agrees with the kernel's. The manager spawns
|
||||
/// the discovery service (the acpi build variant); it claims the acpi-tables
|
||||
/// node, maps the blobs, parses them, and logs its Device count — which must
|
||||
/// equal what the kernel's own parse produced (the equivalence that licenses
|
||||
/// retiring the kernel's device build in M20.3).
|
||||
fn acpiParseTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: acpi-parse\n", .{});
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
// The kernel's own count, from the namespace it already built for \_S5.
|
||||
const kernel_devices = platform.amlDeviceCount();
|
||||
check("the kernel namespace has devices to compare against", kernel_devices >= 1);
|
||||
|
||||
// Spawn the discovery service directly with that count as argv: it parses
|
||||
// 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);
|
||||
var count_text: [16]u8 = undefined;
|
||||
const count_arg = std.fmt.bufPrint(&count_text, "{d}", .{kernel_devices}) catch "0";
|
||||
var spawned = false;
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
if (!eql(item.name, "discovery")) continue;
|
||||
_ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", count_arg }, scheduler.currentId(), null) catch 0;
|
||||
spawned = true;
|
||||
break;
|
||||
}
|
||||
check("discovery service spawned", spawned);
|
||||
result();
|
||||
}
|
||||
|
||||
/// The whole user-side surface at once: spawn process-test's supervisor role,
|
||||
/// which — entirely from ring 3 — creates an exit endpoint, spawns its two
|
||||
/// children supervised, sees them in process_enumerate, kills them (one blocked,
|
||||
|
||||
@@ -1,27 +1,94 @@
|
||||
//! /system/services/acpi — the ACPI discovery service: the x86 firmware
|
||||
//! interpreter, moved out of ring 0 (docs/m19-m20-plan.md, M20). **Placeholder:
|
||||
//! not implemented until M20.1** — it exists so the build's `-Ddiscovery`
|
||||
//! option has both of its values and the ramdisk's neutral `discovery` slot is
|
||||
//! wired before the implementation lands.
|
||||
//! interpreter, moved out of ring 0 (docs/m19-m20-plan.md, M20). Claims the
|
||||
//! `acpi-tables` node the kernel publishes (the AML blobs, the broad io_port
|
||||
//! grant, the SCI), and runs the **shared AML module** in ring 3 — the same
|
||||
//! parser the kernel uses for `\_S5`, now the sole builder of the device
|
||||
//! namespace.
|
||||
//!
|
||||
//! What it becomes (the plan's decisions 5 and 7): claim the `acpi-tables`
|
||||
//! node the kernel publishes (table blobs + the broad io_port grant + the SCI),
|
||||
//! map the tables, and run the **shared AML module** in ring 3 behind a `Hal`
|
||||
//! backed by `mmio_map` and `io_read`/`io_write` — the interpreter cannot tell
|
||||
//! it moved. Then the bus-driver shape: `device_register` the namespace
|
||||
//! devices (`_HID`, `_CRS` resources, containment against the node's
|
||||
//! apertures), report each to the device manager, stay resident under its
|
||||
//! supervision. M21 grows the event side on the same claim: the SCI, PM1 fixed
|
||||
//! events, GPEs, Notify — published through the domain-named power protocol,
|
||||
//! never an "ACPI events" protocol.
|
||||
//! M20.1 (this increment): claim the node, map each AML blob through the
|
||||
//! ordinary mmio grant, parse them into a namespace, and log the Device count —
|
||||
//! which the `acpi-parse` scenario checks equals the kernel's own parse.
|
||||
//! Parsing touches no hardware (the io_port grant and the interpreter's
|
||||
//! OperationRegion evaluation come in with `_CRS`/`_STA` at M20.2). Registering
|
||||
//! and reporting the namespace devices, and retiring the kernel's device build,
|
||||
//! follow in M20.2 and M20.3.
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const aml = @import("aml");
|
||||
const device = runtime.device;
|
||||
|
||||
pub fn main() void {
|
||||
// Not implemented: exit cleanly and silently (a bare spawn by the
|
||||
// initial-ramdisk sweep must not derange other tests' markers). The
|
||||
// supervisor reads a clean exit as "meant to stop" — correct for a
|
||||
// placeholder.
|
||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||
var line: [128]u8 = undefined;
|
||||
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||
}
|
||||
|
||||
/// Find the acpi-tables node the kernel published, or null.
|
||||
fn findTablesNode(buffer: []device.DeviceDescriptor) ?device.DeviceDescriptor {
|
||||
const total = device.enumerate(buffer);
|
||||
for (buffer[0..@min(total, buffer.len)]) |d| {
|
||||
if (d.class == @intFromEnum(device.DeviceClass.acpi_tables)) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
// When the acpi-parse scenario spawns this directly, argv[1] is the kernel's
|
||||
// own device count to self-verify against — deterministic, no log-scraping.
|
||||
const expected: ?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 {
|
||||
_ = runtime.system.write("acpi: out of memory\n");
|
||||
return;
|
||||
};
|
||||
const node = findTablesNode(buffer) orelse {
|
||||
_ = runtime.system.write("acpi: no acpi-tables node to claim\n");
|
||||
return;
|
||||
};
|
||||
if (!device.claim(node.id)) {
|
||||
_ = runtime.system.write("acpi: unable to claim acpi-tables\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Map each memory resource (an AML blob) and collect the byte slices. The
|
||||
// grant preserves each blob's sub-page offset, so the mapped pointer lands
|
||||
// straight on the bytecode.
|
||||
var blocks: [8][]const u8 = undefined;
|
||||
var block_count: usize = 0;
|
||||
for (node.resources[0..@intCast(node.resource_count)], 0..) |resource, index| {
|
||||
if (resource.kind != @intFromEnum(device.ResourceKind.memory)) continue;
|
||||
const base = device.mmioMap(node.id, index) orelse {
|
||||
writeLine("acpi: mmio_map failed for blob {d}\n", .{index});
|
||||
continue;
|
||||
};
|
||||
const pointer: [*]const u8 = @ptrFromInt(base);
|
||||
blocks[block_count] = pointer[0..@intCast(resource.len)];
|
||||
block_count += 1;
|
||||
if (block_count == blocks.len) break;
|
||||
}
|
||||
if (block_count == 0) {
|
||||
_ = runtime.system.write("acpi: no AML blobs on the node\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = aml.parse(runtime.allocator(), blocks[0..block_count]) catch {
|
||||
_ = runtime.system.write("acpi: AML parse failed\n");
|
||||
return;
|
||||
};
|
||||
var namespace = result.namespace;
|
||||
const devices = aml.deviceCount(&namespace);
|
||||
writeLine("acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices });
|
||||
if (expected) |want| {
|
||||
if (devices == want) {
|
||||
_ = runtime.system.write("acpi-parse: ok\n");
|
||||
} else {
|
||||
writeLine("acpi-parse: mismatch (ring-3 {d} vs kernel {d})\n", .{ devices, want });
|
||||
}
|
||||
}
|
||||
|
||||
// Registration and reports arrive in M20.2; stay resident so the claim
|
||||
// holds and the service is here to grow into the supervised discoverer.
|
||||
while (true) runtime.system.sleep(1000);
|
||||
}
|
||||
|
||||
pub const panic = runtime.panic;
|
||||
|
||||
@@ -347,6 +347,12 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// The discovery service (docs/m19-m20-plan.md M20): one per firmware, packed
|
||||
// under the neutral name "discovery", spawned once at startup. It finds and
|
||||
// claims the acpi-tables (or devicetree-blob) node itself. Not a per-device
|
||||
// match — it is the discoverer, not a driver bound to one device.
|
||||
addDriver("discovery", protocol.no_device, false);
|
||||
|
||||
if (test_restart_mode) {
|
||||
// The driver-restart scenario's fixture: claims device 0 (the tree
|
||||
// root, otherwise unclaimed), hellos, then faults — driving backoff,
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
const runtime = @import("runtime");
|
||||
|
||||
pub fn main() void {
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
_ = init;
|
||||
// Not implemented: exit cleanly and silently (a bare spawn by the
|
||||
// initial-ramdisk sweep must not derange other tests' markers). The
|
||||
// supervisor reads a clean exit as "meant to stop" — correct for a
|
||||
|
||||
Reference in New Issue
Block a user