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:
Daniel Samson
2026-07-13 05:44:58 +01:00
parent 1f2c60b3ec
commit 767a2a9a7c
8 changed files with 197 additions and 59 deletions
+1
View File
@@ -583,6 +583,7 @@ pub fn build(b: *std.Build) void {
"system/devices/device-abi.zig", "system/devices/device-abi.zig",
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding "system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
"system/devices/acpi-ids.zig", // _HID name decoding "system/devices/acpi-ids.zig", // _HID name decoding
"system/devices/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
"system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings "system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
"system/devices/usb-ids.zig", // class/subclass/protocol code assignments "system/devices/usb-ids.zig", // class/subclass/protocol code assignments
"library/mmio/mmio.zig", // barriers assemble + registers round-trip "library/mmio/mmio.zig", // barriers assemble + registers round-trip
+8 -6
View File
@@ -84,12 +84,14 @@ auto-merge to main when the branch is green; keep the branch; push everything.
logs `power: button pressed`, publishes `power_button`, acks. Scenario logs `power: button pressed`, publishes `power_button`, acks. Scenario
`power-button` injects a real `system_powerdown` via QMP; initial-ramdisk `power-button` injects a real `system_powerdown` via QMP; initial-ramdisk
timeout 30→60s for the service's added boot work; suite 59/59). timeout 30→60s for the service's added boot work; suite 59/59).
- [ ] **M21.2** — Notify + GPE dispatch: interpreter handles `notify_opcode` - [x] **M21.2** — Notify + GPE dispatch (interpreter handles `notify_opcode`
into a bounded queue drained after evaluate(); on GPE status bits the into a bounded queue, cleared per-evaluate, drained via
service evaluates `\_GPE._Lxx`/`_Exx`, maps notified nodes to events `takeNotifications`; the service walks GPE status/enable bytes, evaluates
(PNP0C0A→battery, ACPI0003→ac, PNP0C0D→lid, else generic), clears `\_GPE._Lxx`/`_Exx` per active bit, maps notified nodes to events
GPE_STS, acks. EC `_Qxx` explicitly out (hardware track). Host unit (battery/ac/lid/generic), clears GPE_STS write-1, acks. EC `_Qxx` out.
tests for Notify in aml.zig; aml.zig joins the `zig build test` loop. Host unit test with hand-encoded AML proves the queue; aml.zig joined the
`zig build test` loop. QEMU raises no GPEs — suite is regression net,
59/59).
- [ ] **M21.3** — orderly shutdown: init keeps child ids (spawnSupervised + - [ ] **M21.3** — orderly shutdown: init keeps child ids (spawnSupervised +
exit endpoint), binds signals, subscribes to `.power`; on `power_button` exit endpoint), binds signals, subscribes to `.power`; on `power_button`
logs `init: shutting down`, runs `stop(child, 2000, endpoint)` in logs `init: shutting down`, runs `stop(child, 2000, endpoint)` in
+28
View File
@@ -230,3 +230,31 @@ test "interpreter runs a method with args, arithmetic, and control flow" {
const lo = try interpreter.evaluate(tst, &.{.{ .integer = 2 }}); // 2+5=7 !> 10 -> 0 const lo = try interpreter.evaluate(tst, &.{.{ .integer = 2 }}); // 2+5=7 !> 10 -> 0
try std.testing.expectEqual(@as(u64, 0), try lo.asInteger()); 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);
}
+41
View File
@@ -141,6 +141,9 @@ const Frame = struct {
/// A CreateField binding: a name that indexes into a buffer object. /// A CreateField binding: a name that indexes into a buffer object.
const BufferField = struct { buffer: *Node, byte_off: usize, bit_width: u32 }; const BufferField = struct { buffer: *Node, byte_off: usize, bit_width: u32 };
/// One Notify(device, code) the interpreter executed.
pub const NotifyEvent = struct { node: *Node, code: u64 };
pub const Interpreter = struct { pub const Interpreter = struct {
namespace: *Namespace, namespace: *Namespace,
hal: Hal, hal: Hal,
@@ -149,6 +152,11 @@ pub const Interpreter = struct {
dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{}, dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{},
/// CreateField bindings active for the current evaluation. /// CreateField bindings active for the current evaluation.
fields: std.AutoHashMapUnmanaged(*Node, BufferField) = .{}, fields: std.AutoHashMapUnmanaged(*Node, BufferField) = .{},
/// Notify(device, code) operations the last evaluation executed — a GPE or
/// EC handler tells the OS "look at this device" this way. Bounded; the
/// caller drains it with `takeNotifications` after `evaluate` (M21).
notify_queue: [16]NotifyEvent = undefined,
notify_count: usize = 0,
pub fn init(namespace: *Namespace, hal: Hal, arena: std.mem.Allocator) Interpreter { pub fn init(namespace: *Namespace, hal: Hal, arena: std.mem.Allocator) Interpreter {
return .{ .namespace = namespace, .hal = hal, .arena = arena }; return .{ .namespace = namespace, .hal = hal, .arena = arena };
@@ -157,6 +165,7 @@ pub const Interpreter = struct {
/// Evaluate a namespace object: invoke a Method, read a Name's value, or read a /// Evaluate a namespace object: invoke a Method, read a Name's value, or read a
/// Field. Resets per-evaluation runtime state first. /// Field. Resets per-evaluation runtime state first.
pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object { pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object {
self.notify_count = 0;
self.dynamic_overrides.clearRetainingCapacity(); self.dynamic_overrides.clearRetainingCapacity();
self.fields.clearRetainingCapacity(); self.fields.clearRetainingCapacity();
return self.invoke(node, args); return self.invoke(node, args);
@@ -267,6 +276,8 @@ pub const Interpreter = struct {
}, },
opcode.to_buffer_opcode => try self.passThroughUnary(current, frame), opcode.to_buffer_opcode => try self.passThroughUnary(current, frame),
opcode.notify_opcode => try self.notify(current, frame),
opcode.extended_opcode_prefix => try self.ext(current, frame), opcode.extended_opcode_prefix => try self.ext(current, frame),
// CreateXField: source, index, name (bit widths differ by op) // CreateXField: source, index, name (bit widths differ by op)
@@ -542,6 +553,36 @@ pub const Interpreter = struct {
try self.storeInto(current, frame, value); try self.storeInto(current, frame, value);
} }
/// Notify(SuperName, NotifyValue): resolve the named device, evaluate the
/// code, and record the pair for the caller to dispatch. AML control flow
/// continues (Notify returns nothing).
fn notify(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const lead = current.peek() orelse return error.Truncated;
var target: ?*Node = null;
if (isNameStart(lead)) {
const name_path = try current.nameString();
target = self.namespace.resolve(frame.scope, name_path.rooted, name_path.parents, name_path.slice());
} else {
// A non-name SuperName (Local/Arg holding a reference).
const obj = try self.term(current, frame);
if (obj == .reference) target = obj.reference;
}
const code = try self.evaluateInteger(current, frame);
if (target) |node| {
if (self.notify_count < self.notify_queue.len) {
self.notify_queue[self.notify_count] = .{ .node = node, .code = code };
self.notify_count += 1;
}
}
return .uninitialized;
}
/// The Notify events the last `evaluate` produced. Valid until the next
/// `evaluate` clears the queue.
pub fn takeNotifications(self: *Interpreter) []const NotifyEvent {
return self.notify_queue[0..self.notify_count];
}
fn storeInto(self: *Interpreter, current: *Cursor, frame: *Frame, value: Object) Error!void { fn storeInto(self: *Interpreter, current: *Cursor, frame: *Frame, value: Object) Error!void {
const lead = current.peek() orelse return error.Truncated; const lead = current.peek() orelse return error.Truncated;
if (isNameStart(lead)) { if (isNameStart(lead)) {
+11 -11
View File
@@ -72,17 +72,17 @@ fn modifierWord(modifiers: scancode.ModifierSnapshot) u32 {
pub fn main(init: runtime.process.Init) void { pub fn main(init: runtime.process.Init) void {
const hid = init.arguments.get(1).?; const hid = init.arguments.get(1).?;
if (hid.len == 0) { if (hid.len == 0) {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no HID argument\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: no HID argument\n");
return; return;
} }
writeLine("system/drivers/ps2-bus/keyboard: starting for hid {s}\n", .{hid}); writeLine("/system/drivers/ps2-bus/keyboard: starting for hid {s}\n", .{hid});
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: out of memory\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: out of memory\n");
return; return;
}; };
if (device.findDeviceDescriptorByHid(buffer, hid) == null) { if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
writeLine("system/drivers/ps2-bus/keyboard: no device for hid {s}\n", .{hid}); writeLine("/system/drivers/ps2-bus/keyboard: no device for hid {s}\n", .{hid});
return; return;
} }
@@ -90,38 +90,38 @@ pub fn main(init: runtime.process.Init) void {
// absent (as today) it defaults to us. // absent (as today) it defaults to us.
const layout_name = init.arguments.get(2) orelse "us"; const layout_name = init.arguments.get(2) orelse "us";
const layout = xkb.byName(layout_name) orelse xkb.us; const layout = xkb.byName(layout_name) orelse xkb.us;
writeLine("system/drivers/ps2-bus/keyboard: layout {s}\n", .{layout.name}); writeLine("/system/drivers/ps2-bus/keyboard: layout {s}\n", .{layout.name});
// Attach to the bus: hand it our endpoint, and it forwards every byte the // Attach to the bus: hand it our endpoint, and it forwards every byte the
// keyboard sends (it owns the controller; we own the decoding). // keyboard sends (it owns the controller; we own the decoding).
const bus = lookupBus() orelse { const bus = lookupBus() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n");
return; return;
}; };
const endpoint = ipc.createIpcEndpoint() orelse { const endpoint = ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no endpoint\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: no endpoint\n");
return; return;
}; };
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) }; var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) };
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined; var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch { const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach call failed\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: attach call failed\n");
return; return;
}; };
if (attached.len < @sizeOf(ps2.AttachReply) or if (attached.len < @sizeOf(ps2.AttachReply) or
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok)) std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
{ {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach refused\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: attach refused\n");
return; return;
} }
// Broadcast keyboard events through the input service so programs can listen // Broadcast keyboard events through the input service so programs can listen
// for them (docs/input.md). // for them (docs/input.md).
var source = runtime.input.connectSource() orelse { var source = runtime.input.connectSource() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: input service unavailable\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: input service unavailable\n");
return; return;
}; };
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n"); _ = runtime.system.write("/system/drivers/ps2-bus/keyboard: ok\n");
var decoder = scancode.Decoder{}; var decoder = scancode.Decoder{};
var state = scancode.KeyboardState{}; var state = scancode.KeyboardState{};
+10 -10
View File
@@ -51,50 +51,50 @@ pub fn main(init: runtime.process.Init) void {
const hid = init.arguments.get(1).?; const hid = init.arguments.get(1).?;
if (hid.len == 0) { if (hid.len == 0) {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no HID argument\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: no HID argument\n");
return; return;
} }
writeLine("system/drivers/ps2-bus/mouse: starting for hid {s}\n", .{hid}); writeLine("/system/drivers/ps2-bus/mouse: starting for hid {s}\n", .{hid});
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: out of memory\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: out of memory\n");
return; return;
}; };
if (device.findDeviceDescriptorByHid(buffer, hid) == null) { if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
writeLine("system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid}); writeLine("/system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid});
return; return;
} }
// Attach to the bus: hand it our endpoint, and it forwards every byte the // Attach to the bus: hand it our endpoint, and it forwards every byte the
// mouse sends (it owns the controller; we own the decoding). // mouse sends (it owns the controller; we own the decoding).
const bus = lookupBus() orelse { const bus = lookupBus() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ps2-bus service unavailable\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: ps2-bus service unavailable\n");
return; return;
}; };
const endpoint = ipc.createIpcEndpoint() orelse { const endpoint = ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no endpoint\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: no endpoint\n");
return; return;
}; };
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.mouse) }; var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.mouse) };
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined; var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch { const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: attach call failed\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: attach call failed\n");
return; return;
}; };
if (attached.len < @sizeOf(ps2.AttachReply) or if (attached.len < @sizeOf(ps2.AttachReply) or
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok)) std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
{ {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: attach refused\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: attach refused\n");
return; return;
} }
// Broadcast mouse events through the input service so programs can listen // Broadcast mouse events through the input service so programs can listen
// for them (docs/input.md). // for them (docs/input.md).
var source = runtime.input.connectSource() orelse { var source = runtime.input.connectSource() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: input service unavailable\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: input service unavailable\n");
return; return;
}; };
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n"); _ = runtime.system.write("/system/drivers/ps2-bus/mouse: ok\n");
var assembler = mouse_packet.Assembler{}; var assembler = mouse_packet.Assembler{};
var buttons: u32 = 0; var buttons: u32 = 0;
+32 -32
View File
@@ -30,19 +30,19 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
/// attaches, or null if nothing was spawned. /// attaches, or null if nothing was spawned.
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType { fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType {
const device_type = controller.identifyDevice(port) orelse { const device_type = controller.identifyDevice(port) orelse {
writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)}); writeLine("/system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
return null; return null;
}; };
const driver_name = device_type.driverName() orelse { const driver_name = device_type.driverName() orelse {
writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)}); writeLine("/system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
return null; return null;
}; };
const hid = device_type.hid() orelse ""; const hid = device_type.hid() orelse "";
if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) { if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) {
writeLine("system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name }); writeLine("/system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name });
return device_type; return device_type;
} }
writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name}); writeLine("/system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
return null; return null;
} }
@@ -83,7 +83,7 @@ fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize {
const device_type = maybe_type orelse continue; const device_type = maybe_type orelse continue;
if (@intFromEnum(device_type) != request.device_type) continue; if (@intFromEnum(device_type) != request.device_type) continue;
port_endpoints[port_index] = endpoint; port_endpoints[port_index] = endpoint;
writeLine("system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)}); writeLine("/system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)});
return reply.write(out, .ok); return reply.write(out, .ok);
} }
return reply.write(out, .no_such_device); return reply.write(out, .no_such_device);
@@ -91,7 +91,7 @@ fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize {
pub fn main() void { pub fn main() void {
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("system/drivers/ps2-bus: out of memory\n"); _ = runtime.system.write("/system/drivers/ps2-bus: out of memory\n");
return; return;
}; };
@@ -103,16 +103,16 @@ pub fn main() void {
// is on which port is decided later by identify, not by this HID. // is on which port is decided later by identify, not by this HID.
const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_keyboard.hid()); const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_keyboard.hid());
if (maybe_controller_device_descriptor) |controller_device_descriptor| { if (maybe_controller_device_descriptor) |controller_device_descriptor| {
_ = runtime.system.write("system/drivers/ps2-bus: found PS/2 controller\n"); _ = runtime.system.write("/system/drivers/ps2-bus: found PS/2 controller\n");
_ = runtime.system.write("system/drivers/ps2-bus: initializing controller\n"); _ = runtime.system.write("/system/drivers/ps2-bus: initializing controller\n");
if (!device.claim(controller_device_descriptor.id)) { if (!device.claim(controller_device_descriptor.id)) {
_ = runtime.system.write("system/drivers/ps2-bus: unable to claim controller \n"); _ = runtime.system.write("/system/drivers/ps2-bus: unable to claim controller \n");
return; return;
} }
const controller = ps2.Controller.init(controller_device_descriptor) orelse { const controller = ps2.Controller.init(controller_device_descriptor) orelse {
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller is missing its IO ports\n");
return; return;
}; };
maybe_controller = controller; maybe_controller = controller;
@@ -123,7 +123,7 @@ pub fn main() void {
controller.flushOutputBuffer(); controller.flushOutputBuffer();
const current = controller.readConfigurationByte() orelse { const current = controller.readConfigurationByte() orelse {
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller configuration timed out\n");
return; return;
}; };
@@ -132,49 +132,49 @@ pub fn main() void {
ps2.configuration_first_port_translation); ps2.configuration_first_port_translation);
if (controller.writeConfigurationByte(update) == null) { if (controller.writeConfigurationByte(update) == null) {
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller configuration timed out\n");
return; return;
} }
if (controller.performSelfTest()) |reply| { if (controller.performSelfTest()) |reply| {
if (reply != ps2.response_controller_test_passed) { if (reply != ps2.response_controller_test_passed) {
_ = runtime.system.write("system/drivers/ps2-bus: perform controller self test failed\n"); _ = runtime.system.write("/system/drivers/ps2-bus: perform controller self test failed\n");
return; return;
} }
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: controller self test timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller self test timed out\n");
return; return;
} }
has_two_channels = controller.hasTwoChannels() orelse { has_two_channels = controller.hasTwoChannels() orelse {
_ = runtime.system.write("system/drivers/ps2-bus: controller channels timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller channels timed out\n");
return; return;
}; };
if (has_two_channels) { if (has_two_channels) {
_ = runtime.system.write("system/drivers/ps2-bus: has two channels\n"); _ = runtime.system.write("/system/drivers/ps2-bus: has two channels\n");
// keep the bus quiet until we have tested the ports and are ready to use them // keep the bus quiet until we have tested the ports and are ready to use them
controller.disablePort(.two); controller.disablePort(.two);
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: has one channel\n"); _ = runtime.system.write("/system/drivers/ps2-bus: has one channel\n");
} }
// interface tests: always test port 1, test port 2 only if it exists // interface tests: always test port 1, test port 2 only if it exists
const port_one_works = (controller.testPort(.one) orelse { const port_one_works = (controller.testPort(.one) orelse {
_ = runtime.system.write("system/drivers/ps2-bus: port 1 test timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: port 1 test timed out\n");
return; return;
}) == ps2.response_port_test_passed; }) == ps2.response_port_test_passed;
var port_two_works = false; var port_two_works = false;
if (has_two_channels) { if (has_two_channels) {
port_two_works = (controller.testPort(.two) orelse { port_two_works = (controller.testPort(.two) orelse {
_ = runtime.system.write("system/drivers/ps2-bus: port 2 test timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: port 2 test timed out\n");
return; return;
}) == ps2.response_port_test_passed; }) == ps2.response_port_test_passed;
} }
if (!port_one_works and !port_two_works) { if (!port_one_works and !port_two_works) {
_ = runtime.system.write("system/drivers/ps2-bus: no usable ports\n"); _ = runtime.system.write("/system/drivers/ps2-bus: no usable ports\n");
return; return;
} }
@@ -188,16 +188,16 @@ pub fn main() void {
// abort bring-up of the other one // abort bring-up of the other one
if (port_one_works) { if (port_one_works) {
if (controller.resetDevice(.one)) |passed| { if (controller.resetDevice(.one)) |passed| {
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset failed\n"); if (!passed) _ = runtime.system.write("/system/drivers/ps2-bus: port 1 device reset failed\n");
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: port 1 device reset timed out\n");
} }
} }
if (port_two_works) { if (port_two_works) {
if (controller.resetDevice(.two)) |passed| { if (controller.resetDevice(.two)) |passed| {
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset failed\n"); if (!passed) _ = runtime.system.write("/system/drivers/ps2-bus: port 2 device reset failed\n");
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: port 2 device reset timed out\n");
} }
} }
@@ -207,13 +207,13 @@ pub fn main() void {
if (port_one_works) port_device_types[@intFromEnum(ps2.Port.one)] = spawnIdentifiedDriver(controller, .one); if (port_one_works) port_device_types[@intFromEnum(ps2.Port.one)] = spawnIdentifiedDriver(controller, .one);
if (port_two_works) port_device_types[@intFromEnum(ps2.Port.two)] = spawnIdentifiedDriver(controller, .two); if (port_two_works) port_device_types[@intFromEnum(ps2.Port.two)] = spawnIdentifiedDriver(controller, .two);
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n"); _ = runtime.system.write("/system/drivers/ps2-bus: no PS/2 controller found\n");
return; return;
} }
const controller = maybe_controller.?; const controller = maybe_controller.?;
const interrupt_index = maybe_interrupt_index orelse { const interrupt_index = maybe_interrupt_index orelse {
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IRQ\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller is missing its IRQ\n");
return; return;
}; };
@@ -221,11 +221,11 @@ pub fn main() void {
// well-known id so the children can find it, the way input subscribers find // well-known id so the children can find it, the way input subscribers find
// the input service. // the input service.
const endpoint = ipc.createIpcEndpoint() orelse { const endpoint = ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("system/drivers/ps2-bus: no endpoint\n"); _ = runtime.system.write("/system/drivers/ps2-bus: no endpoint\n");
return; return;
}; };
if (!ipc.register(.ps2_bus, endpoint)) { if (!ipc.register(.ps2_bus, endpoint)) {
_ = runtime.system.write("system/drivers/ps2-bus: register failed\n"); _ = runtime.system.write("/system/drivers/ps2-bus: register failed\n");
return; return;
} }
@@ -234,7 +234,7 @@ pub fn main() void {
// let the controller raise them — an interrupt with nobody bound is lost. // let the controller raise them — an interrupt with nobody bound is lost.
controller.drainOutputBuffer(); controller.drainOutputBuffer();
if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) { if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) {
_ = runtime.system.write("system/drivers/ps2-bus: irq_bind failed\n"); _ = runtime.system.write("/system/drivers/ps2-bus: irq_bind failed\n");
return; return;
} }
@@ -253,21 +253,21 @@ pub fn main() void {
.gsi = descriptor.resources[auxiliary_index].start, .gsi = descriptor.resources[auxiliary_index].start,
}; };
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: auxiliary irq_bind failed\n"); _ = runtime.system.write("/system/drivers/ps2-bus: auxiliary irq_bind failed\n");
} }
} }
} }
} }
var configuration = controller.readConfigurationByte() orelse { var configuration = controller.readConfigurationByte() orelse {
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n"); _ = runtime.system.write("/system/drivers/ps2-bus: controller configuration timed out\n");
return; return;
}; };
if (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit(); if (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit();
if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit(); if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit();
_ = controller.writeConfigurationByte(configuration); _ = controller.writeConfigurationByte(configuration);
_ = runtime.system.write("system/drivers/ps2-bus: ok\n"); _ = runtime.system.write("/system/drivers/ps2-bus: ok\n");
// The forwarding loop: an IRQ1 notification drains the output buffer, routing // The forwarding loop: an IRQ1 notification drains the output buffer, routing
// each byte to the attached driver of the port it came from; a client message // each byte to the attached driver of the port it came from; a client message
+66
View File
@@ -308,9 +308,75 @@ fn onSci() void {
_ = runtime.system.write("power: button pressed\n"); _ = runtime.system.write("power: button pressed\n");
publishButton(); publishButton();
} }
handleGpe();
_ = device.irqAck(node_id, sci_resource_index); _ = 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 { fn publishButton() void {
const event = power.EventMessage{ .event = @intFromEnum(power.Event.power_button) }; const event = power.EventMessage{ .event = @intFromEnum(power.Event.power_button) };
publishEvent(std.mem.asBytes(&event)); publishEvent(std.mem.asBytes(&event));