//! The ACPI namespace the AML parser builds: a tree of named nodes, plus the name //! resolution rules the parser needs while it walks (so a method invocation can be //! resolved to its declaration to learn its argument count). //! //! Nodes are individually allocated and linked intrusively (first-child / //! next-sibling), the same shape as the device tree in `device.zig`. const std = @import("std"); pub const NodeKind = enum { root, scope, device, method, name, region, // OperationRegion field, // a Field unit mutex, event, processor, power_resource, thermal_zone, alias, external, other, }; pub const Node = struct { /// The 4-byte NameSeg identifying this node within its parent. The root uses /// all-zero. segment: [4]u8 = .{ 0, 0, 0, 0 }, kind: NodeKind = .other, /// For Method / External: the declared argument count (0..7). Used to resolve /// how many TermArgs a method invocation consumes. arg_count: u8 = 0, /// For Name: the AML bytes of its DataReferenceObject (so a value like a sleep /// state's (`_Sx`) Package can be parsed on demand). For Method: the AML bytes of the body, /// interpreted on demand by the evaluator. Empty otherwise. value: []const u8 = &.{}, // OperationRegion metadata (kind == .region): the address space, plus the AML // of the offset/length expressions (evaluated lazily, usually constants). region_space: u8 = 0, region_offset_aml: []const u8 = &.{}, region_len_aml: []const u8 = &.{}, // Field-unit metadata (kind == .field): which region it lives in and its bit // position/width/access, so the evaluator can read/write it. region: ?*Node = null, bit_offset: u32 = 0, bit_width: u32 = 0, access_type: u8 = 0, parent: ?*Node = null, first_child: ?*Node = null, next_sibling: ?*Node = null, /// Depth-first count of this node and everything under it. pub fn subtreeCount(self: *const Node) usize { var n: usize = 1; var c = self.first_child; while (c) |child| : (c = child.next_sibling) n += child.subtreeCount(); return n; } }; pub const Namespace = struct { allocator: std.mem.Allocator, root: *Node, pub fn init(allocator: std.mem.Allocator) !Namespace { const root = try allocator.create(Node); root.* = .{ .kind = .root }; return .{ .allocator = allocator, .root = root }; } pub fn nodeCount(self: *const Namespace) usize { return self.root.subtreeCount(); } fn findChild(parent: *Node, segment: [4]u8) ?*Node { var c = parent.first_child; while (c) |child| : (c = child.next_sibling) { if (std.mem.eql(u8, &child.segment, &segment)) return child; } return null; } /// The direct child of `node` named `segment`, or null. Unlike `resolve`, this does /// not apply the search-rule walk-up — it looks only at immediate children (for /// reading a device's own hardware ID (`_HID`) / current resource settings (`_CRS`)). pub fn childOf(node: *Node, segment: [4]u8) ?*Node { return findChild(node, segment); } fn newChild(self: *Namespace, parent: *Node, segment: [4]u8, kind: NodeKind) !*Node { const n = try self.allocator.create(Node); n.* = .{ .segment = segment, .kind = kind, .parent = parent }; // Append at the tail so a dump reads in declaration order. if (parent.first_child == null) { parent.first_child = n; } else { var current = parent.first_child.?; while (current.next_sibling) |sib| current = sib; current.next_sibling = n; } return n; } /// Create a Field unit node directly under `scope` (field units live in the /// scope of the Field/IndexField/BankField, not under the region). pub fn newFieldUnit(self: *Namespace, scope: *Node, segment: [4]u8) !*Node { return self.findOrCreate(scope, segment, .field); } fn findOrCreate(self: *Namespace, parent: *Node, segment: [4]u8, kind: NodeKind) !*Node { if (findChild(parent, segment)) |existing| { // Reopening a scope (e.g. Scope(\_SB) after Device \_SB) keeps the more // specific kind rather than downgrading to a plain scope. if (existing.kind == .scope and kind != .scope) existing.kind = kind; return existing; } return self.newChild(parent, segment, kind); } /// The node a definition's NameString names, creating any intermediate scopes. /// The final segment is created (or found) with `kind`; intermediates are /// scopes. Returns the namespace root for a NullName (empty path). pub fn place( self: *Namespace, current: *Node, rooted: bool, parents: u8, segments: []const [4]u8, kind: NodeKind, ) !*Node { var base = startNode(self, current, rooted, parents); if (segments.len == 0) return base; var i: usize = 0; while (i + 1 < segments.len) : (i += 1) { base = try self.findOrCreate(base, segments[i], .scope); } return self.findOrCreate(base, segments[segments.len - 1], kind); } /// Resolve a NameString *reference* to an existing node, or null. A single /// relative segment uses the ACPI search rule (walk up the ancestors); any /// rooted, parented, or multi-segment path is resolved exactly. pub fn resolve( self: *Namespace, current: *Node, rooted: bool, parents: u8, segments: []const [4]u8, ) ?*Node { if (segments.len == 0) return null; if (!rooted and parents == 0 and segments.len == 1) { // Search rule: this scope, then each ancestor up to the root. var scope: ?*Node = current; while (scope) |s| : (scope = s.parent) { if (findChild(s, segments[0])) |n| return n; } return null; } var base = startNode(self, current, rooted, parents); for (segments) |segment| { base = findChild(base, segment) orelse return null; } return base; } fn startNode(self: *Namespace, current: *Node, rooted: bool, parents: u8) *Node { if (rooted) return self.root; var base = current; var up = parents; while (up > 0) : (up -= 1) base = base.parent orelse self.root; return base; } };