//! 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: 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; }