Files
danos/system/drivers/usb-hid/keyboard.zig
T
Daniel Samson ea470afe84 runtime: consolidate the device-manager hello into runtime.device_manager
Every supervised driver owes the device manager a hello at startup, and
the retry-lookup-call-check for it had been copied into five drivers:
usb.helloManager (misfiled in the USB client) plus hand-rolled twins in
display, virtio-gpu, pci-bus, and usb-xhci-bus.

Extract it once as a runtime client, runtime.device_manager.hello(role,
device_id) ?Handle — returns the manager endpoint (bus drivers keep it to
report children through), null when there is no manager or it refused the
handshake, and logs the outcome itself so each call site is one line.

Also correct two roles while collapsing their calls: virtio-gpu and the
display driver each claim one PCI function and report no children, so they
are Role.device, not Role.bus. The manager ignores role today, so this is
cosmetic, but it matches the protocol's own definition (bus = reports
children via child_added).

Behavior-preserving otherwise: pci-bus and usb-xhci-bus move their hello
logging from raw serial writes to std.log, which the kernel renders with
the same "<path>: " prefix, so driver-restart still matches
"usb-xhci-bus: hello acknowledged". zig build clean; 8 QEMU cases pass
(driver-restart, pci-scan, usb-hid, usb-storage, virtio-gpu,
display-reattach, device-list, device-manager).
2026-07-22 19:27:20 +01:00

171 lines
7.9 KiB
Zig

//! 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
//! (`runtime.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 runtime = @import("runtime");
const usb_abi = @import("usb-abi");
const xkb = @import("xkeyboard-config");
const hid = @import("hid-report.zig");
const ipc = runtime.ipc;
const process = runtime.process;
const input_protocol = runtime.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: runtime.process.Init) void {
const argument = init.arguments.get(1) orelse {
_ = runtime.system.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 (runtime.device_manager.hello(.device, device_id) == null) return;
var device = runtime.usb.open(device_id) orelse {
std.log.info("could not open device {d}", .{device_id});
return;
};
const endpoint = device.findEndpoint(runtime.usb.transfer_type_interrupt, true) orelse {
_ = runtime.system.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)) {
_ = runtime.system.write("/system/drivers/usb-hid/keyboard: interrupt subscribe failed\n");
return;
}
var source = runtime.input.connectSource() orelse {
_ = runtime.system.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() or got.len < @sizeOf(runtime.usb.InterruptReport)) continue;
const message = std.mem.bytesToValue(runtime.usb.InterruptReport, receive[0..@sizeOf(runtime.usb.InterruptReport)]);
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)) {
_ = runtime.system.write(&[1]u8{@intCast(character)});
}
}
},
.released => {
_ = source.publishKeyboardEvent(.{
.kind = @intFromEnum(input_protocol.EventKind.key_up),
.keycode = transition.usage,
.character = 0,
.modifiers = modifier_word,
});
},
}
}
}
}