Notify dispatch and GPE handlers (M21.2)
The AML interpreter now handles the Notify opcode (0x86, previously unhandled): it resolves the target device, evaluates the code, and records the pair in a bounded per-evaluate queue the caller drains with takeNotifications. A host unit test with hand-encoded AML — a method that issues Notify(DEV_, 0x80) — proves the device and code come back; aml.zig joins the zig build test loop so the interpreter is covered on the host. The acpi service's SCI handler now services general-purpose events too: for each set-and-enabled GPE bit it evaluates the \_GPE._Lxx (level) or _Exx (edge) handler method, drains the Notify queue that produced, and publishes a domain event per notified device — PNP0C0A battery, ACPI0003 AC, PNP0C0D lid, else generic notify — then clears the status bit and acks. The embedded controller's _Qxx queries are out of scope (hardware track). QEMU raises no GPEs on this config, so the QEMU suite is the regression net (the power button still works with GPE servicing in the path); correctness is the unit test. Suite 59/59.
This commit is contained in:
@@ -308,9 +308,75 @@ fn onSci() void {
|
||||
_ = runtime.system.write("power: button pressed\n");
|
||||
publishButton();
|
||||
}
|
||||
handleGpe();
|
||||
_ = device.irqAck(node_id, sci_resource_index);
|
||||
}
|
||||
|
||||
/// General-purpose events: for each set+enabled GPE bit, evaluate its `\_GPE`
|
||||
/// handler method (`_Lxx` level / `_Exx` edge), drain the Notify queue the
|
||||
/// method produced, and publish an event per notified device. Then clear the
|
||||
/// status bit. QEMU raises no GPEs on this config, so this path is exercised by
|
||||
/// host unit tests (docs/m21-plan.md decision 5); on real hardware it carries
|
||||
/// battery/AC/lid. The embedded controller's `_Qxx` queries are out of scope.
|
||||
fn handleGpe() void {
|
||||
handleGpeBlock(gpe0_blk, gpe0_len, 0);
|
||||
handleGpeBlock(gpe1_blk, gpe1_len, gpe0_len * 4);
|
||||
}
|
||||
|
||||
fn handleGpeBlock(blk: u16, len: u8, gpe_base: u32) void {
|
||||
if (blk == 0 or len == 0) return;
|
||||
const status_bytes = len / 2; // status half, then enable half
|
||||
var byte_index: u8 = 0;
|
||||
while (byte_index < status_bytes) : (byte_index += 1) {
|
||||
const sts: u8 = @truncate(halPioRead(1, blk + byte_index));
|
||||
const en: u8 = @truncate(halPioRead(1, blk + status_bytes + byte_index));
|
||||
const active = sts & en;
|
||||
if (active == 0) continue;
|
||||
var bit: u3 = 0;
|
||||
while (true) : (bit += 1) {
|
||||
if (active & (@as(u8, 1) << bit) != 0) {
|
||||
dispatchGpe(gpe_base + @as(u32, byte_index) * 8 + bit);
|
||||
}
|
||||
if (bit == 7) break;
|
||||
}
|
||||
halPioWrite(1, blk + byte_index, active); // write-1-to-clear the serviced bits
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate the `\_GPE._L%02X` or `_E%02X` handler for GPE number `n`, then
|
||||
/// publish an event for each device it notified.
|
||||
fn dispatchGpe(n: u32) void {
|
||||
const gpe_scope = aml.Namespace.resolve(&persistent_namespace, persistent_namespace.root, true, 0, &.{seg4("_GPE")}) orelse return;
|
||||
var name: [4]u8 = .{ '_', 'L', 0, 0 };
|
||||
writeHex2(name[2..4], n);
|
||||
var method = aml.Namespace.childOf(gpe_scope, name);
|
||||
if (method == null) {
|
||||
name[1] = 'E';
|
||||
method = aml.Namespace.childOf(gpe_scope, name);
|
||||
}
|
||||
const m = method orelse return; // no handler — the status bit was already cleared
|
||||
_ = global_interpreter.evaluate(m, &.{}) catch return;
|
||||
for (global_interpreter.takeNotifications()) |event| publishNotify(event.node, event.code);
|
||||
}
|
||||
|
||||
fn publishNotify(node: *aml.Node, code: u64) void {
|
||||
// Map the notified device's _HID to a domain event where we recognize it.
|
||||
var hid: [8]u8 = .{0} ** 8;
|
||||
if (readHid(node, &global_interpreter)) |h| hid = h;
|
||||
const which: power.Event = if (std.mem.eql(u8, hid[0..7], "PNP0C0A")) .battery else if (std.mem.eql(u8, hid[0..7], "ACPI0003")) .ac else if (std.mem.eql(u8, hid[0..7], "PNP0C0D")) .lid else .notify;
|
||||
var event = power.EventMessage{ .event = @intFromEnum(which), .code = @truncate(code) };
|
||||
event.hid = hid;
|
||||
writeLine("power: notify {s} code {d}\n", .{ hid[0..7], code });
|
||||
publishEvent(std.mem.asBytes(&event));
|
||||
}
|
||||
|
||||
/// Two lowercase hex digits of `n` into `out[0..2]`.
|
||||
fn writeHex2(out: []u8, n: u32) void {
|
||||
const digits = "0123456789ABCDEF";
|
||||
out[0] = digits[(n >> 4) & 0xF];
|
||||
out[1] = digits[n & 0xF];
|
||||
}
|
||||
|
||||
fn publishButton() void {
|
||||
const event = power.EventMessage{ .event = @intFromEnum(power.Event.power_button) };
|
||||
publishEvent(std.mem.asBytes(&event));
|
||||
|
||||
Reference in New Issue
Block a user