The acpi service evaluates _CRS/_STA in ring 3 and reports devices (M20.2)
AML method evaluation now runs in userspace touching real hardware: the service builds an interpreter with a ring-3 Hal (port I/O routed through its claimed acpi-tables node; a scratch page backs SystemMemory maps so a stray OperationRegion degrades to zeros instead of faulting a process that cannot map arbitrary physical memory). It walks the namespace and, for each present _HID device that is not a PCI root, evaluates _CRS, registers it under acpi-tables, and reports it with its EISA-decoded hid. Containment for this needed the broker's irq check to become range-based — an interrupt line is still indivisible, but a parent may own a range, so the acpi-tables node's broad irq window contains its children's legacy lines (a length-1 range is exactly the old equality, so single-irq parents are unaffected). ChildAdded gained a hid field for firmware string identity. Matching those reports to drivers stays off until M20.3, so ps2-bus still comes up via the kernel path — no regression. The acpi-report scenario proves the PS/2 keyboard (io 0x60/0x64 + IRQ) and mouse (IRQ) are reported with their resources. Suite 57/57.
This commit is contained in:
+273
-21
@@ -1,29 +1,54 @@
|
||||
//! /system/services/acpi — the ACPI discovery service: the x86 firmware
|
||||
//! interpreter, moved out of ring 0 (docs/m19-m20-plan.md, M20). Claims the
|
||||
//! `acpi-tables` node the kernel publishes (the AML blobs, the broad io_port
|
||||
//! grant, the SCI), and runs the **shared AML module** in ring 3 — the same
|
||||
//! parser the kernel uses for `\_S5`, now the sole builder of the device
|
||||
//! namespace.
|
||||
//! grant, a broad irq window, the SCI), and runs the **shared AML module** in
|
||||
//! ring 3 — the same parser and interpreter the kernel uses.
|
||||
//!
|
||||
//! M20.1 (this increment): claim the node, map each AML blob through the
|
||||
//! ordinary mmio grant, parse them into a namespace, and log the Device count —
|
||||
//! which the `acpi-parse` scenario checks equals the kernel's own parse.
|
||||
//! Parsing touches no hardware (the io_port grant and the interpreter's
|
||||
//! OperationRegion evaluation come in with `_CRS`/`_STA` at M20.2). Registering
|
||||
//! and reporting the namespace devices, and retiring the kernel's device build,
|
||||
//! follow in M20.2 and M20.3.
|
||||
//! M20.2 (this increment): after parsing, walk the namespace and, for each
|
||||
//! present Device with a hardware id (`_HID`), evaluate its current resource
|
||||
//! settings (`_CRS`) through a ring-3 `Hal` (port I/O over the claimed node),
|
||||
//! register it under the acpi-tables node (its I/O ports and IRQs contained by
|
||||
//! the node's broad grants), and report it to the device manager with its
|
||||
//! EISA-decoded hid as identity. Matching those reports to drivers (ps2-bus)
|
||||
//! and retiring the kernel's own device build follow in M20.3.
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const aml = @import("aml");
|
||||
const device = runtime.device;
|
||||
const protocol = runtime.device_manager_protocol;
|
||||
|
||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||
var line: [128]u8 = undefined;
|
||||
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||
}
|
||||
|
||||
/// Find the acpi-tables node the kernel published, or null.
|
||||
// The claimed acpi-tables node and the resource index of its broad io_port
|
||||
// window — the Hal routes every port access through this one claim.
|
||||
var node_id: u64 = 0;
|
||||
var io_resource_index: u64 = 0;
|
||||
|
||||
// A scratch page returned for SystemMemory OperationRegion maps: the service
|
||||
// cannot map arbitrary physical memory from ring 3, so such regions are
|
||||
// unsupported and degrade to harmless zeros rather than faulting. The M20.2
|
||||
// targets (ps2, the legacy devices) use SystemIO and static templates.
|
||||
var mmio_scratch: [4096]u8 align(4096) = .{0} ** 4096;
|
||||
|
||||
fn halMapMmio(physical: u64, len: u64, writable: bool) u64 {
|
||||
_ = physical;
|
||||
_ = len;
|
||||
_ = writable;
|
||||
return @intFromPtr(&mmio_scratch);
|
||||
}
|
||||
|
||||
fn halPioRead(width: u8, port: u16) u32 {
|
||||
return device.ioRead(node_id, io_resource_index, port, width) orelse 0;
|
||||
}
|
||||
|
||||
fn halPioWrite(width: u8, port: u16, value: u32) void {
|
||||
_ = device.ioWrite(node_id, io_resource_index, port, width, value);
|
||||
}
|
||||
|
||||
fn findTablesNode(buffer: []device.DeviceDescriptor) ?device.DeviceDescriptor {
|
||||
const total = device.enumerate(buffer);
|
||||
for (buffer[0..@min(total, buffer.len)]) |d| {
|
||||
@@ -45,22 +70,24 @@ pub fn main(init: runtime.process.Init) void {
|
||||
_ = runtime.system.write("acpi: no acpi-tables node to claim\n");
|
||||
return;
|
||||
};
|
||||
if (!device.claim(node.id)) {
|
||||
node_id = node.id;
|
||||
if (!device.claim(node_id)) {
|
||||
_ = runtime.system.write("acpi: unable to claim acpi-tables\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Map each memory resource (an AML blob) and collect the byte slices. The
|
||||
// grant preserves each blob's sub-page offset, so the mapped pointer lands
|
||||
// straight on the bytecode.
|
||||
// Map each memory resource (an AML blob) and note the io_port resource.
|
||||
var blocks: [8][]const u8 = undefined;
|
||||
var block_count: usize = 0;
|
||||
var found_io = false;
|
||||
for (node.resources[0..@intCast(node.resource_count)], 0..) |resource, index| {
|
||||
if (resource.kind != @intFromEnum(device.ResourceKind.memory)) continue;
|
||||
const base = device.mmioMap(node.id, index) orelse {
|
||||
writeLine("acpi: mmio_map failed for blob {d}\n", .{index});
|
||||
if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and !found_io) {
|
||||
io_resource_index = index;
|
||||
found_io = true;
|
||||
continue;
|
||||
};
|
||||
}
|
||||
if (resource.kind != @intFromEnum(device.ResourceKind.memory)) continue;
|
||||
const base = device.mmioMap(node_id, index) orelse continue;
|
||||
const pointer: [*]const u8 = @ptrFromInt(base);
|
||||
blocks[block_count] = pointer[0..@intCast(resource.len)];
|
||||
block_count += 1;
|
||||
@@ -84,13 +111,238 @@ pub fn main(init: runtime.process.Init) void {
|
||||
} else {
|
||||
writeLine("acpi-parse: mismatch (ring-3 {d} vs kernel {d})\n", .{ devices, want });
|
||||
}
|
||||
// Self-verify mode is standalone (no manager); stop before reporting.
|
||||
while (true) runtime.system.sleep(1000);
|
||||
}
|
||||
|
||||
// Registration and reports arrive in M20.2; stay resident so the claim
|
||||
// holds and the service is here to grow into the supervised discoverer.
|
||||
// Register + report the present _HID devices (M20.2).
|
||||
var arena = std.heap.ArenaAllocator.init(runtime.allocator());
|
||||
var interpreter = aml.Interpreter.init(&namespace, .{
|
||||
.mapMmio = halMapMmio,
|
||||
.pioRead = halPioRead,
|
||||
.pioWrite = halPioWrite,
|
||||
}, arena.allocator());
|
||||
|
||||
const manager = runtime.ipc.lookup(.device_manager);
|
||||
var reported: u32 = 0;
|
||||
walkDevices(namespace.root, &interpreter, manager, &reported);
|
||||
writeLine("acpi: reported {d} device(s) to the manager\n", .{reported});
|
||||
|
||||
// Stay resident: the claim holds, and the service is here to grow into the
|
||||
// supervised discoverer (M20.3, then the M21 event side on the SCI).
|
||||
while (true) runtime.system.sleep(1000);
|
||||
}
|
||||
|
||||
/// Depth-first walk: register + report each present device with a _HID, then
|
||||
/// descend. Scopes (\_SB, \_GPE …) are descended without producing a node.
|
||||
fn walkDevices(node: *aml.Node, interpreter: *aml.Interpreter, manager: ?runtime.ipc.Handle, reported: *u32) void {
|
||||
var child = node.first_child;
|
||||
while (child) |c| : (child = c.next_sibling) {
|
||||
if (c.kind != .device) {
|
||||
walkDevices(c, interpreter, manager, reported);
|
||||
continue;
|
||||
}
|
||||
if (!devicePresent(interpreter, c)) continue; // absent: skip it and its subtree
|
||||
|
||||
if (readHid(c, interpreter)) |hid| {
|
||||
// Skip PCI roots — pci-bus already reports PCI functions; ACPI adds
|
||||
// only the non-PCI _HID devices (docs/m19-m20-plan.md M20.2).
|
||||
if (!std.mem.eql(u8, hid[0..7], "PNP0A03") and !std.mem.eql(u8, hid[0..7], "PNP0A08")) {
|
||||
registerAndReport(c, hid, interpreter, manager, reported);
|
||||
}
|
||||
}
|
||||
walkDevices(c, interpreter, manager, reported);
|
||||
}
|
||||
}
|
||||
|
||||
fn registerAndReport(node: *aml.Node, hid: [8]u8, interpreter: *aml.Interpreter, manager: ?runtime.ipc.Handle, reported: *u32) void {
|
||||
var descriptor = std.mem.zeroes(device.DeviceDescriptor);
|
||||
descriptor.class = @intFromEnum(device.DeviceClass.acpi_device);
|
||||
descriptor.pci_class = device.no_pci_class;
|
||||
const hid_len: u64 = std.mem.indexOfScalar(u8, &hid, 0) orelse hid.len;
|
||||
descriptor.hid_len = hid_len;
|
||||
@memcpy(descriptor.hid[0..@intCast(hid_len)], hid[0..@intCast(hid_len)]);
|
||||
applyCrs(&descriptor, node, interpreter);
|
||||
|
||||
const registered = device.register(node_id, &descriptor) orelse {
|
||||
writeLine("acpi: register refused for {s}\n", .{hid[0..@intCast(hid_len)]});
|
||||
return;
|
||||
};
|
||||
writeLine("acpi: reported {s} (device {d}, {d} resources)\n", .{ hid[0..@intCast(hid_len)], registered, descriptor.resource_count });
|
||||
reported.* += 1;
|
||||
|
||||
if (manager) |h| {
|
||||
var report = protocol.ChildAdded{
|
||||
.parent = node_id,
|
||||
.bus_address = registered,
|
||||
.identity = 0,
|
||||
.device_id = registered,
|
||||
};
|
||||
@memcpy(report.hid[0..@intCast(hid_len)], hid[0..@intCast(hid_len)]);
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
_ = runtime.ipc.call(h, std.mem.asBytes(&report), &reply) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
/// _STA bit 0 (present); absent method or a failed evaluation is treated as
|
||||
/// present, per the ACPI rules.
|
||||
fn devicePresent(interpreter: *aml.Interpreter, node: *aml.Node) bool {
|
||||
const sta = aml.Namespace.childOf(node, seg4("_STA")) orelse return true;
|
||||
const obj = interpreter.evaluate(sta, &.{}) catch return true;
|
||||
const status = obj.asInteger() catch return true;
|
||||
return (status & 0x01) != 0;
|
||||
}
|
||||
|
||||
/// The device's EISA-decoded _HID (e.g. "PNP0303"), or null.
|
||||
fn readHid(node: *aml.Node, interpreter: *aml.Interpreter) ?[8]u8 {
|
||||
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return null;
|
||||
var buffer: [8]u8 = .{0} ** 8;
|
||||
if (hid.kind == .method) {
|
||||
const obj = interpreter.evaluate(hid, &.{}) catch return null;
|
||||
switch (obj) {
|
||||
.integer => |n| {
|
||||
_ = eisaIdToStr(@truncate(n), &buffer);
|
||||
return buffer;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
if (hid.kind != .name or hid.value.len == 0) return null;
|
||||
const v = hid.value;
|
||||
switch (v[0]) {
|
||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
||||
var p: usize = 0;
|
||||
const n = readIntObj(v, &p) orelse return null;
|
||||
_ = eisaIdToStr(@truncate(n), &buffer);
|
||||
return buffer;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
|
||||
// --- _CRS resource-template decode (ported from the kernel's acpi.zig) --------
|
||||
|
||||
fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
||||
const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return;
|
||||
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
||||
const bytes = switch (obj) {
|
||||
.buffer => |b| b,
|
||||
else => return,
|
||||
};
|
||||
var i: usize = 0;
|
||||
while (i < bytes.len) {
|
||||
const tag = bytes[i];
|
||||
if (tag & 0x80 == 0) {
|
||||
const len: usize = tag & 0x07;
|
||||
const body = i + 1;
|
||||
if (body + len > bytes.len) break;
|
||||
switch ((tag >> 3) & 0x0F) {
|
||||
0x04 => if (len >= 2) { // IRQ mask
|
||||
const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8);
|
||||
var b: usize = 0;
|
||||
while (b < 16) : (b += 1) {
|
||||
if (mask & (@as(u16, 1) << @intCast(b)) != 0) addResource(descriptor, .irq, b, 1);
|
||||
}
|
||||
},
|
||||
0x08 => if (len >= 7) addResource(descriptor, .io_port, rd16(bytes, body + 1), bytes[body + 6]),
|
||||
0x09 => if (len >= 3) addResource(descriptor, .io_port, rd16(bytes, body), bytes[body + 2]),
|
||||
0x0F => break,
|
||||
else => {},
|
||||
}
|
||||
i = body + len;
|
||||
} else {
|
||||
if (i + 3 > bytes.len) break;
|
||||
const len: usize = @intCast(rd16(bytes, i + 1));
|
||||
const body = i + 3;
|
||||
if (body + len > bytes.len) break;
|
||||
switch (tag) {
|
||||
0x85 => if (len >= 17) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 13)),
|
||||
0x86 => if (len >= 9) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 5)),
|
||||
0x89 => if (len >= 2) {
|
||||
const count = bytes[body + 1];
|
||||
var k: usize = 0;
|
||||
while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) {
|
||||
addResource(descriptor, .irq, rd32(bytes, body + 2 + k * 4), 1);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
i = body + len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn addResource(descriptor: *device.DeviceDescriptor, kind: device.ResourceKind, start: u64, len: u64) void {
|
||||
if (descriptor.resource_count >= descriptor.resources.len) return;
|
||||
descriptor.resources[@intCast(descriptor.resource_count)] = .{ .kind = @intFromEnum(kind), .start = start, .len = len };
|
||||
descriptor.resource_count += 1;
|
||||
}
|
||||
|
||||
// --- small helpers ported verbatim from the kernel's acpi.zig ----------------
|
||||
|
||||
fn seg4(comptime s: *const [4:0]u8) [4]u8 {
|
||||
return s[0..4].*;
|
||||
}
|
||||
|
||||
fn hexDigit(n: u8) u8 {
|
||||
return if (n < 10) '0' + n else 'A' + (n - 10);
|
||||
}
|
||||
|
||||
fn eisaIdToStr(id: u32, buffer: *[8]u8) []const u8 {
|
||||
const b0: u16 = @intCast(id & 0xFF);
|
||||
const b1: u16 = @intCast((id >> 8) & 0xFF);
|
||||
const b2: u8 = @truncate(id >> 16);
|
||||
const b3: u8 = @truncate(id >> 24);
|
||||
const mfg = (b0 << 8) | b1;
|
||||
buffer[0] = '@' + @as(u8, @intCast((mfg >> 10) & 0x1F));
|
||||
buffer[1] = '@' + @as(u8, @intCast((mfg >> 5) & 0x1F));
|
||||
buffer[2] = '@' + @as(u8, @intCast(mfg & 0x1F));
|
||||
buffer[3] = hexDigit((b2 >> 4) & 0xF);
|
||||
buffer[4] = hexDigit(b2 & 0xF);
|
||||
buffer[5] = hexDigit((b3 >> 4) & 0xF);
|
||||
buffer[6] = hexDigit(b3 & 0xF);
|
||||
buffer[7] = 0;
|
||||
return buffer[0..7];
|
||||
}
|
||||
|
||||
fn readIntObj(bytes: []const u8, p: *usize) ?u64 {
|
||||
if (p.* >= bytes.len) return null;
|
||||
const op = bytes[p.*];
|
||||
p.* += 1;
|
||||
switch (op) {
|
||||
0x00 => return 0,
|
||||
0x01 => return 1,
|
||||
0xFF => return 1,
|
||||
0x0A => {
|
||||
if (p.* >= bytes.len) return null;
|
||||
const v = bytes[p.*];
|
||||
p.* += 1;
|
||||
return v;
|
||||
},
|
||||
0x0B => {
|
||||
if (p.* + 2 > bytes.len) return null;
|
||||
const v = rd16(bytes, p.*);
|
||||
p.* += 2;
|
||||
return v;
|
||||
},
|
||||
0x0C => {
|
||||
if (p.* + 4 > bytes.len) return null;
|
||||
const v = rd32(bytes, p.*);
|
||||
p.* += 4;
|
||||
return v;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
|
||||
fn rd16(bytes: []const u8, off: usize) u64 {
|
||||
return @as(u64, bytes[off]) | (@as(u64, bytes[off + 1]) << 8);
|
||||
}
|
||||
|
||||
fn rd32(bytes: []const u8, off: usize) u64 {
|
||||
return rd16(bytes, off) | (rd16(bytes, off + 2) << 16);
|
||||
}
|
||||
|
||||
pub const panic = runtime.panic;
|
||||
comptime {
|
||||
_ = &runtime.start._start; // pull the runtime entry shim into the image
|
||||
|
||||
Reference in New Issue
Block a user