//! shared definitions between the different PS/2 drivers const std = @import("std"); const runtime = @import("runtime"); const acpi_ids = @import("acpi-ids"); const device = runtime.device; const system = runtime.system; /// PS-2 io ports: /// The PS/2 Controller itself uses 2 IO ports (usually, IO ports 0x60 and 0x64). Like many IO /// ports, reads and writes may access different internal registers. /// /// Historical note: The PC-XT PPI had used port 0x61 to reset the keyboard interrupt request /// signal (among other unrelated functions). Port 0x61 has no keyboard related functions on AT and /// PS/2 compatibles. /// /// The Data Port (typically IO Port 0x60) is used for reading data that was received from a PS/2 /// device or from the PS/2 controller itself and writing data to a PS/2 device or to the PS/2 /// controller itself. // Access type: Read/Write pub const dataPort = 0x60; // Access type: Read pub const statusRegisterPort = 0x64; // Access type: Write pub const CommandRegisterPort = 0x64; /// How long to poll the status register before giving up. PS/2 controller /// responses normally arrive within a few milliseconds. pub const default_wait_timeout_nanoseconds: u64 = 10_000_000; // 10 ms /// A PS/2 device reset (0xFF) runs the device's self-test (BAT), whose reply can /// take far longer than an ordinary controller response. pub const device_reset_timeout_nanoseconds: u64 = 750_000_000; // 750 ms /// PS/2 controller commands, written to the command register (port 0x64). pub const cmd_read_configuration_byte: u8 = 0x20; // read controller configuration byte (internal RAM byte 0) pub const cmd_write_configuration_byte: u8 = 0x60; // write controller configuration byte (internal RAM byte 0) pub const cmd_disable_second_port: u8 = 0xA7; // disable second PS/2 port (dual-channel controllers only) pub const cmd_enable_second_port: u8 = 0xA8; // enable second PS/2 port (dual-channel controllers only) pub const cmd_test_second_port: u8 = 0xA9; // test second PS/2 port pub const cmd_test_controller: u8 = 0xAA; // controller self-test pub const cmd_test_first_port: u8 = 0xAB; // test first PS/2 port pub const cmd_diagnostic_dump: u8 = 0xAC; // read all bytes of internal RAM pub const cmd_disable_first_port: u8 = 0xAD; // disable first PS/2 port pub const cmd_enable_first_port: u8 = 0xAE; // enable first PS/2 port pub const cmd_read_controller_input_port: u8 = 0xC0; // read controller input port pub const cmd_read_controller_output_port: u8 = 0xD0; // read controller output port pub const cmd_write_controller_output_port: u8 = 0xD1; // write next data byte to the controller output port pub const cmd_write_first_port_output: u8 = 0xD2; // write next data byte to the first port output buffer pub const cmd_write_second_port_output: u8 = 0xD3; // write next data byte to the second port output buffer pub const cmd_write_second_port_input: u8 = 0xD4; // write next data byte to the second port input buffer (to the mouse) pub const cmd_pulse_system_reset: u8 = 0xFE; // pulse output line 0 low: resets the CPU /// PS/2 status register bits (read from the status port, 0x64). Bit 4 is /// chipset-specific and intentionally omitted. pub const status_output_buffer_full: u8 = 1 << 0; // 1 = a byte is waiting to be read from the data port pub const status_input_buffer_full: u8 = 1 << 1; // 1 = the controller has not yet consumed the last write pub const status_system_flag: u8 = 1 << 2; // set once the controller passes POST pub const status_command_or_data: u8 = 1 << 3; // 1 = last write was a command, 0 = data /// Chipset-specific in the original spec, universal in practice on dual-channel /// controllers: set = the waiting byte came from the second port (the mouse). pub const status_auxiliary_output: u8 = 1 << 5; pub const status_timeout_error: u8 = 1 << 6; // 1 = time-out error pub const status_parity_error: u8 = 1 << 7; // 1 = parity error /// Controller configuration byte bits (internal RAM byte 0; read/written via 0x20/0x60). pub const configuration_first_port_interrupt: u8 = 1 << 0; // 1 = first port IRQ (IRQ1) enabled pub const configuration_second_port_interrupt: u8 = 1 << 1; // 1 = second port IRQ (IRQ12) enabled pub const configuration_system_flag: u8 = 1 << 2; // 1 = system passed POST pub const configuration_first_port_clock_disabled: u8 = 1 << 4; // 1 = first port clock disabled pub const configuration_second_port_clock_disabled: u8 = 1 << 5; // 1 = second port clock disabled pub const configuration_first_port_translation: u8 = 1 << 6; // 1 = first port scancode translation enabled /// Controller output port bits (read/written via 0xD0/0xD1). pub const output_port_system_reset: u8 = 1 << 0; // WARNING: keep this 1; writing 0 can lock the machine pub const output_port_a20_gate: u8 = 1 << 1; // A20 gate pub const output_port_second_port_clock: u8 = 1 << 2; // dual-channel controllers only pub const output_port_second_port_data: u8 = 1 << 3; // dual-channel controllers only pub const output_port_first_port_output_full: u8 = 1 << 4; // output buffer full from first port (IRQ1) pub const output_port_second_port_output_full: u8 = 1 << 5; // output buffer full from second port (IRQ12) pub const output_port_first_port_clock: u8 = 1 << 6; // first port clock pub const output_port_first_port_data: u8 = 1 << 7; // first port data /// Controller self-test (0xAA) result codes. pub const response_controller_test_passed: u8 = 0x55; pub const response_controller_test_failed: u8 = 0xFC; /// Port test (0xAB / 0xA9) result codes. pub const response_port_test_passed: u8 = 0x00; pub const response_port_test_clock_stuck_low: u8 = 0x01; pub const response_port_test_clock_stuck_high: u8 = 0x02; pub const response_port_test_data_stuck_low: u8 = 0x03; pub const response_port_test_data_stuck_high: u8 = 0x04; /// PS/2 device commands, written to the data port (0x60) to reach the attached device. pub const device_cmd_identify: u8 = 0xF2; // identify device pub const device_cmd_enable_scanning: u8 = 0xF4; pub const device_cmd_disable_scanning: u8 = 0xF5; pub const device_cmd_reset: u8 = 0xFF; // reset and run the device self-test (BAT) /// PS/2 device response bytes, read from the data port (0x60). pub const device_response_self_test_passed: u8 = 0xAA; // BAT succeeded after a reset pub const device_response_echo: u8 = 0xEE; pub const device_response_acknowledge: u8 = 0xFA; // ACK pub const device_response_self_test_failed_1: u8 = 0xFC; // BAT failure pub const device_response_self_test_failed_2: u8 = 0xFD; // BAT failure pub const device_response_resend: u8 = 0xFE; // ask the host to resend the last byte /// PS/2 device identify (0xF2) reply bytes. A keyboard returns a two-byte id /// beginning with 0xAB; a mouse returns a single-byte id (0x00/0x03/0x04); an /// ancient AT keyboard returns nothing at all. pub const identify_keyboard_mf2: u8 = 0xAB; // first byte of a MF2 keyboard id (a subtype byte follows) pub const identify_mouse_standard: u8 = 0x00; pub const identify_mouse_scroll: u8 = 0x03; // mouse with scroll wheel pub const identify_mouse_five_button: u8 = 0x04; // 5-button mouse fn waitReadable(id: u64, cmd_index: u64, wait_timeout_nanoseconds: u64) bool { const deadline = system.clock() + wait_timeout_nanoseconds; while (system.clock() < deadline) { if (status(id, cmd_index) & status_output_buffer_full != 0) return true; // OBF set -> data ready } return false; } fn waitWritable(id: u64, cmd_index: u64, wait_timeout_nanoseconds: u64) bool { const deadline = system.clock() + wait_timeout_nanoseconds; while (system.clock() < deadline) { if (status(id, cmd_index) & status_input_buffer_full == 0) return true; // IBF clear -> ok to write } return false; // timed out } pub fn status(id: u64, cmd_index: u64) u8 { return @intCast(device.ioRead(id, cmd_index, 0, 1) orelse 0); } pub fn sendCommand(id: u64, cmd_index: u64, byte: u8, timeout_nanoseconds: u64) bool { // wait IBF clear if (!waitWritable(id, cmd_index, timeout_nanoseconds)) return false; return device.ioWrite(id, cmd_index, 0, 1, byte); } pub fn readData(id: u64, status_index: u64, data_index: u64, timeout_nanoseconds: u64) ?u8 { // OBF lives in the status register (0x64); wait for it there, then read the data port (0x60) if (!waitReadable(id, status_index, timeout_nanoseconds)) return null; return @intCast(device.ioRead(id, data_index, 0, 1) orelse 0); } pub fn writeData(id: u64, status_index: u64, data_index: u64, byte: u8, timeout_nanoseconds: u64) bool { // IBF lives in the status register (0x64); wait for it to clear there, then write the data port // (0x60) if (!waitWritable(id, status_index, timeout_nanoseconds)) return false; return device.ioWrite(id, data_index, 0, 1, byte); } pub const Port = enum(u2) { one, two, /// Command register byte that disables this port. fn disableCommand(self: Port) u8 { return switch (self) { .one => cmd_disable_first_port, .two => cmd_disable_second_port, }; } /// Command register byte that enables this port (and its clock). fn enableCommand(self: Port) u8 { return switch (self) { .one => cmd_enable_first_port, .two => cmd_enable_second_port, }; } /// Command register byte that runs this port's interface test. fn testCommand(self: Port) u8 { return switch (self) { .one => cmd_test_first_port, .two => cmd_test_second_port, }; } /// Configuration-byte bit that, when set, disables this port's clock. pub fn clockDisabledBit(self: Port) u8 { return switch (self) { .one => configuration_first_port_clock_disabled, .two => configuration_second_port_clock_disabled, }; } /// Configuration-byte bit that, when set, enables this port's interrupt. pub fn interruptBit(self: Port) u8 { return switch (self) { .one => configuration_first_port_interrupt, .two => configuration_second_port_interrupt, }; } /// Command register byte that writes the next data byte into this port's /// output buffer (makes a byte appear as if it came from the device). pub fn writeOutputBufferCommand(self: Port) u8 { return switch (self) { .one => cmd_write_first_port_output, .two => cmd_write_second_port_output, }; } /// Controller command that must prefix a byte destined for this port's /// device. Port 1 is the default target of the data port, so it needs no /// prefix (null); port 2 requires the "write second port input" command. pub fn deviceInputCommand(self: Port) ?u8 { return switch (self) { .one => null, .two => cmd_write_second_port_input, }; } /// Controller output-port bit driving this port's clock line. pub fn outputPortClockBit(self: Port) u8 { return switch (self) { .one => output_port_first_port_clock, .two => output_port_second_port_clock, }; } /// Controller output-port bit driving this port's data line. pub fn outputPortDataBit(self: Port) u8 { return switch (self) { .one => output_port_first_port_data, .two => output_port_second_port_data, }; } /// Controller output-port bit set when this port's output buffer is full /// (wired to the port's IRQ line). pub fn outputPortBufferFullBit(self: Port) u8 { return switch (self) { .one => output_port_first_port_output_full, .two => output_port_second_port_output_full, }; } }; /// The kind of device attached to a port, as reported by the device itself in /// response to the identify command — not assumed from the port number. Fixed /// `u32` values because the type also travels in an `AttachRequest`. pub const DeviceType = enum(u32) { keyboard = 0, mouse = 1, unknown = 2, /// Initial-ramdisk name of the driver that serves this device type, or null /// if we could not classify it. pub fn driverName(self: DeviceType) ?[]const u8 { return switch (self) { .keyboard => "ps2-keyboard", .mouse => "ps2-mouse", .unknown => null, }; } /// Canonical ACPI HID for this device type, handed to the spawned driver as /// its command-line argument, or null if we could not classify it. pub fn hid(self: DeviceType) ?[]const u8 { return switch (self) { .keyboard => acpi_ids.HardwareId.ps2_keyboard.hid(), .mouse => acpi_ids.HardwareId.ps2_mouse.hid(), .unknown => null, }; } }; // --- the bus <-> child-driver forwarding protocol ----------------------------- // // The 8042's ports and IRQ1 live on the PNP0303 node that only the ps2-bus driver // claims, so the child device drivers (ps2-keyboard, ps2-mouse) cannot read port // 0x60 themselves. Instead each child **attaches**: it calls the bus's well-known // `ps2_bus` endpoint with an `AttachRequest`, handing over its own endpoint as the // call's capability. From then on the bus forwards every byte the device sends as // a `ForwardedByte` via the asynchronous `ipc.send` — the IRQ path in the bus can // never block on a slow child, and the child never touches the controller. /// A child driver registering for its device's bytes. `device_type` is a /// `DeviceType` value; the child's receive endpoint travels as the call's /// capability (`send_cap`). pub const AttachRequest = extern struct { device_type: u32, }; /// How the bus answered an `AttachRequest` (`AttachReply.status`). pub const AttachStatus = enum(i32) { ok = 0, /// The request was malformed (too short to be an `AttachRequest`). invalid_request = -1, /// The call carried no endpoint capability to forward to. missing_endpoint = -2, /// No port identified a device of the requested type. no_such_device = -3, }; /// Reply to an `AttachRequest`. `status` is an `AttachStatus` value. pub const AttachReply = extern struct { status: i32, _padding: u32 = 0, }; /// One raw byte read from the data port, forwarded to the attached child whose /// port it came from (routed by the status register's auxiliary-output bit). pub const ForwardedByte = extern struct { /// The `Port` the byte came from, as `@intFromEnum`. port: u32, byte: u32, }; /// A single PS/2 (8042) controller. Construct one with `Controller.init` and /// drive the controller through its methods; there is only ever one 8042 per /// machine, but holding the resolved resource indices in an instance keeps the /// call sites free of global state. pub const Controller = struct { device_id: u64, /// Resource index of the command/status port (0x64). status_index: u64, /// Resource index of the data port (0x60). data_index: u64, /// Resolve the controller's IO-port resource indices from its device /// descriptor. Returns null if either the data or command/status port is /// missing from the descriptor. pub fn init(device_descriptor: device.DeviceDescriptor) ?Controller { var data_index: ?u64 = null; var status_index: ?u64 = null; for (device_descriptor.resources, 0..device_descriptor.resource_count) |resource, resource_index| { if (resource.kind != @intFromEnum(device.ResourceKind.io_port)) continue; if (resource.start == dataPort) { data_index = @intCast(resource_index); } else if (resource.start == statusRegisterPort) { status_index = @intCast(resource_index); } } return .{ .device_id = device_descriptor.id, .data_index = data_index orelse return null, .status_index = status_index orelse return null, }; } pub fn disablePort(self: Controller, port: Port) void { // port enable/disable are controller commands and go to the command register (0x64) _ = sendCommand(self.device_id, self.status_index, port.disableCommand(), default_wait_timeout_nanoseconds); } pub fn enablePort(self: Controller, port: Port) void { // enabling a port also starts its clock _ = sendCommand(self.device_id, self.status_index, port.enableCommand(), default_wait_timeout_nanoseconds); } /// Run a port's interface test. Returns the controller's reply — compare it /// to `response_port_test_passed` (0x00) — or null on timeout. pub fn testPort(self: Controller, port: Port) ?u8 { if (!sendCommand(self.device_id, self.status_index, port.testCommand(), default_wait_timeout_nanoseconds)) return null; return readData(self.device_id, self.status_index, self.data_index, default_wait_timeout_nanoseconds); } pub fn flushOutputBuffer(self: Controller) void { // flush any stale byte the controller buffered _ = device.ioRead(self.device_id, self.data_index, 0, 1); } pub fn readConfigurationByte(self: Controller) ?u8 { // ask the controller to place its configuration byte in the output buffer, then read it if (!sendCommand(self.device_id, self.status_index, cmd_read_configuration_byte, default_wait_timeout_nanoseconds)) return null; return readData(self.device_id, self.status_index, self.data_index, default_wait_timeout_nanoseconds); } pub fn writeConfigurationByte(self: Controller, update_byte: u8) ?u8 { // command 0x60 makes the controller store the next data-port byte as its configuration byte if (!sendCommand(self.device_id, self.status_index, cmd_write_configuration_byte, default_wait_timeout_nanoseconds)) return null; if (!writeData(self.device_id, self.status_index, self.data_index, update_byte, default_wait_timeout_nanoseconds)) return null; return update_byte; } /// Run the controller self-test. Returns the reply — compare it to /// `response_controller_test_passed` (0x55) — or null on timeout. pub fn performSelfTest(self: Controller) ?u8 { if (!sendCommand(self.device_id, self.status_index, cmd_test_controller, default_wait_timeout_nanoseconds)) return null; return readData(self.device_id, self.status_index, self.data_index, default_wait_timeout_nanoseconds); } /// Detect whether this is a dual-channel controller by temporarily enabling /// port 2 and checking whether its clock turned on. Note: this leaves port 2 /// enabled; the caller should disable it again to keep the bus quiet until /// device bring-up. pub fn hasTwoChannels(self: Controller) ?bool { self.enablePort(.two); const configuration = self.readConfigurationByte() orelse return null; return (configuration & Port.two.clockDisabledBit()) == 0; } /// Reset the device attached to `port` (device command 0xFF) and wait for /// its power-on self-test (BAT) result. Returns true if the device both /// acknowledged and passed, false if it reported a self-test failure, or /// null on timeout. The BAT reply can be slow, so the response reads use /// `device_reset_timeout_nanoseconds`. pub fn resetDevice(self: Controller, port: Port) ?bool { // A byte destined for port 2 must be prefixed with the "write to second // port input buffer" controller command (0xD4); port 1 is the default. if (port.deviceInputCommand()) |prefix| { if (!sendCommand(self.device_id, self.status_index, prefix, default_wait_timeout_nanoseconds)) return null; } if (!writeData(self.device_id, self.status_index, self.data_index, device_cmd_reset, default_wait_timeout_nanoseconds)) return null; // A successful reset yields both an ACK (0xFA) and a self-test-passed // byte (0xAA). Their order is not guaranteed, so accept either ordering. var saw_acknowledge = false; var saw_self_test_passed = false; var reads: u8 = 0; while (reads < 2) : (reads += 1) { const reply = readData(self.device_id, self.status_index, self.data_index, device_reset_timeout_nanoseconds) orelse return null; switch (reply) { device_response_acknowledge => saw_acknowledge = true, device_response_self_test_passed => saw_self_test_passed = true, device_response_self_test_failed_1, device_response_self_test_failed_2 => return false, else => {}, } } return saw_acknowledge and saw_self_test_passed; } /// Send one command byte to the device on `port` (applying the port-2 prefix /// as needed) and consume its acknowledgement. Returns true on ACK (0xFA), /// false on any other reply, or null on timeout. pub fn sendToDevice(self: Controller, port: Port, byte: u8) ?bool { if (port.deviceInputCommand()) |prefix| { if (!sendCommand(self.device_id, self.status_index, prefix, default_wait_timeout_nanoseconds)) return null; } if (!writeData(self.device_id, self.status_index, self.data_index, byte, default_wait_timeout_nanoseconds)) return null; const reply = readData(self.device_id, self.status_index, self.data_index, default_wait_timeout_nanoseconds) orelse return null; return reply == device_response_acknowledge; } /// Discard any bytes sitting in the output buffer (for example the device-id /// byte a mouse emits after a reset) so they cannot be mistaken for the reply /// to a subsequent command. pub fn drainOutputBuffer(self: Controller) void { var guard: u8 = 0; while (guard < 16) : (guard += 1) { if (status(self.device_id, self.status_index) & status_output_buffer_full == 0) return; _ = device.ioRead(self.device_id, self.data_index, 0, 1); } } /// Ask the device on `port` what it is (command 0xF2) and classify the reply. /// Scanning is disabled around the query so a streaming device cannot inject /// data bytes that look like the identifier. Returns the device type, or null /// if the identify command itself timed out. pub fn identifyDevice(self: Controller, port: Port) ?DeviceType { // Clear any leftover bytes (e.g. a post-reset mouse id) before we start. self.drainOutputBuffer(); // Stop the device reporting so its data can't be mistaken for the reply. if (self.sendToDevice(port, device_cmd_disable_scanning) == null) return null; if (self.sendToDevice(port, device_cmd_identify) == null) return null; // After the ACK, the device sends 0, 1, or 2 identifier bytes. const first = readData(self.device_id, self.status_index, self.data_index, default_wait_timeout_nanoseconds); const device_type: DeviceType = if (first) |id| switch (id) { identify_keyboard_mf2 => blk: { // A MF2 keyboard sends a second subtype byte; consume and ignore it. _ = readData(self.device_id, self.status_index, self.data_index, default_wait_timeout_nanoseconds); break :blk .keyboard; }, identify_mouse_standard, identify_mouse_scroll, identify_mouse_five_button => .mouse, else => .unknown, } else // No identifier bytes at all is a legacy AT keyboard. .keyboard; // Resume scanning so the device works once its driver takes over. _ = self.sendToDevice(port, device_cmd_enable_scanning); return device_type; } };