//! USB HID boot keyboard driver. //! //! Spawned by the device manager when the xHCI bus driver reports a HID / boot / //! keyboard interface (class 3, subclass 1, protocol 1); its assigned device id //! arrives as argv[1] and an optional layout name ("us", "gb", ...) as argv[2]. //! It owns no hardware: it opens its device through the USB transfer protocol //! (`usb`), asks the device for the boot protocol, subscribes to its //! interrupt-IN endpoint, and turns each 8-byte boot report into input-protocol //! events, published to the input service — the USB analogue of ps2-bus/keyboard. //! //! interrupt report -> hid-report diff -> key_down / key_up //! -> xkeyboard-config -> character -> key_press //! //! Because a USB keyboard's usages ARE the input protocol's keycodes (both are //! HID keyboard page 0x07), the decode is nearly 1:1 — no scancode translation. const std = @import("std"); const ipc = @import("ipc"); const process = @import("process"); const service = @import("service"); const input = @import("input-client"); const device_manager = @import("driver"); const logging = @import("logging"); const usb = @import("usb"); const usb_abi = @import("usb-abi"); const xkb = @import("xkeyboard-config"); const hid = @import("hid-report.zig"); const input_protocol = @import("input-protocol"); // The modifier state a character lookup needs — derived from the report's // modifier byte, plus the driver-tracked caps-lock toggle. const ModifierSnapshot = struct { shift: bool, control: bool, right_alt: bool, caps_lock: bool, }; /// The character a key produces under `modifiers`, or 0 for none — the layout /// lookup for printable keys, with ASCII control characters for the keys every /// consumer expects (Enter, Tab, Backspace, Escape), exactly as ps2-bus/keyboard. fn characterFor(layout: *const xkb.Layout, usage: u8, modifiers: 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, }; } fn modifierWord(modifiers: u8) u32 { var word: u32 = 0; if (modifiers & (hid.modifier_left_shift | hid.modifier_right_shift) != 0) word |= input_protocol.modifier_shift; if (modifiers & (hid.modifier_left_control | hid.modifier_right_control) != 0) word |= input_protocol.modifier_control; if (modifiers & (hid.modifier_left_alt | hid.modifier_right_alt) != 0) word |= input_protocol.modifier_alt; return word; } pub fn main(init: process.Init) void { const argument = init.arguments.get(1) orelse { _ = logging.write("/system/drivers/usb-hid/keyboard: missing device id (argv[1])\n"); return; }; const device_id = std.fmt.parseInt(u64, argument, 10) catch { std.log.info("malformed device id '{s}'", .{argument}); return; }; const layout = xkb.byName(init.arguments.get(2) orelse "us") orelse xkb.us; // Hello the manager first (meet the spawn deadline), then open the device. if (device_manager.hello(.device, device_id) == null) return; var device = usb.open(device_id) orelse { std.log.info("could not open device {d}", .{device_id}); return; }; const endpoint = device.findEndpoint(usb.transfer_type_interrupt, true) orelse { _ = logging.write("/system/drivers/usb-hid/keyboard: no interrupt-IN endpoint\n"); return; }; // Ask for the boot protocol and an indefinite idle (report only on change). _ = device.controlOut(@bitCast(usb_abi.setProtocol(@enumFromInt(device.interface_number), .boot))); _ = device.controlOut(@bitCast(usb_abi.setIdle(@enumFromInt(device.interface_number), 0, 0))); if (!device.subscribeInterrupt(endpoint.address, endpoint.max_packet_size)) { _ = logging.write("/system/drivers/usb-hid/keyboard: interrupt subscribe failed\n"); return; } var source = input.connectSource() orelse { _ = logging.write("/system/drivers/usb-hid/keyboard: input service unavailable\n"); return; }; _ = process.bindSignals(device.endpoint); std.log.info("ok (device {d}, interface {d}, layout {s})", .{ device_id, device.interface_number, layout.name }); var decoder = hid.KeyboardDecoder{}; var caps_lock = false; var receive: [64]u8 = undefined; while (true) { const got = ipc.replyWait(device.endpoint, &.{}, &receive, null); if (!got.isNotification()) continue; if (process.signalsFrom(got.badge)) |signals| { if (signals.has(.terminate)) return; continue; } if (!got.isMessage()) continue; // An `interrupt_report` event packet: the verb in its folded header, the // report after it. Anything else on this endpoint is not ours. const message = usb.reportOf(receive[0..got.len]) orelse continue; if (message.length < @sizeOf(hid.KeyboardReport)) continue; const report = std.mem.bytesToValue(hid.KeyboardReport, message.data[0..@sizeOf(hid.KeyboardReport)]); const transitions = decoder.feed(report); // Caps Lock toggles on its own key-down (a stateful lock, not a modifier). for (transitions.slice()) |transition| { if (transition.kind == .pressed and @as(input_protocol.Keycode, @enumFromInt(transition.usage)) == .caps_lock) caps_lock = !caps_lock; } const modifiers = ModifierSnapshot{ .shift = report.modifiers & (hid.modifier_left_shift | hid.modifier_right_shift) != 0, .control = report.modifiers & (hid.modifier_left_control | hid.modifier_right_control) != 0, .right_alt = report.modifiers & hid.modifier_right_alt != 0, .caps_lock = caps_lock, }; const modifier_word = modifierWord(report.modifiers); for (transitions.slice()) |transition| { switch (transition.kind) { .pressed => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_down), .keycode = transition.usage, .character = 0, .modifiers = modifier_word, }); const character = characterFor(layout, transition.usage, modifiers); if (character != 0) { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_press), .keycode = transition.usage, .character = character, .modifiers = modifier_word, }); // Echo the character to the log — a simple end-to-end // keyboard check on real hardware: type a known phrase, // then read it back from usb-hid-keyboard.log (or watch // it appear live on screen in a -Ddiagnose boot, where // the kernel console is a log sink). Printable ASCII and // newline only; other keys are left to the input service. if (character == '\n' or (character >= 0x20 and character < 0x7F)) { _ = logging.write(&[1]u8{@intCast(character)}); } } }, .released => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_up), .keycode = transition.usage, .character = 0, .modifiers = modifier_word, }); }, } } } }