diff --git a/build.zig b/build.zig index 1f404f5..88406b9 100644 --- a/build.zig +++ b/build.zig @@ -583,6 +583,7 @@ pub fn build(b: *std.Build) void { "system/devices/device-abi.zig", "system/devices/pci-class.zig", // class/subclass/prog-IF 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-ids.zig", // class/subclass/protocol code assignments "library/mmio/mmio.zig", // barriers assemble + registers round-trip diff --git a/docs/m21-plan.md b/docs/m21-plan.md index 7ad0928..af6b946 100644 --- a/docs/m21-plan.md +++ b/docs/m21-plan.md @@ -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 `power-button` injects a real `system_powerdown` via QMP; initial-ramdisk timeout 30→60s for the service's added boot work; suite 59/59). -- [ ] **M21.2** — Notify + GPE dispatch: interpreter handles `notify_opcode` - into a bounded queue drained after evaluate(); on GPE status bits the - service evaluates `\_GPE._Lxx`/`_Exx`, maps notified nodes to events - (PNP0C0A→battery, ACPI0003→ac, PNP0C0D→lid, else generic), clears - GPE_STS, acks. EC `_Qxx` explicitly out (hardware track). Host unit - tests for Notify in aml.zig; aml.zig joins the `zig build test` loop. +- [x] **M21.2** — Notify + GPE dispatch (interpreter handles `notify_opcode` + into a bounded queue, cleared per-evaluate, drained via + `takeNotifications`; the service walks GPE status/enable bytes, evaluates + `\_GPE._Lxx`/`_Exx` per active bit, maps notified nodes to events + (battery/ac/lid/generic), clears GPE_STS write-1, acks. EC `_Qxx` out. + 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 + exit endpoint), binds signals, subscribes to `.power`; on `power_button` logs `init: shutting down`, runs `stop(child, 2000, endpoint)` in diff --git a/system/devices/aml/aml.zig b/system/devices/aml/aml.zig index 8ad4b66..8206c07 100644 --- a/system/devices/aml/aml.zig +++ b/system/devices/aml/aml.zig @@ -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 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); +} diff --git a/system/devices/aml/interpreter.zig b/system/devices/aml/interpreter.zig index bef89d8..a75cc03 100644 --- a/system/devices/aml/interpreter.zig +++ b/system/devices/aml/interpreter.zig @@ -141,6 +141,9 @@ const Frame = struct { /// A CreateField binding: a name that indexes into a buffer object. 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 { namespace: *Namespace, hal: Hal, @@ -149,6 +152,11 @@ pub const Interpreter = struct { dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{}, /// CreateField bindings active for the current evaluation. 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 { 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 /// Field. Resets per-evaluation runtime state first. pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object { + self.notify_count = 0; self.dynamic_overrides.clearRetainingCapacity(); self.fields.clearRetainingCapacity(); return self.invoke(node, args); @@ -267,6 +276,8 @@ pub const Interpreter = struct { }, 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), // CreateXField: source, index, name (bit widths differ by op) @@ -542,6 +553,36 @@ pub const Interpreter = struct { 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 { const lead = current.peek() orelse return error.Truncated; if (isNameStart(lead)) { diff --git a/system/drivers/ps2-bus/keyboard.zig b/system/drivers/ps2-bus/keyboard.zig index a38f826..2b5ba0f 100644 --- a/system/drivers/ps2-bus/keyboard.zig +++ b/system/drivers/ps2-bus/keyboard.zig @@ -72,17 +72,17 @@ fn modifierWord(modifiers: scancode.ModifierSnapshot) u32 { pub fn main(init: runtime.process.Init) void { const hid = init.arguments.get(1).?; 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; } - 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 { - _ = 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; }; 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; } @@ -90,38 +90,38 @@ pub fn main(init: runtime.process.Init) void { // absent (as today) it defaults to us. const layout_name = init.arguments.get(2) orelse "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 // keyboard sends (it owns the controller; we own the decoding). 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; }; 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; }; var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) }; var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined; 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; }; if (attached.len < @sizeOf(ps2.AttachReply) or 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; } // Broadcast keyboard events through the input service so programs can listen // for them (docs/input.md). 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; }; - _ = 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 state = scancode.KeyboardState{}; diff --git a/system/drivers/ps2-bus/mouse.zig b/system/drivers/ps2-bus/mouse.zig index c70d0a0..c106eab 100644 --- a/system/drivers/ps2-bus/mouse.zig +++ b/system/drivers/ps2-bus/mouse.zig @@ -51,50 +51,50 @@ pub fn main(init: runtime.process.Init) void { const hid = init.arguments.get(1).?; 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; } - 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 { - _ = 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; }; 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; } // Attach to the bus: hand it our endpoint, and it forwards every byte the // mouse sends (it owns the controller; we own the decoding). 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; }; 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; }; var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.mouse) }; var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined; 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; }; if (attached.len < @sizeOf(ps2.AttachReply) or 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; } // Broadcast mouse events through the input service so programs can listen // for them (docs/input.md). 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; }; - _ = 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 buttons: u32 = 0; diff --git a/system/drivers/ps2-bus/ps2-bus.zig b/system/drivers/ps2-bus/ps2-bus.zig index 2438a92..8f258ad 100644 --- a/system/drivers/ps2-bus/ps2-bus.zig +++ b/system/drivers/ps2-bus/ps2-bus.zig @@ -30,19 +30,19 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void { /// attaches, or null if nothing was spawned. fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType { 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; }; 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; }; const hid = device_type.hid() orelse ""; 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; } - 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; } @@ -83,7 +83,7 @@ fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize { const device_type = maybe_type orelse continue; if (@intFromEnum(device_type) != request.device_type) continue; 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, .no_such_device); @@ -91,7 +91,7 @@ fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize { pub fn main() void { 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; }; @@ -103,16 +103,16 @@ pub fn main() void { // 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()); 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: initializing controller\n"); + _ = runtime.system.write("/system/drivers/ps2-bus: found PS/2 controller\n"); + _ = runtime.system.write("/system/drivers/ps2-bus: initializing controller\n"); 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; } 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; }; maybe_controller = controller; @@ -123,7 +123,7 @@ pub fn main() void { controller.flushOutputBuffer(); 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; }; @@ -132,49 +132,49 @@ pub fn main() void { ps2.configuration_first_port_translation); 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; } if (controller.performSelfTest()) |reply| { 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; } } 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; } 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; }; 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 controller.disablePort(.two); } 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 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; }) == ps2.response_port_test_passed; var port_two_works = false; if (has_two_channels) { 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; }) == ps2.response_port_test_passed; } 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; } @@ -188,16 +188,16 @@ pub fn main() void { // abort bring-up of the other one if (port_one_works) { 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 { - _ = 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 (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 { - _ = 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_two_works) port_device_types[@intFromEnum(ps2.Port.two)] = spawnIdentifiedDriver(controller, .two); } 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; } const controller = maybe_controller.?; 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; }; @@ -221,11 +221,11 @@ pub fn main() void { // well-known id so the children can find it, the way input subscribers find // the input service. 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; }; 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; } @@ -234,7 +234,7 @@ pub fn main() void { // let the controller raise them — an interrupt with nobody bound is lost. controller.drainOutputBuffer(); 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; } @@ -253,21 +253,21 @@ pub fn main() void { .gsi = descriptor.resources[auxiliary_index].start, }; } 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 { - _ = 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; }; if (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit(); if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit(); _ = 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 // each byte to the attached driver of the port it came from; a client message diff --git a/system/services/acpi/acpi.zig b/system/services/acpi/acpi.zig index 6132dfd..df8542c 100644 --- a/system/services/acpi/acpi.zig +++ b/system/services/acpi/acpi.zig @@ -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));