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.
228 lines
9.5 KiB
Zig
228 lines
9.5 KiB
Zig
//! AML (ACPI Machine Language) — the bytecode in the DSDT and SSDTs that describes
|
|
//! the parts of the machine the static tables don't.
|
|
//!
|
|
//! This module has two stages. `parser.zig` walks the entire byte stream and
|
|
//! records every named object into a namespace tree (`namespace.zig`), capturing
|
|
//! method bodies and field/region layout. `interpreter.zig` then *evaluates* control
|
|
//! methods on demand — running operators, control flow, and OperationRegion field
|
|
//! access — so callers can resolve device status (`_STA`), current resource
|
|
//! settings (`_CRS`), sleep states (`_Sx`), and the like against the live namespace.
|
|
|
|
const std = @import("std");
|
|
const opcode = @import("opcodes.zig");
|
|
const parser = @import("parser.zig");
|
|
|
|
pub const Namespace = @import("namespace.zig").Namespace;
|
|
pub const Node = @import("namespace.zig").Node;
|
|
pub const NodeKind = @import("namespace.zig").NodeKind;
|
|
|
|
/// The AML evaluator: interprets control methods (and reads Names/Fields) far
|
|
/// enough for device discovery. See `interpreter.zig`.
|
|
pub const Interpreter = @import("interpreter.zig").Interpreter;
|
|
pub const Object = @import("interpreter.zig").Object;
|
|
pub const EvaluateHal = @import("interpreter.zig").Hal;
|
|
|
|
/// The SLP_TYP values written to PM1a/PM1b control to enter a sleep state.
|
|
pub const SleepType = struct {
|
|
slp_typ_a: u8,
|
|
slp_typ_b: u8,
|
|
};
|
|
|
|
pub const ParseResult = struct {
|
|
namespace: Namespace,
|
|
/// Bytes the parser consumed across all blocks...
|
|
consumed: usize,
|
|
/// ...out of this many. A clean full traversal has `consumed == total`.
|
|
total: usize,
|
|
};
|
|
|
|
/// Parse the given AML blocks (DSDT first, then SSDTs) into one namespace. Later
|
|
/// blocks extend the namespace built by earlier ones, exactly as ACPI intends.
|
|
pub fn parse(allocator: std.mem.Allocator, blocks: []const []const u8) !ParseResult {
|
|
var namespace = try Namespace.init(allocator);
|
|
var consumed: usize = 0;
|
|
var total: usize = 0;
|
|
for (blocks) |block| {
|
|
var p = parser.Parser.init(block, &namespace);
|
|
consumed += p.parseAll();
|
|
total += block.len;
|
|
}
|
|
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 {
|
|
const segment = [4]u8{ '_', 'S', '0' + state, '_' };
|
|
const node = namespace.resolve(namespace.root, false, 0, &.{segment}) orelse return null;
|
|
if (node.kind != .name) return null;
|
|
return parseSleepPackage(node.value);
|
|
}
|
|
|
|
/// Decode a `Package(){ SLP_TYPa, SLP_TYPb, ... }` from the raw AML of a Name's
|
|
/// value. Returns the first two elements as bytes (missing elements default to 0).
|
|
fn parseSleepPackage(value: []const u8) ?SleepType {
|
|
if (value.len == 0 or value[0] != opcode.package_opcode) return null;
|
|
var p: usize = 1;
|
|
p += packageLengthSize(value, p) orelse return null;
|
|
if (p >= value.len) return null;
|
|
const number_elements = value[p];
|
|
p += 1;
|
|
|
|
const a: u8 = if (number_elements >= 1) @truncate(readInteger(value, &p) orelse 0) else 0;
|
|
const b: u8 = if (number_elements >= 2) @truncate(readInteger(value, &p) orelse 0) else 0;
|
|
return .{ .slp_typ_a = a, .slp_typ_b = b };
|
|
}
|
|
|
|
/// Bytes a PkgLength field occupies at `p` (we only need to step over it here).
|
|
fn packageLengthSize(bytes: []const u8, p: usize) ?usize {
|
|
if (p >= bytes.len) return null;
|
|
const follow: usize = bytes[p] >> 6;
|
|
if (p + 1 + follow > bytes.len) return null;
|
|
return 1 + follow;
|
|
}
|
|
|
|
/// Read one AML integer data object at `p`, advancing `p`.
|
|
fn readInteger(bytes: []const u8, p: *usize) ?u64 {
|
|
if (p.* >= bytes.len) return null;
|
|
const opcode_byte = bytes[p.*];
|
|
p.* += 1;
|
|
return switch (opcode_byte) {
|
|
opcode.zero_opcode => 0,
|
|
opcode.one_opcode => 1,
|
|
opcode.ones_opcode => 0xFF,
|
|
opcode.byte_prefix => readLittle(bytes, p, 1),
|
|
opcode.word_prefix => readLittle(bytes, p, 2),
|
|
opcode.dword_prefix => readLittle(bytes, p, 4),
|
|
opcode.qword_prefix => readLittle(bytes, p, 8),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
fn readLittle(bytes: []const u8, p: *usize, n: usize) ?u64 {
|
|
if (p.* + n > bytes.len) return null;
|
|
var v: u64 = 0;
|
|
var k: usize = 0;
|
|
while (k < n) : (k += 1) v |= @as(u64, bytes[p.* + k]) << @intCast(k * 8);
|
|
p.* += n;
|
|
return v;
|
|
}
|
|
|
|
// --- tests ------------------------------------------------------------------
|
|
|
|
test "parses a nested namespace and finds the sleep package" {
|
|
// A hand-assembled AML blob (all PkgLengths computed to be single-byte):
|
|
// Name(_S5, Package(2){0x05, 0x00})
|
|
// Scope(\_SB) { Device(PCI0) {
|
|
// Name(_HID, 0x11)
|
|
// Method(MTHD, 1) {}
|
|
// Method(CALL, 0) { MTHD(Zero) } // invocation of a 1-arg method
|
|
// } }
|
|
// OperationRegion(DBG0, SystemIO, 0x0402, 1)
|
|
// Field(DBG0, ...) { DBGB, 8 }
|
|
const blob = [_]u8{
|
|
// Name(_S5, Package(2){Byte 0x05, Byte 0x00})
|
|
0x08, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x06, 0x02, 0x0A, 0x05, 0x0A, 0x00,
|
|
// Scope(\_SB) packagelen=0x27
|
|
0x10, 0x27, 0x5C, 0x5F, 0x53, 0x42, 0x5F,
|
|
// Device(PCI0) packagelen=0x1F
|
|
0x5B, 0x82, 0x1F, 0x50, 0x43,
|
|
0x49, 0x30,
|
|
// Name(_HID, 0x11)
|
|
0x08, 0x5F, 0x48, 0x49, 0x44, 0x0A, 0x11,
|
|
// Method(MTHD, flags=1) empty, packagelen=0x06
|
|
0x14, 0x06, 0x4D,
|
|
0x54, 0x48, 0x44, 0x01,
|
|
// Method(CALL, flags=0) { MTHD(Zero) }, packagelen=0x0B
|
|
0x14, 0x0B, 0x43, 0x41, 0x4C, 0x4C, 0x00, 0x4D,
|
|
0x54, 0x48, 0x44, 0x00,
|
|
// OperationRegion(DBG0, SystemIO, Word 0x0402, Byte 1)
|
|
0x5B, 0x80, 0x44, 0x42, 0x47, 0x30, 0x01, 0x0B,
|
|
0x02, 0x04, 0x0A, 0x01,
|
|
// Field(DBG0, flags=1) { DBGB, 8 }, packagelen=0x0B
|
|
0x5B, 0x81, 0x0B, 0x44, 0x42, 0x47, 0x30, 0x01,
|
|
0x44, 0x42, 0x47, 0x42, 0x08,
|
|
};
|
|
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
var result = try parse(arena.allocator(), &.{&blob});
|
|
|
|
// Integrity: the parser consumed exactly the whole blob (no desync).
|
|
try std.testing.expectEqual(blob.len, result.consumed);
|
|
try std.testing.expectEqual(blob.len, result.total);
|
|
|
|
const namespace = &result.namespace;
|
|
|
|
// Expected top-level nodes.
|
|
const sb = namespace.resolve(namespace.root, false, 0, &.{.{ '_', 'S', 'B', '_' }}) orelse return error.NoSB;
|
|
try std.testing.expectEqual(NodeKind.scope, sb.kind);
|
|
const pci0 = namespace.resolve(sb, false, 0, &.{.{ 'P', 'C', 'I', '0' }}) orelse return error.NoPCI0;
|
|
try std.testing.expectEqual(NodeKind.device, pci0.kind);
|
|
_ = namespace.resolve(pci0, false, 0, &.{.{ '_', 'H', 'I', 'D' }}) orelse return error.NoHID;
|
|
|
|
// The 1-arg method's arg count was parsed from its flags byte.
|
|
const mthd = namespace.resolve(pci0, false, 0, &.{.{ 'M', 'T', 'H', 'D' }}) orelse return error.NoMTHD;
|
|
try std.testing.expectEqual(NodeKind.method, mthd.kind);
|
|
try std.testing.expectEqual(@as(u8, 1), mthd.arg_count);
|
|
|
|
// OperationRegion and the Field unit made it into the namespace.
|
|
_ = namespace.resolve(namespace.root, false, 0, &.{.{ 'D', 'B', 'G', '0' }}) orelse return error.NoRegion;
|
|
_ = namespace.resolve(namespace.root, false, 0, &.{.{ 'D', 'B', 'G', 'B' }}) orelse return error.NoField;
|
|
|
|
// The sleep package decoded.
|
|
const s5 = sleepState(namespace, 5) orelse return error.NoS5;
|
|
try std.testing.expectEqual(@as(u8, 5), s5.slp_typ_a);
|
|
try std.testing.expectEqual(@as(u8, 0), s5.slp_typ_b);
|
|
}
|
|
|
|
fn noMap(physical: u64, _: u64, _: bool) u64 {
|
|
return physical;
|
|
}
|
|
fn noRead(_: u8, _: u16) u32 {
|
|
return 0;
|
|
}
|
|
fn noWrite(_: u8, _: u16, _: u32) void {}
|
|
|
|
test "interpreter runs a method with args, arithmetic, and control flow" {
|
|
// Method(TST_, 1) {
|
|
// Store(Arg0, Local0); Add(Local0, 5, Local0)
|
|
// If (LGreater(Local0, 10)) { Return(One) }
|
|
// Return(Zero)
|
|
// }
|
|
const blob = [_]u8{
|
|
0x14, 0x18, 0x54, 0x53, 0x54, 0x5F, 0x01, // Method TST_, 1 arg
|
|
0x70, 0x68, 0x60, // Store(Arg0, Local0)
|
|
0x72, 0x60, 0x0A, 0x05, 0x60, // Add(Local0, 5, Local0)
|
|
0xA0, 0x07, 0x94, 0x60, 0x0A, 0x0A, 0xA4, 0x01, // If(LGreater(Local0,10)) { Return(One) }
|
|
0xA4, 0x00, // Return(Zero)
|
|
};
|
|
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
var result = try parse(arena.allocator(), &.{&blob});
|
|
const namespace = &result.namespace;
|
|
const tst = namespace.resolve(namespace.root, false, 0, &.{.{ 'T', 'S', 'T', '_' }}) orelse return error.NoMethod;
|
|
|
|
var interpreter = Interpreter.init(namespace, .{ .mapMmio = noMap, .pioRead = noRead, .pioWrite = noWrite }, arena.allocator());
|
|
|
|
const hi = try interpreter.evaluate(tst, &.{.{ .integer = 7 }}); // 7+5=12 > 10 -> 1
|
|
try std.testing.expectEqual(@as(u64, 1), try hi.asInteger());
|
|
const lo = try interpreter.evaluate(tst, &.{.{ .integer = 2 }}); // 2+5=7 !> 10 -> 0
|
|
try std.testing.expectEqual(@as(u64, 0), try lo.asInteger());
|
|
}
|