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:
@@ -13,6 +13,7 @@ const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const ps2 = @import("ps2-library.zig");
|
||||
const device = runtime.device;
|
||||
const ipc = runtime.ipc;
|
||||
|
||||
/// Format one whole log line and emit it in a single `debug_write`, so output
|
||||
/// from the child drivers (which run concurrently) can never interleave with it.
|
||||
@@ -23,22 +24,68 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||
|
||||
/// Ask the device on `port` what it is, then spawn the matching driver from the
|
||||
/// initial-ramdisk, handing it the device's HID as argv[1]. The driver is chosen
|
||||
/// from what the device reports, not from the port number.
|
||||
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) void {
|
||||
/// from what the device reports, not from the port number. Returns the identified
|
||||
/// type so the forwarding loop can route that port's bytes to the driver once it
|
||||
/// attaches, or null if nothing was spawned.
|
||||
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType {
|
||||
const device_type = controller.identifyDevice(port) orelse {
|
||||
writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
|
||||
return;
|
||||
return null;
|
||||
};
|
||||
const driver_name = device_type.driverName() orelse {
|
||||
writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
|
||||
return;
|
||||
return null;
|
||||
};
|
||||
const hid = device_type.hid() orelse "";
|
||||
if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) {
|
||||
writeLine("system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name });
|
||||
} else {
|
||||
writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
|
||||
return device_type;
|
||||
}
|
||||
writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resource index of the controller's IRQ (IRQ1) on the PNP0303 descriptor, found
|
||||
/// the way the ports are found in `Controller.init`.
|
||||
fn findInterruptResourceIndex(descriptor: device.DeviceDescriptor) ?u64 {
|
||||
for (0..descriptor.resource_count) |index| {
|
||||
if (descriptor.resources[index].kind == @intFromEnum(device.ResourceKind.irq)) return index;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Forwarding endpoints of the attached child drivers, indexed by `ps2.Port`.
|
||||
/// Written when a child's `AttachRequest` arrives, read on every forwarded byte.
|
||||
var port_endpoints = [_]?ipc.Handle{ null, null };
|
||||
|
||||
/// Which device type each port identified as, so an attaching child (which knows
|
||||
/// its type, not its port) can be matched to the right port's byte stream.
|
||||
var port_device_types = [_]?ps2.DeviceType{ null, null };
|
||||
|
||||
/// Handle a child driver's `AttachRequest`: record the endpoint capability it
|
||||
/// passed as the forwarding target for the port whose device matches its type.
|
||||
/// Writes an `AttachReply` into `out` and returns its length.
|
||||
fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize {
|
||||
const reply = struct {
|
||||
fn write(buffer: []u8, status: i32) usize {
|
||||
const header = ps2.AttachReply{ .status = status };
|
||||
@memcpy(buffer[0..@sizeOf(ps2.AttachReply)], std.mem.asBytes(&header));
|
||||
return @sizeOf(ps2.AttachReply);
|
||||
}
|
||||
};
|
||||
|
||||
if (message.len < @sizeOf(ps2.AttachRequest)) return reply.write(out, -1);
|
||||
const request = std.mem.bytesToValue(ps2.AttachRequest, message[0..@sizeOf(ps2.AttachRequest)]);
|
||||
const endpoint = got.cap orelse return reply.write(out, -1); // no endpoint passed
|
||||
|
||||
for (&port_device_types, 0..) |maybe_type, port_index| {
|
||||
const device_type = maybe_type orelse continue;
|
||||
if (@intFromEnum(device_type) != request.device_type) continue;
|
||||
port_endpoints[port_index] = endpoint;
|
||||
writeLine("system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)});
|
||||
return reply.write(out, 0);
|
||||
}
|
||||
return reply.write(out, -1); // no port identified as that device type
|
||||
}
|
||||
|
||||
pub fn main() void {
|
||||
@@ -48,6 +95,8 @@ pub fn main() void {
|
||||
};
|
||||
|
||||
var has_two_channels = false;
|
||||
var maybe_controller: ?ps2.Controller = null;
|
||||
var maybe_interrupt_index: ?u64 = null;
|
||||
// The 8042's IO ports (0x60/0x64) are enumerated under the keyboard ACPI node
|
||||
// (PNP0303), so we init the controller from that descriptor — but which device
|
||||
// is on which port is decided later by identify, not by this HID.
|
||||
@@ -65,6 +114,8 @@ pub fn main() void {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n");
|
||||
return;
|
||||
};
|
||||
maybe_controller = controller;
|
||||
maybe_interrupt_index = findInterruptResourceIndex(controller_device_descriptor);
|
||||
|
||||
controller.disablePort(.One);
|
||||
controller.disablePort(.Two);
|
||||
@@ -126,18 +177,12 @@ pub fn main() void {
|
||||
return;
|
||||
}
|
||||
|
||||
// enable the working ports and, via a read-modify-write, their interrupts
|
||||
// Enable the working ports. Their interrupts stay off until IRQ1 is bound
|
||||
// below — reset and identify use polled reads, which must never race the
|
||||
// interrupt-driven drain loop for bytes.
|
||||
controller.enablePort(.One);
|
||||
if (port_two_works) controller.enablePort(.Two);
|
||||
|
||||
var configuration = controller.readConfigurationByte() orelse {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
||||
return;
|
||||
};
|
||||
if (port_one_works) configuration |= ps2.Port.One.interruptBit();
|
||||
if (port_two_works) configuration |= ps2.Port.Two.interruptBit();
|
||||
_ = controller.writeConfigurationByte(configuration);
|
||||
|
||||
// reset each working device; a failing device is logged but does not
|
||||
// abort bring-up of the other one
|
||||
if (port_one_works) {
|
||||
@@ -158,15 +203,77 @@ pub fn main() void {
|
||||
// Identify the device on each working port and hand it off to the driver
|
||||
// that matches what it reported — a port is not assumed to be a keyboard
|
||||
// or a mouse by its number.
|
||||
if (port_one_works) spawnIdentifiedDriver(controller, .One);
|
||||
if (port_two_works) spawnIdentifiedDriver(controller, .Two);
|
||||
if (port_one_works) port_device_types[@intFromEnum(ps2.Port.One)] = spawnIdentifiedDriver(controller, .One);
|
||||
if (port_two_works) port_device_types[@intFromEnum(ps2.Port.Two)] = spawnIdentifiedDriver(controller, .Two);
|
||||
} else {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = maybe_controller.?;
|
||||
const interrupt_index = maybe_interrupt_index orelse {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IRQ\n");
|
||||
return;
|
||||
};
|
||||
|
||||
// The endpoint the child drivers attach to and IRQ1 wakes. Registered under a
|
||||
// well-known id so the children can find it, the way input subscribers find
|
||||
// the input service.
|
||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: no endpoint\n");
|
||||
return;
|
||||
};
|
||||
if (!ipc.register(.ps2_bus, endpoint)) {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: register failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// From here on, only the interrupt path reads the data port. Drop anything a
|
||||
// device sent between enable-scanning and now, bind the IRQ, and only then
|
||||
// let the controller raise it — an interrupt with nobody bound is lost.
|
||||
controller.drainOutputBuffer();
|
||||
if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: irq_bind failed\n");
|
||||
return;
|
||||
}
|
||||
var configuration = controller.readConfigurationByte() orelse {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
||||
return;
|
||||
};
|
||||
if (port_device_types[@intFromEnum(ps2.Port.One)] != null) configuration |= ps2.Port.One.interruptBit();
|
||||
if (port_device_types[@intFromEnum(ps2.Port.Two)] != null) configuration |= ps2.Port.Two.interruptBit();
|
||||
_ = controller.writeConfigurationByte(configuration);
|
||||
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: ok\n");
|
||||
while (true) runtime.system.sleep(1000);
|
||||
|
||||
// The forwarding loop: an IRQ1 notification drains the output buffer, routing
|
||||
// each byte to the attached driver of the port it came from; a client message
|
||||
// is a child driver's AttachRequest.
|
||||
var reply_buffer: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
||||
var reply_len: usize = 0;
|
||||
var receive: [@sizeOf(ps2.AttachRequest)]u8 = undefined;
|
||||
while (true) {
|
||||
const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null);
|
||||
if (got.isNotification()) {
|
||||
reply_len = 0;
|
||||
if (got.isMessage() or got.isChildExit()) continue; // nothing sends us these
|
||||
while (true) {
|
||||
const current_status = ps2.status(controller.device_id, controller.status_index);
|
||||
if (current_status & ps2.status_output_buffer_full == 0) break;
|
||||
const byte = device.ioRead(controller.device_id, controller.data_index, 0, 1) orelse break;
|
||||
const port: ps2.Port = if (current_status & ps2.status_auxiliary_output != 0) .Two else .One;
|
||||
if (port_endpoints[@intFromEnum(port)]) |child| {
|
||||
const forwarded = ps2.ForwardedByte{ .port = @intFromEnum(port), .byte = byte };
|
||||
_ = ipc.send(child, std.mem.asBytes(&forwarded));
|
||||
}
|
||||
// An unattached port's byte is dropped — e.g. a keystroke before
|
||||
// the keyboard driver has attached.
|
||||
}
|
||||
_ = device.irqAck(controller.device_id, interrupt_index);
|
||||
continue;
|
||||
}
|
||||
reply_len = handleAttach(receive[0..got.len], got, &reply_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub const panic = runtime.panic;
|
||||
|
||||
Reference in New Issue
Block a user