//! PS/2 Keyboard Driver //! //! Spawned by the ps2-bus driver once the controller is initialized and the port //! has passed its interface test and device reset. The bus driver hands us our //! device HID as argv[1] and, optionally, a layout name (`"us"`, `"gb"`, ...) as //! argv[2]. //! //! The 8042's ports (0x60/0x64) and IRQ1 live on the PNP0303 node, which the //! ps2-bus driver exclusively owns — so this driver never touches the hardware. //! Instead it **attaches** to the bus (handing over its endpoint as a capability) //! and receives every scancode byte as a forwarded asynchronous message. Each byte //! feeds the set-2 decoder; a decoded key becomes input-protocol events: //! //! scancode byte -> HID usage keycode -> key_down / key_up //! -> xkeyboard-config -> character -> key_press const std = @import("std"); const runtime = @import("runtime"); const xkb = @import("xkeyboard-config"); const ps2 = @import("ps2-library.zig"); const scancode = @import("scancode.zig"); const device = runtime.device; const ipc = runtime.ipc; const protocol = runtime.input_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); } /// Look up the ps2-bus service, retrying while the bus (which spawned us before /// registering) is still coming up. fn lookupBus() ?ipc.Handle { var attempts: usize = 0; while (attempts < 100) : (attempts += 1) { if (ipc.lookup(.ps2_bus)) |handle| return handle; runtime.system.sleep(50); } return null; } /// The character a pressed key produces under `modifiers`, or 0 for none. The /// layout lookup answers for printable keys; the keys whose keysym has no Unicode /// mapping but that every consumer still expects as a character (Enter, Tab, /// Backspace, Escape) are given their ASCII control characters here. fn characterFor(layout: *const xkb.Layout, usage: u8, modifiers: scancode.ModifierSnapshot) u32 { const mapping = xkb.map(layout, usage, .{ .shift = modifiers.shift, .caps_lock = modifiers.caps_lock, .level3 = modifiers.right_alt, .control = modifiers.control, }); if (mapping.character) |character| return character; return switch (@as(protocol.Keycode, @enumFromInt(usage))) { .enter, .keypad_enter => '\n', .tab => '\t', .backspace => 0x08, .escape => 0x1B, else => 0, }; } /// The input protocol's modifier word for a snapshot. fn modifierWord(modifiers: scancode.ModifierSnapshot) u32 { var word: u32 = 0; if (modifiers.shift) word |= protocol.modifier_shift; if (modifiers.control) word |= protocol.modifier_control; if (modifiers.alt) word |= protocol.modifier_alt; return word; } 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"); return; } 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"); return; }; if (device.findDeviceDescriptorByHid(buffer, hid) == null) { writeLine("system/drivers/ps2-bus/keyboard: no device for hid {s}\n", .{hid}); return; } // The layout is a spawn argument so a later settings source can choose it; // 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}); // 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"); return; }; const endpoint = ipc.createIpcEndpoint() orelse { _ = 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"); 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"); 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"); return; }; _ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n"); var decoder = scancode.Decoder{}; var state = scancode.KeyboardState{}; var receive: [@sizeOf(ps2.ForwardedByte)]u8 = undefined; while (true) { const got = ipc.replyWait(endpoint, &.{}, &receive, null); if (!got.isMessage() or got.len < @sizeOf(ps2.ForwardedByte)) continue; const forwarded = std.mem.bytesToValue(ps2.ForwardedByte, receive[0..@sizeOf(ps2.ForwardedByte)]); const key = decoder.feed(@intCast(forwarded.byte & 0xFF)) orelse continue; const transition = state.apply(key); const modifiers = modifierWord(transition.modifiers); switch (transition.action) { .pressed => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(protocol.EventKind.key_down), .keycode = key.usage, .character = 0, .modifiers = modifiers, }); const character = characterFor(layout, key.usage, transition.modifiers); if (character != 0) { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(protocol.EventKind.key_press), .keycode = key.usage, .character = character, .modifiers = modifiers, }); } }, // Typematic repeat: the key did not physically go down again, so no // key_down — but it keeps producing its character. .repeated => { const character = characterFor(layout, key.usage, transition.modifiers); if (character != 0) { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(protocol.EventKind.key_press), .keycode = key.usage, .character = character, .modifiers = modifiers, }); } }, .released => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(protocol.EventKind.key_up), .keycode = key.usage, .character = 0, .modifiers = modifiers, }); }, } } } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; }