502 lines
24 KiB
Zig
502 lines
24 KiB
Zig
//! shared definitions between the different PS/2 drivers
|
|
const std = @import("std");
|
|
const device = @import("driver");
|
|
const ipc = @import("ipc");
|
|
const time = @import("time");
|
|
const acpi_ids = @import("acpi-ids");
|
|
|
|
/// 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 = time.clock() + wait_timeout_nanoseconds;
|
|
while (time.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 = time.clock() + wait_timeout_nanoseconds;
|
|
while (time.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 `_HID`s a PS/2 pointing device (the controller's aux channel) can enumerate
|
|
/// under. It is the same 8042 mouse channel whichever id the firmware chose:
|
|
/// QEMU/OVMF report the generic `.ps2_mouse` (PNP0F13), VirtualBox reports
|
|
/// `.microsoft_ps2_mouse` (PNP0F03). Both mean "the mouse on port two".
|
|
pub const mouse_hardware_ids = [_]acpi_ids.HardwareId{ .ps2_mouse, .microsoft_ps2_mouse };
|
|
|
|
/// Find the aux (mouse) device's ACPI node, whichever of the PS/2-mouse `_HID`s the
|
|
/// firmware used — the bus needs it to bind IRQ12, and the mouse driver to confirm
|
|
/// its device is present. Returns the first match, or null if none is reported.
|
|
pub fn findMouseDescriptor(buffer: []device.DeviceDescriptor) ?device.DeviceDescriptor {
|
|
for (mouse_hardware_ids) |id| {
|
|
if (device.findDeviceDescriptorByHid(buffer, id.hid())) |descriptor| return descriptor;
|
|
}
|
|
return 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;
|
|
}
|
|
};
|