diff --git a/system/drivers/ps2-bus/ps2-bus.zig b/system/drivers/ps2-bus/ps2-bus.zig index fcc0c72..9911562 100644 --- a/system/drivers/ps2-bus/ps2-bus.zig +++ b/system/drivers/ps2-bus/ps2-bus.zig @@ -114,10 +114,9 @@ pub fn main() void { _ = logging.write("/system/drivers/ps2-bus: found PS/2 controller\n"); _ = logging.write("/system/drivers/ps2-bus: initializing controller\n"); - device.claim(controller_device_descriptor.id) catch |e| { - std.log.warn("unable to claim controller: {s}", .{@errorName(e)}); - return; - }; + // The controller node arrived with the spawn — the manager holds the hardware + // and names it in the call that creates this process + // (docs/os-development/device-authority.md). Nothing to claim. const controller = ps2.Controller.init(controller_device_descriptor) orelse { _ = logging.write("/system/drivers/ps2-bus: controller is missing its IO ports\n"); @@ -255,18 +254,21 @@ pub fn main() void { if (port_device_types[@intFromEnum(ps2.Port.two)] != null) { if (ps2.findMouseDescriptor(buffer)) |descriptor| { if (findInterruptResourceIndex(descriptor)) |auxiliary_index| { - if (device.claim(descriptor.id)) |_| { - if (device.irqBind(descriptor.id, auxiliary_index, endpoint)) { - maybe_auxiliary_interrupt = .{ - .device_id = descriptor.id, - .interrupt_index = auxiliary_index, - .gsi = descriptor.resources[auxiliary_index].start, - }; - } else { - _ = logging.write("/system/drivers/ps2-bus: auxiliary irq_bind failed\n"); - } - } else |e| { - std.log.warn("auxiliary claim failed: {s}", .{@errorName(e)}); + // The mouse node is a *second* device for this one instance — the + // 8042 is one controller with two ports, so it cannot be split across + // two processes. It is transferred to us after the spawn, which is + // safe because we only reach it here, long after the controller + // handshakes and identify. irq_bind is the proof we hold it: it is + // gated on ownership, so a failure here means the handover has not + // landed rather than a hardware problem. + if (device.irqBind(descriptor.id, auxiliary_index, endpoint)) { + maybe_auxiliary_interrupt = .{ + .device_id = descriptor.id, + .interrupt_index = auxiliary_index, + .gsi = descriptor.resources[auxiliary_index].start, + }; + } else { + _ = logging.write("/system/drivers/ps2-bus: auxiliary irq_bind failed (mouse node not delegated?)\n"); } } } diff --git a/system/services/device-manager/device-manager.zig b/system/services/device-manager/device-manager.zig index 126c2b0..0692992 100644 --- a/system/services/device-manager/device-manager.zig +++ b/system/services/device-manager/device-manager.zig @@ -220,6 +220,13 @@ fn childCountOf(reporter: u32) u32 { /// The driver entry a live process id belongs to. Zero is not a process id here: /// it is what `onDriverExit` writes back to retire an id it has already acted on, /// so a second notification for the same death matches nothing. +fn driverByName(name: []const u8) ?*Driver { + for (&drivers) |*driver| { + if (driver.used and std.mem.eql(u8, driver.name(), name)) return driver; + } + return null; +} + fn driverByProcess(process_id: u32) ?*Driver { if (process_id == 0) return null; for (&drivers) |*driver| { @@ -249,6 +256,7 @@ const delegated_drivers = [_][]const u8{ "usb-xhci-bus", "pci-bus", "virtio-gpu", + "ps2-bus", }; /// Matched on the **last path component**, because a driver reaches this table under @@ -512,9 +520,36 @@ fn onChildAdded(_: void, invocation: Invocation(device_manager_protocol.ChildAdd if (match.ambiguous) std.log.info("/system/configuration/devices.csv: multiple equally-specific rules match the device {s} reported; binding {s}", .{ driver.name(), match.driver }); if (id.bus == .acpi) { - // An hid-matched driver (ps2-bus) is a singleton that finds its - // own devices once spawned — spawn it once, no device assignment. - if (!alreadySupervised(match.driver)) addDriver(match.driver, device_manager_protocol.no_device, false); + // An hid-matched driver is a singleton over one piece of hardware + // described by several nodes: the 8042 is a single controller whose + // I/O ports live under the keyboard node (PNP0303) while the mouse + // is a second node (PNP0F13). Two processes would fight over the + // same 0x60/0x64 registers, so there is exactly one instance — and + // it needs *every* matching device, however many the machine has + // (some have none, some one port, some two). + // + // The first rides the spawn; the rest are transferred to the + // running instance. Late arrival is fine here because the order is + // natural: the instance needs the controller node immediately and + // reaches the mouse only after the 8042 handshakes and identify. + if (!alreadySupervised(match.driver)) { + addDriver(match.driver, device_id, false); + } else if (driverByName(match.driver)) |running| { + if (running.process_id != 0) { + device.claim(device_id) catch |e| switch (e) { + error.AlreadyClaimed => {}, + else => { + std.log.warn("cannot hold device {d} for {s}: {s}", .{ device_id, match.driver, @errorName(e) }); + return status; + }, + }; + device.transfer(device_id, running.process_id) catch |e| { + std.log.warn("could not give device {d} to {s}: {s}", .{ device_id, match.driver, @errorName(e) }); + return status; + }; + std.log.info("delegated device {d} to {s} (already running)", .{ device_id, match.driver }); + } + } } else { // A per-device driver: one instance, the registered id as argv[1]. if (!driverForDevice(device_id)) addDriver(match.driver, device_id, true); diff --git a/test/qemu_test.py b/test/qemu_test.py index 708f373..1fcc6ad 100644 --- a/test/qemu_test.py +++ b/test/qemu_test.py @@ -780,8 +780,16 @@ CASES = [ {"name": "acpi-ps2", "smp": 4, "timeout": 150, + # The 8042 is ONE controller described by two ACPI nodes, so the single ps2-bus + # instance must receive BOTH. The keyboard node rides the spawn — it carries the + # 0x60/0x64 ports and is needed immediately — and the mouse node is transferred to + # the already-running instance, which is safe because it is not touched until after + # the controller handshakes and identify. Two processes cannot split this: they + # would fight over the same registers. "expect": r"discovery: device \d+\s+bus=acpi hid=PNP0303[\s\S]*" - r"device-manager: spawned \S*ps2-bus[\s\S]*" + r"device-manager: delegated device (\d+) to \S*ps2-bus[\s\S]*" + r"device-manager: spawned \S*ps2-bus for device \1[\s\S]*" + r"device-manager: delegated device \d+ to \S*ps2-bus \(already running\)[\s\S]*" r"ps2-bus: keyboard driver attached", "fail": r"DANOS-TEST-RESULT: FAIL"}, # M21.1: the SCI + power button. Boot the manager (which spawns the acpi