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:
Daniel Samson
2026-07-13 03:07:11 +01:00
parent d8dd62c639
commit a299363b59
11 changed files with 229 additions and 27 deletions
+86 -19
View File
@@ -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,
+2 -1
View File
@@ -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