Files
danos/system/devices/aml/namespace.zig
Daniel Samson 8754d4e46a Re-organize the source tree as a monorepo mirroring the FHS
The source layout now mirrors the runtime filesystem hierarchy
(docs/danos-file-system-hierarchy-FSH.md): what lives under system/ in the
source is what a running danos represents under /system. Each service and
driver is a sub-project directory that is its own Zig module — cross-project
references go by module name, never by a path into another project's files.

Moves (all git mv, history preserved):
- src/            -> system/            (danos internals; the self-representation)
    root.zig      -> danos.zig          (the kernel<->user contract module)
    kernel/arch/  -> kernel/architecture/   (arch -> architecture)
    device/       -> devices/           (what /system/devices reflects)
    boot/         -> /boot              (the loaders, top level)
- sbin/           -> split by role:
    init, vfs     -> system/services/<name>/<name>.zig
    hpetd, busd   -> system/drivers/<name>/<name>.zig
    vfs-test      -> system/services/vfs/vfs-test.zig  (inside the vfs project)
- lib/            -> library/runtime/   (room for other libraries beside runtime)

The VFS wire protocol becomes its own module, system/services/vfs/protocol.zig
("vfs-protocol"): the vfs sub-project exposes its interface, and the runtime's
file layer imports it by name. First instance of the "protocol module" pattern
(docs/driver-model.md); usb/block will expose theirs the same way.

Also: fix a naming-standard violation in the protocol — Op -> Operation (and
req -> request, _pad -> _padding). Docs updated: /system/services added to the
FHS doc, a repository-layout section added to the docs index, and stale source
paths swept across comments and docs.

Runtime boot paths are unchanged (the bootloader still loads /sbin/init);
aligning the runtime filesystem to the FHS is a separate follow-up. Suite 35/35
plus host tests green.
2026-07-10 12:55:56 +01:00

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