Wire real PS/2 scancodes through to input events and characters
Replace the keyboard driver's synthetic stream with the real path: - ps2-bus binds IRQ1 (interrupt bits set only after the bind), drains port 0x60 on each interrupt, and forwards every byte to the attached child driver over async ipc_send, routed by the status register's auxiliary-output bit. Children attach via the new well-known ps2_bus service, handing over their endpoint as a capability. - scancode.zig (new, pure, host-tested): scancode set 2 -> USB HID usage decoding (F0/E0/E1 prefix state machine) plus keyboard state — pressed-key bitmap, typematic-repeat classification, modifier and caps-lock tracking. - keyboard.zig decodes the forwarded stream and publishes real key_down/key_press/key_up events, filling key_press characters via xkeyboard-config (layout from argv[2], default us) and synthesizing ASCII control characters for Enter/Tab/Backspace/Escape. - protocol.zig names the full HID usage set in Keycode; build.zig threads the xkeyboard-config module into user binaries. Verified end to end in QEMU via monitor sendkey: shift, caps lock, and control-character synthesis all decode correctly. The mouse driver still publishes its synthetic stream; attaching it to the bus the same way is the follow-up.
This commit is contained in:
@@ -1,19 +1,74 @@
|
||||
//! PS/2 Keyboard Driver
|
||||
//!
|
||||
//! Spawned by the ps2-bus driver once the controller is initialized and port 1
|
||||
//! 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]; we use it to locate our own device descriptor.
|
||||
//! 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) {
|
||||
@@ -31,25 +86,95 @@ pub fn main(init: runtime.process.Init) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// The 8042 ports (0x60/0x64) and this keyboard's IRQ1 both live on the same
|
||||
// PNP0303 node, which the ps2-bus driver exclusively owns — so the keyboard is
|
||||
// served through the bus and does not claim the controller itself.
|
||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: served by ps2-bus (controller owned by bus)\n");
|
||||
// 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});
|
||||
|
||||
// Broadcast keyboard events through the input service so programs can listen for them
|
||||
// (docs/input.md). Until the bus reads real IRQ1 scancodes and hands them here (a
|
||||
// follow-up), we publish the same synthetic stand-in stream the demo source uses — the
|
||||
// fan-out path is real, only the source of the bytes is placeholder.
|
||||
// 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 != 0)
|
||||
{
|
||||
_ = 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 step: usize = 0;
|
||||
while (true) : (step +%= 1) {
|
||||
_ = source.publishKeyboardEvent(runtime.input.syntheticKeyEvent(step));
|
||||
runtime.system.sleep(200);
|
||||
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,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user