//! 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 device = @import("driver"); const ipc = @import("ipc"); const process = @import("process"); const time = @import("time"); const input = @import("input-client"); const memory = @import("memory"); const logging = @import("logging"); const xkb = @import("xkeyboard-config"); const ps2 = @import("ps2-library.zig"); const scancode = @import("scancode.zig"); const input_protocol = @import("input-protocol"); /// 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; time.sleepMillis(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(input_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 |= input_protocol.modifier_shift; if (modifiers.control) word |= input_protocol.modifier_control; if (modifiers.alt) word |= input_protocol.modifier_alt; return word; } pub fn main(init: process.Init) void { const hid = init.arguments.get(1).?; if (hid.len == 0) { _ = logging.write("/system/drivers/ps2-bus/keyboard: no HID argument\n"); return; } std.log.info("starting for hid {s}", .{hid}); const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch { _ = logging.write("/system/drivers/ps2-bus/keyboard: out of memory\n"); return; }; if (device.findDeviceDescriptorByHid(buffer, hid) == null) { std.log.info("no device for hid {s}", .{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; std.log.info("layout {s}", .{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 { _ = logging.write("/system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n"); return; }; const endpoint = ipc.createIpcEndpoint() orelse { _ = logging.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 { _ = logging.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)) { _ = logging.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 = input.connectSource() orelse { _ = logging.write("/system/drivers/ps2-bus/keyboard: input service unavailable\n"); return; }; _ = logging.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(input_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(input_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(input_protocol.EventKind.key_press), .keycode = key.usage, .character = character, .modifiers = modifiers, }); } }, .released => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_up), .keycode = key.usage, .character = 0, .modifiers = modifiers, }); }, } } }