danos/system/devices/aml/parser.zig

518 lines
23 KiB
Zig

//! Recursive-descent AML parser. Walks the entire byte stream — including method
//! bodies — building the ACPI namespace as it goes. It does not *evaluate*
//! anything (no OperationRegion reads, no arithmetic); it parses structure so the
//! cursor stays aligned and every named object is recorded.
//!
//! The one genuine ambiguity in AML is method invocation: a bare NameString in an
//! operand position is a call whose argument count is only known from the method's
//! (earlier) declaration. Because we build the namespace in the same in-order pass,
//! `resolve` finds that declaration and tells us how many operands to consume.
//!
//! Safety net: every object delimited by a PkgLength (Scope/Device/Method/If/While/
//! Field/Buffer/Package/…) is parsed within its known extent, and the cursor is
//! snapped to that extent afterwards. So a mis-resolved invocation can only desync
//! *within* one such object; the enclosing walk realigns at the boundary.
const std = @import("std");
const opcode = @import("opcodes.zig");
const Namespace = @import("namespace.zig").Namespace;
const Node = @import("namespace.zig").Node;
const NodeKind = @import("namespace.zig").NodeKind;
pub const Error = error{ Truncated, Malformed } || std.mem.Allocator.Error;
const maximum_segments = 64;
/// A parsed NameString: an optional root anchor or some parent hops, then a list
/// of 4-byte segments.
const NamePath = struct {
rooted: bool = false,
parents: u8 = 0,
segments: [maximum_segments][4]u8 = undefined,
count: usize = 0,
fn slice(self: *const NamePath) []const [4]u8 {
return self.segments[0..self.count];
}
};
pub const Parser = struct {
aml: []const u8,
position: usize = 0,
namespace: *Namespace,
pub fn init(aml: []const u8, namespace: *Namespace) Parser {
return .{ .aml = aml, .namespace = namespace };
}
/// Parse the whole block as a TermList under the namespace root. Returns the
/// number of bytes consumed — equal to `aml.len` for a clean full traversal.
pub fn parseAll(self: *Parser) usize {
self.termList(self.aml.len, self.namespace.root);
return self.position;
}
// --- cursor primitives --------------------------------------------------
fn eof(self: *Parser) bool {
return self.position >= self.aml.len;
}
fn peek(self: *Parser) ?u8 {
return if (self.eof()) null else self.aml[self.position];
}
fn readByte(self: *Parser) Error!u8 {
if (self.eof()) return error.Truncated;
const b = self.aml[self.position];
self.position += 1;
return b;
}
fn skip(self: *Parser, n: usize) Error!void {
if (self.position + n > self.aml.len) return error.Truncated;
self.position += n;
}
fn skipCString(self: *Parser) Error!void {
while (true) {
const b = try self.readByte();
if (b == 0) return;
}
}
/// AML PkgLength: the lead byte's top two bits give how many extra bytes
/// follow; the value counts from the start of the PkgLength field.
fn readPackageLength(self: *Parser) Error!usize {
const lead = try self.readByte();
const follow: usize = lead >> 6;
if (follow == 0) return lead & 0x3F;
var value: usize = lead & 0x0F;
var i: usize = 0;
while (i < follow) : (i += 1) {
const b = try self.readByte();
value |= @as(usize, b) << @intCast(4 + i * 8);
}
return value;
}
fn readNameSegment(self: *Parser) Error![4]u8 {
if (self.position + 4 > self.aml.len) return error.Truncated;
const segment = self.aml[self.position..][0..4].*;
self.position += 4;
return segment;
}
fn readNameString(self: *Parser) Error!NamePath {
var name_path = NamePath{};
// A NameString is either root-anchored or parent-relative, not both.
if (self.peek() == opcode.root_char) {
name_path.rooted = true;
self.position += 1;
} else {
while (self.peek() == opcode.parent_prefix_char) : (self.position += 1) name_path.parents += 1;
}
const lead = self.peek() orelse return name_path;
switch (lead) {
0x00 => self.position += 1, // NullName
opcode.dual_name_prefix => {
self.position += 1;
try self.appendSegment(&name_path);
try self.appendSegment(&name_path);
},
opcode.multi_name_prefix => {
self.position += 1;
const count = try self.readByte();
var i: usize = 0;
while (i < count) : (i += 1) try self.appendSegment(&name_path);
},
else => {
if (isNameStart(lead)) try self.appendSegment(&name_path);
},
}
return name_path;
}
fn appendSegment(self: *Parser, name_path: *NamePath) Error!void {
const segment = try self.readNameSegment();
if (name_path.count < maximum_segments) {
name_path.segments[name_path.count] = segment;
name_path.count += 1;
}
}
// --- term list / object -------------------------------------------------
/// Parse objects until `end`, then snap to `end`. Any parse error resyncs to
/// the boundary rather than propagating — containment for the rare desync.
fn termList(self: *Parser, end: usize, scope: *Node) void {
while (self.position < end) {
self.object(scope) catch break;
}
self.position = end;
}
/// Parse exactly one object/term at the cursor. Used for both TermObjs and
/// operands (TermArg / SuperName / Target all reduce to "one object" for the
/// purpose of advancing the cursor).
fn object(self: *Parser, scope: *Node) Error!void {
const lead = self.peek() orelse return error.Truncated;
if (isNameStart(lead)) return self.nameInvocation(scope);
_ = try self.readByte();
switch (lead) {
// constants and no-operand statements
opcode.zero_opcode, opcode.one_opcode, opcode.ones_opcode => {},
opcode.noop_opcode, opcode.continue_opcode, opcode.break_opcode, opcode.break_point_opcode => {},
opcode.local0_opcode...opcode.local7_opcode => {},
opcode.arg0_opcode...opcode.arg6_opcode => {},
// literal data
opcode.byte_prefix => try self.skip(1),
opcode.word_prefix => try self.skip(2),
opcode.dword_prefix => try self.skip(4),
opcode.qword_prefix => try self.skip(8),
opcode.string_prefix => try self.skipCString(),
// data containers (contents skipped via their PkgLength)
opcode.buffer_opcode, opcode.package_opcode, opcode.var_package_opcode => try self.skipPackage(),
// namespace modifiers / named objects
opcode.name_opcode => try self.parseName(scope),
opcode.alias_opcode => try self.parseAlias(scope),
opcode.scope_opcode => try self.parseScopeLike(scope, .scope),
opcode.method_opcode => try self.parseMethod(scope),
opcode.external_opcode => try self.parseExternal(scope),
opcode.extended_opcode_prefix => try self.parseExtended(scope),
// control flow
opcode.if_opcode => try self.parseIf(scope),
opcode.else_opcode => try self.parseElse(scope),
opcode.while_opcode => try self.parseWhile(scope),
opcode.return_opcode => try self.object(scope),
opcode.notify_opcode => try self.args(scope, 2),
// stores / references / unary+target
opcode.store_opcode => try self.args(scope, 2),
opcode.ref_of_opcode, opcode.dereference_of_opcode, opcode.size_of_opcode, opcode.object_type_opcode => try self.args(scope, 1),
opcode.increment_opcode, opcode.decrement_opcode => try self.args(scope, 1),
opcode.not_opcode, opcode.find_set_left_bit_opcode, opcode.find_set_right_bit_opcode => try self.args(scope, 2),
opcode.to_buffer_opcode, opcode.to_decimal_string_opcode, opcode.to_hex_string_opcode, opcode.to_integer_opcode => try self.args(scope, 2),
opcode.copy_object_opcode => try self.args(scope, 2),
// binary + target
opcode.add_opcode, opcode.subtract_opcode, opcode.multiply_opcode, opcode.mod_opcode => try self.args(scope, 3),
opcode.and_opcode, opcode.nand_opcode, opcode.or_opcode, opcode.nor_opcode, opcode.xor_opcode => try self.args(scope, 3),
opcode.shift_left_opcode, opcode.shift_right_opcode, opcode.concat_opcode, opcode.concat_resource_opcode, opcode.index_opcode => try self.args(scope, 3),
opcode.divide_opcode => try self.args(scope, 4),
opcode.to_string_opcode => try self.args(scope, 3),
opcode.mid_opcode => try self.args(scope, 4),
// logical
opcode.land_opcode, opcode.lor_opcode => try self.args(scope, 2),
opcode.lequal_opcode, opcode.lgreater_opcode, opcode.lless_opcode => try self.args(scope, 2),
opcode.lnot_opcode => try self.parseLnot(scope),
opcode.match_opcode => try self.parseMatch(scope),
// CreateXField: <source> <index> NameString
opcode.create_dword_field_opcode,
opcode.create_word_field_opcode,
opcode.create_byte_field_opcode,
opcode.create_bit_field_opcode,
opcode.create_qword_field_opcode,
=> try self.parseCreateField(scope, 2),
else => return error.Malformed,
}
}
/// Parse `n` operands.
fn args(self: *Parser, scope: *Node, n: usize) Error!void {
var i: usize = 0;
while (i < n) : (i += 1) try self.object(scope);
}
/// A NameString in operand/statement position: a method invocation (consuming
/// the callee's declared argument count) or a plain name reference.
fn nameInvocation(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
if (self.namespace.resolve(scope, name_path.rooted, name_path.parents, name_path.slice())) |node| {
if ((node.kind == .method or node.kind == .external) and node.arg_count > 0) {
try self.args(scope, node.arg_count);
}
}
}
/// Skip a PkgLength-delimited body wholesale (Buffer / Package / VarPackage):
/// the contents are pure data, never namespace declarations.
fn skipPackage(self: *Parser) Error!void {
const start = self.position;
const len = try self.readPackageLength();
const end = start + len;
if (end > self.aml.len) return error.Truncated;
self.position = end;
}
// --- namespace objects --------------------------------------------------
fn parseName(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
const value_start = self.position;
try self.object(scope); // the DataReferenceObject value
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .name);
node.value = self.aml[value_start..self.position];
}
fn parseAlias(self: *Parser, scope: *Node) Error!void {
_ = try self.readNameString(); // source
const name_path = try self.readNameString(); // the alias name
_ = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .alias);
}
fn parseMethod(self: *Parser, scope: *Node) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
const name_path = try self.readNameString();
const flags = try self.readByte();
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .method);
node.arg_count = flags & 0x7;
// Capture the body for on-demand evaluation and skip it — objects declared
// inside a method are created at *runtime*, not at load, so they must not
// become permanent namespace nodes.
node.value = self.aml[self.position..@min(end, self.aml.len)];
self.position = end;
}
fn parseExternal(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
_ = try self.readByte(); // object type
const arg_count = try self.readByte();
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .external);
node.arg_count = arg_count;
}
/// Scope / Device / ThermalZone: PkgLength, NameString, then a nested TermList.
fn parseScopeLike(self: *Parser, scope: *Node, kind: NodeKind) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
const name_path = try self.readNameString();
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), kind);
self.termList(end, node);
}
fn parseProcessor(self: *Parser, scope: *Node) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
const name_path = try self.readNameString();
try self.skip(6); // ProcID(byte) + PblkAddress(dword) + PblkLen(byte)
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .processor);
self.termList(end, node);
}
fn parsePowerResource(self: *Parser, scope: *Node) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
const name_path = try self.readNameString();
try self.skip(3); // SystemLevel(byte) + ResourceOrder(word)
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .power_resource);
self.termList(end, node);
}
/// OperationRegion: NameString, RegionSpace(byte), Offset(TermArg), Len(TermArg).
/// The offset/length expressions are kept as AML for lazy evaluation.
fn parseRegion(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
const space = try self.readByte();
const off_start = self.position;
try self.object(scope);
const off_end = self.position;
try self.object(scope);
const len_end = self.position;
const node = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .region);
node.region_space = space;
node.region_offset_aml = self.aml[off_start..off_end];
node.region_len_aml = self.aml[off_end..len_end];
}
fn parseDataRegion(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
try self.args(scope, 3); // signature, oem id, oem table id (TermArgs)
_ = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .region);
}
fn parseMutex(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
try self.skip(1); // sync flags
_ = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .mutex);
}
fn parseEvent(self: *Parser, scope: *Node) Error!void {
const name_path = try self.readNameString();
_ = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .event);
}
/// CreateXField: `count` TermArgs then the new field's NameString.
fn parseCreateField(self: *Parser, scope: *Node, count: usize) Error!void {
try self.args(scope, count);
const name_path = try self.readNameString();
_ = try self.namespace.place(scope, name_path.rooted, name_path.parents, name_path.slice(), .name);
}
/// Field / IndexField / BankField: a region/bank reference, flags, then a
/// FieldList whose NamedFields become nodes in the current scope. For a plain
/// Field, the first NameString is the backing region — captured so field units
/// carry a region + bit position the evaluator can read/write.
fn parseField(self: *Parser, scope: *Node, name_strings: u8, bank: bool) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
var region: ?*Node = null;
var i: u8 = 0;
while (i < name_strings) : (i += 1) {
const name_path = try self.readNameString();
// Only a plain Field's single NameString denotes an OperationRegion.
if (name_strings == 1) region = self.namespace.resolve(scope, name_path.rooted, name_path.parents, name_path.slice());
}
if (bank) try self.object(scope); // bank value TermArg
const flags = try self.readByte();
self.fieldList(end, scope, region, flags & 0x0F);
}
fn fieldList(self: *Parser, end: usize, scope: *Node, region: ?*Node, initial_access: u8) void {
var bit_offset: u32 = 0;
var access = initial_access;
while (self.position < end) {
const lead = self.peek() orelse break;
switch (lead) {
0x00 => { // ReservedField: advances the bit position
self.position += 1;
const width = self.readPackageLength() catch break;
bit_offset += @intCast(width);
},
0x01 => { // AccessField: AccessType (low nibble) + AccessAttrib
self.position += 1;
const at = self.readByte() catch break;
self.skip(1) catch break;
access = at & 0x0F;
},
0x02 => { // ConnectField: NameString | BufferData
self.position += 1;
self.object(scope) catch break;
},
0x03 => { // ExtendedAccessField: type + attrib + length
self.position += 1;
self.skip(3) catch break;
},
else => { // NamedField: NameSegment + PkgLength (bit width)
const segment = self.readNameSegment() catch break;
const width = self.readPackageLength() catch break;
const unit = self.namespace.newFieldUnit(scope, segment) catch break;
unit.region = region;
unit.bit_offset = bit_offset;
unit.bit_width = @intCast(width);
unit.access_type = access;
bit_offset += @intCast(width);
},
}
}
self.position = end;
}
// --- control flow -------------------------------------------------------
fn parseIf(self: *Parser, scope: *Node) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
try self.object(scope); // predicate
self.termList(end, scope);
if (self.peek() == opcode.else_opcode) {
self.position += 1;
try self.parseElse(scope);
}
}
fn parseElse(self: *Parser, scope: *Node) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
self.termList(end, scope);
}
fn parseWhile(self: *Parser, scope: *Node) Error!void {
const start = self.position;
const end = start + try self.readPackageLength();
try self.object(scope); // predicate
self.termList(end, scope);
}
fn parseLnot(self: *Parser, scope: *Node) Error!void {
// 0x92 followed by 0x93/94/95 is a compound comparison (two operands);
// otherwise it is a plain LNot of one operand.
const b = self.peek() orelse return error.Truncated;
switch (b) {
opcode.lnot.not_equal, opcode.lnot.less_equal, opcode.lnot.greater_equal => {
self.position += 1;
try self.args(scope, 2);
},
else => try self.object(scope),
}
}
fn parseMatch(self: *Parser, scope: *Node) Error!void {
try self.object(scope); // search package
try self.skip(1); // match opcode 1
try self.object(scope); // operand 1
try self.skip(1); // match opcode 2
try self.object(scope); // operand 2
try self.object(scope); // start index
}
// --- extended opcodes (0x5B xx) -----------------------------------------
fn parseExtended(self: *Parser, scope: *Node) Error!void {
const e = try self.readByte();
switch (e) {
opcode.extended.mutex => try self.parseMutex(scope),
opcode.extended.event => try self.parseEvent(scope),
opcode.extended.operation_region => try self.parseRegion(scope),
opcode.extended.data_region => try self.parseDataRegion(scope),
opcode.extended.field => try self.parseField(scope, 1, false),
opcode.extended.index_field => try self.parseField(scope, 2, false),
opcode.extended.bank_field => try self.parseField(scope, 2, true),
opcode.extended.device => try self.parseScopeLike(scope, .device),
opcode.extended.thermal_zone => try self.parseScopeLike(scope, .thermal_zone),
opcode.extended.processor => try self.parseProcessor(scope),
opcode.extended.power_resource => try self.parsePowerResource(scope),
opcode.extended.conditional_reference_of => try self.args(scope, 2), // SuperName, Target
opcode.extended.create_field => try self.parseCreateField(scope, 3),
opcode.extended.load_table => try self.args(scope, 6),
opcode.extended.load => try self.args(scope, 2), // NameString, Target
opcode.extended.stall, opcode.extended.sleep => try self.args(scope, 1),
opcode.extended.acquire => {
try self.object(scope); // mutex SuperName
try self.skip(2); // timeout WordData
},
opcode.extended.signal, opcode.extended.reset, opcode.extended.release, opcode.extended.unload => try self.args(scope, 1),
opcode.extended.wait => try self.args(scope, 2),
opcode.extended.from_bcd, opcode.extended.to_bcd => try self.args(scope, 2),
opcode.extended.fatal => {
try self.skip(5); // Type(byte) + Code(dword)
try self.object(scope); // Arg TermArg
},
opcode.extended.revision, opcode.extended.debug, opcode.extended.timer => {},
else => return error.Malformed,
}
}
};
fn isNameStart(b: u8) bool {
return (b >= opcode.name_char_start and b <= opcode.name_char_end) or
b == opcode.name_char_underscore or
b == opcode.root_char or
b == opcode.parent_prefix_char or
b == opcode.dual_name_prefix or
b == opcode.multi_name_prefix;
}