danos/library/device/acpi/aml/namespace.zig

182 lines
6.4 KiB
Zig

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