reorg: move device data modules into library/device/<domain>/
First step of the device-code reorganization (plan: group device code by domain, split each domain into a shareable data module + a logic module). This moves the pure-data modules — the enums, wire types, and taxonomies that any layer including the kernel can import cheaply — out of system/devices/ and into their domain home: device-abi -> library/device/model/device-abi.zig pci-class -> library/device/pci/pci-class.zig usb-abi -> library/device/usb/usb-abi.zig usb-ids -> library/device/usb/usb-ids.zig acpi-ids -> library/device/acpi/acpi-ids.zig aml/ -> library/device/acpi/aml/ Module names are unchanged, so this is a pure file move: only the b.addModule paths and the host-test file list in build.zig change; no importer is touched. system/devices/ now holds only the kernel-internal device model (device-model, platform, acpi, device-tree, power). The device *logic* (the pci Function helper, the usb transfer client) and the kernel's cosmetic taxonomy dependency are handled in following commits. zig build + zig build test green.
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
//! AML (ACPI Machine Language) — the bytecode in the DSDT and SSDTs that describes
|
||||
//! the parts of the machine the static tables don't.
|
||||
//!
|
||||
//! This module has two stages. `parser.zig` walks the entire byte stream and
|
||||
//! records every named object into a namespace tree (`namespace.zig`), capturing
|
||||
//! method bodies and field/region layout. `interpreter.zig` then *evaluates* control
|
||||
//! methods on demand — running operators, control flow, and OperationRegion field
|
||||
//! access — so callers can resolve device status (`_STA`), current resource
|
||||
//! settings (`_CRS`), sleep states (`_Sx`), and the like against the live namespace.
|
||||
|
||||
const std = @import("std");
|
||||
const opcode = @import("opcodes.zig");
|
||||
const parser = @import("parser.zig");
|
||||
|
||||
/// The named AML opcode/prefix bytes (`zero_opcode`, `byte_prefix`, …). Re-exported so
|
||||
/// callers that decode raw AML bytes — e.g. the acpi service reading a `_HID` integer —
|
||||
/// name the opcodes instead of writing bare 0x0A/0x0B/… literals (docs/coding-standards.md).
|
||||
pub const opcodes = @import("opcodes.zig");
|
||||
|
||||
pub const Namespace = @import("namespace.zig").Namespace;
|
||||
pub const Node = @import("namespace.zig").Node;
|
||||
pub const NodeKind = @import("namespace.zig").NodeKind;
|
||||
|
||||
/// The AML evaluator: interprets control methods (and reads Names/Fields) far
|
||||
/// enough for device discovery. See `interpreter.zig`.
|
||||
pub const Interpreter = @import("interpreter.zig").Interpreter;
|
||||
pub const Object = @import("interpreter.zig").Object;
|
||||
pub const EvaluateHal = @import("interpreter.zig").Hal;
|
||||
|
||||
/// The SLP_TYP values written to PM1a/PM1b control to enter a sleep state.
|
||||
pub const SleepType = struct {
|
||||
slp_typ_a: u8,
|
||||
slp_typ_b: u8,
|
||||
};
|
||||
|
||||
pub const ParseResult = struct {
|
||||
namespace: Namespace,
|
||||
/// Bytes the parser consumed across all blocks...
|
||||
consumed: usize,
|
||||
/// ...out of this many. A clean full traversal has `consumed == total`.
|
||||
total: usize,
|
||||
};
|
||||
|
||||
/// Parse the given AML blocks (DSDT first, then SSDTs) into one namespace. Later
|
||||
/// blocks extend the namespace built by earlier ones, exactly as ACPI intends.
|
||||
pub fn parse(allocator: std.mem.Allocator, blocks: []const []const u8) !ParseResult {
|
||||
var namespace = try Namespace.init(allocator);
|
||||
var consumed: usize = 0;
|
||||
var total: usize = 0;
|
||||
for (blocks) |block| {
|
||||
var p = parser.Parser.init(block, &namespace);
|
||||
consumed += p.parseAll();
|
||||
total += block.len;
|
||||
}
|
||||
return .{ .namespace = namespace, .consumed = consumed, .total = total };
|
||||
}
|
||||
|
||||
/// Count the Device objects in a parsed namespace — what the acpi service
|
||||
/// (docs/discovery.md) reports, and what the kernel's own parse counts
|
||||
/// so the two can be checked equal across the ring-3 move.
|
||||
pub fn deviceCount(namespace: *const Namespace) usize {
|
||||
return countKind(namespace.root, .device);
|
||||
}
|
||||
|
||||
fn countKind(node: *const Node, kind: NodeKind) usize {
|
||||
var n: usize = if (node.kind == kind) 1 else 0;
|
||||
var c = node.first_child;
|
||||
while (c) |child| : (c = child.next_sibling) n += countKind(child, kind);
|
||||
return n;
|
||||
}
|
||||
|
||||
/// Look up the `\_S{state}` sleep package in a parsed namespace and return its
|
||||
/// first two integer elements (SLP_TYP for PM1a / PM1b), or null if absent.
|
||||
pub fn sleepState(namespace: *Namespace, state: u8) ?SleepType {
|
||||
const segment = [4]u8{ '_', 'S', '0' + state, '_' };
|
||||
const node = namespace.resolve(namespace.root, false, 0, &.{segment}) orelse return null;
|
||||
if (node.kind != .name) return null;
|
||||
return parseSleepPackage(node.value);
|
||||
}
|
||||
|
||||
/// Decode a `Package(){ SLP_TYPa, SLP_TYPb, ... }` from the raw AML of a Name's
|
||||
/// value. Returns the first two elements as bytes (missing elements default to 0).
|
||||
fn parseSleepPackage(value: []const u8) ?SleepType {
|
||||
if (value.len == 0 or value[0] != opcode.package_opcode) return null;
|
||||
var p: usize = 1;
|
||||
p += packageLengthSize(value, p) orelse return null;
|
||||
if (p >= value.len) return null;
|
||||
const number_elements = value[p];
|
||||
p += 1;
|
||||
|
||||
const a: u8 = if (number_elements >= 1) @truncate(readInteger(value, &p) orelse 0) else 0;
|
||||
const b: u8 = if (number_elements >= 2) @truncate(readInteger(value, &p) orelse 0) else 0;
|
||||
return .{ .slp_typ_a = a, .slp_typ_b = b };
|
||||
}
|
||||
|
||||
/// Bytes a PkgLength field occupies at `p` (we only need to step over it here).
|
||||
fn packageLengthSize(bytes: []const u8, p: usize) ?usize {
|
||||
if (p >= bytes.len) return null;
|
||||
const follow: usize = bytes[p] >> 6;
|
||||
if (p + 1 + follow > bytes.len) return null;
|
||||
return 1 + follow;
|
||||
}
|
||||
|
||||
/// Read one AML integer data object at `p`, advancing `p`.
|
||||
fn readInteger(bytes: []const u8, p: *usize) ?u64 {
|
||||
if (p.* >= bytes.len) return null;
|
||||
const opcode_byte = bytes[p.*];
|
||||
p.* += 1;
|
||||
return switch (opcode_byte) {
|
||||
opcode.zero_opcode => 0,
|
||||
opcode.one_opcode => 1,
|
||||
opcode.ones_opcode => 0xFF,
|
||||
opcode.byte_prefix => readLittle(bytes, p, 1),
|
||||
opcode.word_prefix => readLittle(bytes, p, 2),
|
||||
opcode.dword_prefix => readLittle(bytes, p, 4),
|
||||
opcode.qword_prefix => readLittle(bytes, p, 8),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn readLittle(bytes: []const u8, p: *usize, n: usize) ?u64 {
|
||||
if (p.* + n > bytes.len) return null;
|
||||
var v: u64 = 0;
|
||||
var k: usize = 0;
|
||||
while (k < n) : (k += 1) v |= @as(u64, bytes[p.* + k]) << @intCast(k * 8);
|
||||
p.* += n;
|
||||
return v;
|
||||
}
|
||||
|
||||
// --- tests ------------------------------------------------------------------
|
||||
|
||||
test "parses a nested namespace and finds the sleep package" {
|
||||
// A hand-assembled AML blob (all PkgLengths computed to be single-byte):
|
||||
// Name(_S5, Package(2){0x05, 0x00})
|
||||
// Scope(\_SB) { Device(PCI0) {
|
||||
// Name(_HID, 0x11)
|
||||
// Method(MTHD, 1) {}
|
||||
// Method(CALL, 0) { MTHD(Zero) } // invocation of a 1-arg method
|
||||
// } }
|
||||
// OperationRegion(DBG0, SystemIO, 0x0402, 1)
|
||||
// Field(DBG0, ...) { DBGB, 8 }
|
||||
const blob = [_]u8{
|
||||
// Name(_S5, Package(2){Byte 0x05, Byte 0x00})
|
||||
0x08, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x06, 0x02, 0x0A, 0x05, 0x0A, 0x00,
|
||||
// Scope(\_SB) packagelen=0x27
|
||||
0x10, 0x27, 0x5C, 0x5F, 0x53, 0x42, 0x5F,
|
||||
// Device(PCI0) packagelen=0x1F
|
||||
0x5B, 0x82, 0x1F, 0x50, 0x43,
|
||||
0x49, 0x30,
|
||||
// Name(_HID, 0x11)
|
||||
0x08, 0x5F, 0x48, 0x49, 0x44, 0x0A, 0x11,
|
||||
// Method(MTHD, flags=1) empty, packagelen=0x06
|
||||
0x14, 0x06, 0x4D,
|
||||
0x54, 0x48, 0x44, 0x01,
|
||||
// Method(CALL, flags=0) { MTHD(Zero) }, packagelen=0x0B
|
||||
0x14, 0x0B, 0x43, 0x41, 0x4C, 0x4C, 0x00, 0x4D,
|
||||
0x54, 0x48, 0x44, 0x00,
|
||||
// OperationRegion(DBG0, SystemIO, Word 0x0402, Byte 1)
|
||||
0x5B, 0x80, 0x44, 0x42, 0x47, 0x30, 0x01, 0x0B,
|
||||
0x02, 0x04, 0x0A, 0x01,
|
||||
// Field(DBG0, flags=1) { DBGB, 8 }, packagelen=0x0B
|
||||
0x5B, 0x81, 0x0B, 0x44, 0x42, 0x47, 0x30, 0x01,
|
||||
0x44, 0x42, 0x47, 0x42, 0x08,
|
||||
};
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var result = try parse(arena.allocator(), &.{&blob});
|
||||
|
||||
// Integrity: the parser consumed exactly the whole blob (no desync).
|
||||
try std.testing.expectEqual(blob.len, result.consumed);
|
||||
try std.testing.expectEqual(blob.len, result.total);
|
||||
|
||||
const namespace = &result.namespace;
|
||||
|
||||
// Expected top-level nodes.
|
||||
const sb = namespace.resolve(namespace.root, false, 0, &.{.{ '_', 'S', 'B', '_' }}) orelse return error.NoSB;
|
||||
try std.testing.expectEqual(NodeKind.scope, sb.kind);
|
||||
const pci0 = namespace.resolve(sb, false, 0, &.{.{ 'P', 'C', 'I', '0' }}) orelse return error.NoPCI0;
|
||||
try std.testing.expectEqual(NodeKind.device, pci0.kind);
|
||||
_ = namespace.resolve(pci0, false, 0, &.{.{ '_', 'H', 'I', 'D' }}) orelse return error.NoHID;
|
||||
|
||||
// The 1-arg method's arg count was parsed from its flags byte.
|
||||
const mthd = namespace.resolve(pci0, false, 0, &.{.{ 'M', 'T', 'H', 'D' }}) orelse return error.NoMTHD;
|
||||
try std.testing.expectEqual(NodeKind.method, mthd.kind);
|
||||
try std.testing.expectEqual(@as(u8, 1), mthd.arg_count);
|
||||
|
||||
// OperationRegion and the Field unit made it into the namespace.
|
||||
_ = namespace.resolve(namespace.root, false, 0, &.{.{ 'D', 'B', 'G', '0' }}) orelse return error.NoRegion;
|
||||
_ = namespace.resolve(namespace.root, false, 0, &.{.{ 'D', 'B', 'G', 'B' }}) orelse return error.NoField;
|
||||
|
||||
// The sleep package decoded.
|
||||
const s5 = sleepState(namespace, 5) orelse return error.NoS5;
|
||||
try std.testing.expectEqual(@as(u8, 5), s5.slp_typ_a);
|
||||
try std.testing.expectEqual(@as(u8, 0), s5.slp_typ_b);
|
||||
}
|
||||
|
||||
fn noMap(physical: u64, _: u64, _: bool) u64 {
|
||||
return physical;
|
||||
}
|
||||
fn noRead(_: u8, _: u16) u32 {
|
||||
return 0;
|
||||
}
|
||||
fn noWrite(_: u8, _: u16, _: u32) void {}
|
||||
|
||||
test "interpreter runs a method with args, arithmetic, and control flow" {
|
||||
// Method(TST_, 1) {
|
||||
// Store(Arg0, Local0); Add(Local0, 5, Local0)
|
||||
// If (LGreater(Local0, 10)) { Return(One) }
|
||||
// Return(Zero)
|
||||
// }
|
||||
const blob = [_]u8{
|
||||
0x14, 0x18, 0x54, 0x53, 0x54, 0x5F, 0x01, // Method TST_, 1 arg
|
||||
0x70, 0x68, 0x60, // Store(Arg0, Local0)
|
||||
0x72, 0x60, 0x0A, 0x05, 0x60, // Add(Local0, 5, Local0)
|
||||
0xA0, 0x07, 0x94, 0x60, 0x0A, 0x0A, 0xA4, 0x01, // If(LGreater(Local0,10)) { Return(One) }
|
||||
0xA4, 0x00, // Return(Zero)
|
||||
};
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var result = try parse(arena.allocator(), &.{&blob});
|
||||
const namespace = &result.namespace;
|
||||
const tst = namespace.resolve(namespace.root, false, 0, &.{.{ 'T', 'S', 'T', '_' }}) orelse return error.NoMethod;
|
||||
|
||||
var interpreter = Interpreter.init(namespace, .{ .mapMmio = noMap, .pioRead = noRead, .pioWrite = noWrite }, arena.allocator());
|
||||
|
||||
const hi = try interpreter.evaluate(tst, &.{.{ .integer = 7 }}); // 7+5=12 > 10 -> 1
|
||||
try std.testing.expectEqual(@as(u64, 1), try hi.asInteger());
|
||||
const lo = try interpreter.evaluate(tst, &.{.{ .integer = 2 }}); // 2+5=7 !> 10 -> 0
|
||||
try std.testing.expectEqual(@as(u64, 0), try lo.asInteger());
|
||||
}
|
||||
|
||||
test "interpreter records Notify(device, code)" {
|
||||
// Device(DEV_) { Name(_HID, 0x030AD041) } // PNP0A03-ish placeholder
|
||||
// Method(TST_, 0) { Notify(DEV_, 0x80); Return(Zero) }
|
||||
// Encoded: a Device holding a Name, then a Method issuing Notify on it.
|
||||
const blob = [_]u8{
|
||||
0x5B, 0x82, 0x0F, 0x44, 0x45, 0x56, 0x5F, // Device(DEV_) len=0x0F (pkglen + DEV_ + Name)
|
||||
0x08, 0x5F, 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x03, // Name(_HID, DWord 0x030AD041)
|
||||
0x14, 0x0F, 0x54, 0x53, 0x54, 0x5F, 0x00, // Method(TST_, 0) len=0x0F (pkglen + TST_ + flags + body)
|
||||
0x86, 0x44, 0x45, 0x56, 0x5F, 0x0A, 0x80, // Notify(DEV_, 0x80)
|
||||
0xA4, 0x00, // Return(Zero)
|
||||
};
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var result = try parse(arena.allocator(), &.{&blob});
|
||||
const namespace = &result.namespace;
|
||||
const tst = namespace.resolve(namespace.root, false, 0, &.{.{ 'T', 'S', 'T', '_' }}) orelse return error.NoMethod;
|
||||
const dev = namespace.resolve(namespace.root, false, 0, &.{.{ 'D', 'E', 'V', '_' }}) orelse return error.NoDevice;
|
||||
|
||||
var interpreter = Interpreter.init(namespace, .{ .mapMmio = noMap, .pioRead = noRead, .pioWrite = noWrite }, arena.allocator());
|
||||
_ = try interpreter.evaluate(tst, &.{});
|
||||
|
||||
const events = interpreter.takeNotifications();
|
||||
try std.testing.expectEqual(@as(usize, 1), events.len);
|
||||
try std.testing.expectEqual(dev, events[0].node);
|
||||
try std.testing.expectEqual(@as(u64, 0x80), events[0].code);
|
||||
}
|
||||
Reference in New Issue
Block a user