Finishing PS/2 bus driver
This commit is contained in:
@@ -298,6 +298,9 @@ pub fn build(b: *std.Build) void {
|
|||||||
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
||||||
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "hpet", "system/drivers/hpet/hpet.zig");
|
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "hpet", "system/drivers/hpet/hpet.zig");
|
||||||
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/bus/bus.zig");
|
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/bus/bus.zig");
|
||||||
|
const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/ps2-bus/ps2-bus.zig");
|
||||||
|
const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig");
|
||||||
|
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
||||||
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||||
const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "args-echo", "system/services/args-echo/args-echo.zig");
|
const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "args-echo", "system/services/args-echo/args-echo.zig");
|
||||||
const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "process-test", "system/services/process-test/process-test.zig");
|
const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "process-test", "system/services/process-test/process-test.zig");
|
||||||
@@ -316,6 +319,12 @@ pub fn build(b: *std.Build) void {
|
|||||||
mk_run.addFileArg(hpet_exe.getEmittedBin());
|
mk_run.addFileArg(hpet_exe.getEmittedBin());
|
||||||
mk_run.addArg("bus");
|
mk_run.addArg("bus");
|
||||||
mk_run.addFileArg(bus_exe.getEmittedBin());
|
mk_run.addFileArg(bus_exe.getEmittedBin());
|
||||||
|
mk_run.addArg("ps2-bus");
|
||||||
|
mk_run.addFileArg(ps2_bus_exe.getEmittedBin());
|
||||||
|
mk_run.addArg("ps2-keyboard");
|
||||||
|
mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin());
|
||||||
|
mk_run.addArg("ps2-mouse");
|
||||||
|
mk_run.addFileArg(ps2_mouse_exe.getEmittedBin());
|
||||||
mk_run.addArg("device-manager");
|
mk_run.addArg("device-manager");
|
||||||
mk_run.addFileArg(device_manager_exe.getEmittedBin());
|
mk_run.addFileArg(device_manager_exe.getEmittedBin());
|
||||||
mk_run.addArg("args-echo");
|
mk_run.addArg("args-echo");
|
||||||
@@ -330,6 +339,9 @@ pub fn build(b: *std.Build) void {
|
|||||||
.{ device_manager_exe, "system/services" },
|
.{ device_manager_exe, "system/services" },
|
||||||
.{ hpet_exe, "system/drivers" },
|
.{ hpet_exe, "system/drivers" },
|
||||||
.{ bus_exe, "system/drivers" },
|
.{ bus_exe, "system/drivers" },
|
||||||
|
.{ ps2_bus_exe, "system/drivers" },
|
||||||
|
.{ ps2_keyboard_exe, "system/drivers" },
|
||||||
|
.{ ps2_mouse_exe, "system/drivers" },
|
||||||
}) |entry| {
|
}) |entry| {
|
||||||
const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } });
|
const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } });
|
||||||
b.getInstallStep().dependOn(&step.step);
|
b.getInstallStep().dependOn(&step.step);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
//! ownership of its hardware; the claim is the capability the kernel checks before
|
//! ownership of its hardware; the claim is the capability the kernel checks before
|
||||||
//! mapping registers or routing an IRQ.
|
//! mapping registers or routing an IRQ.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
const abi = @import("abi");
|
const abi = @import("abi");
|
||||||
const device_abi = @import("device-abi");
|
const device_abi = @import("device-abi");
|
||||||
const sc = @import("system-call.zig");
|
const sc = @import("system-call.zig");
|
||||||
@@ -108,3 +109,19 @@ pub fn ioRead(device_id: u64, resource_index: u64, offset: u64, width: u8) ?u32
|
|||||||
pub fn ioWrite(device_id: u64, resource_index: u64, offset: u64, width: u8, value: u32) bool {
|
pub fn ioWrite(device_id: u64, resource_index: u64, offset: u64, width: u8, value: u32) bool {
|
||||||
return !failed(sc.systemCall5(.io_write, device_id, resource_index, offset, width, value));
|
return !failed(sc.systemCall5(.io_write, device_id, resource_index, offset, width, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find DeviceDescription by hid
|
||||||
|
///
|
||||||
|
/// Utility function for driver development
|
||||||
|
pub fn findDeviceDescriptorByHid(buffer: []DeviceDescriptor, hid_needle: []const u8) ?DeviceDescriptor {
|
||||||
|
const total = enumerate(buffer);
|
||||||
|
const n = @min(total, buffer.len);
|
||||||
|
for (@as([]DeviceDescriptor, buffer[0..n])) |d| {
|
||||||
|
const hid_haystack = d.hid[0..@intCast(d.hid_len)];
|
||||||
|
if (std.mem.eql(u8, hid_haystack, hid_needle)) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
//! stubs, one per kernel call. Numbers come from `abi.SystemCall`, the single
|
//! stubs, one per kernel call. Numbers come from `abi.SystemCall`, the single
|
||||||
//! source of truth shared with the kernel dispatcher.
|
//! source of truth shared with the kernel dispatcher.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
const abi = @import("abi");
|
const abi = @import("abi");
|
||||||
const sc = @import("system-call.zig");
|
const sc = @import("system-call.zig");
|
||||||
|
|
||||||
@@ -98,6 +99,16 @@ pub fn processes(out: []abi.ProcessDescriptor) usize {
|
|||||||
return sc.systemCall2(.process_enumerate, @intFromPtr(out.ptr), out.len);
|
return sc.systemCall2(.process_enumerate, @intFromPtr(out.ptr), out.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a process spawned under `name` (its argv[0]) is currently alive.
|
||||||
|
pub fn isProcessRunning(name: []const u8) bool {
|
||||||
|
var table: [32]ProcessDescriptor = undefined;
|
||||||
|
const total = processes(&table);
|
||||||
|
for (table[0..@min(total, table.len)]) |descriptor| {
|
||||||
|
if (std.mem.eql(u8, descriptor.name[0..descriptor.name_length], name)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// End process `id`. Only its supervisor — the process that spawned it — may;
|
/// End process `id`. Only its supervisor — the process that spawned it — may;
|
||||||
/// anyone else gets false, as does a stale or unknown id (ids are never reused).
|
/// anyone else gets false, as does a stale or unknown id (ids are never reused).
|
||||||
/// Delivery is prompt but asynchronous, like a signal: a target caught running on
|
/// Delivery is prompt but asynchronous, like a signal: a target caught running on
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! Discovery backends (ACPI today, device-tree later) translate their native
|
//! Discovery backends (ACPI today, device-tree later) translate their native
|
||||||
//! hardware description into this one shape, so the rest of the kernel walks a
|
//! hardware description into this one shape, so the rest of the kernel walks a
|
||||||
//! plain `Device` tree without knowing which firmware described the machine —
|
//! plain `Device` tree without knowing which firmware described the machine —
|
||||||
//! the same discipline `root.zig`'s `MemoryKind` applies to memory and `architecture`
|
//! the same discipline `ps2-library.zig`'s `MemoryKind` applies to memory and `architecture`
|
||||||
//! applies to the CPU.
|
//! applies to the CPU.
|
||||||
//!
|
//!
|
||||||
//! This is deliberately minimal: enough to *describe* what was discovered (a
|
//! This is deliberately minimal: enough to *describe* what was discovered (a
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
//! PS/2 Keyboard Driver
|
||||||
|
//!
|
||||||
|
//! Spawned by the ps2-bus driver once the controller is initialized and port 1
|
||||||
|
//! 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.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const runtime = @import("runtime");
|
||||||
|
const ps2 = @import("ps2-library.zig");
|
||||||
|
const device = runtime.device;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const hid = runtime.argument(1);
|
||||||
|
if (hid.len == 0) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no HID argument\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeLine("system/drivers/ps2-bus/keyboard: starting for hid {s}\n", .{hid});
|
||||||
|
|
||||||
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: out of memory\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
|
||||||
|
writeLine("system/drivers/ps2-bus/keyboard: no device for hid {s}\n", .{hid});
|
||||||
|
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");
|
||||||
|
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n");
|
||||||
|
while (true) runtime.system.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const panic = runtime.panic;
|
||||||
|
comptime {
|
||||||
|
_ = &runtime.start._start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
//! PS/2 Mouse Driver
|
||||||
|
//!
|
||||||
|
//! Spawned by the ps2-bus driver once the controller is initialized and port 2
|
||||||
|
//! 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.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const runtime = @import("runtime");
|
||||||
|
const ps2 = @import("ps2-library.zig");
|
||||||
|
const device = runtime.device;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const hid = runtime.argument(1);
|
||||||
|
if (hid.len == 0) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no HID argument\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeLine("system/drivers/ps2-bus/mouse: starting for hid {s}\n", .{hid});
|
||||||
|
|
||||||
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: out of memory\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const mouse_device_descriptor = device.findDeviceDescriptorByHid(buffer, hid) orelse {
|
||||||
|
writeLine("system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The mouse's own node (PNP0F13) carries IRQ12 and is not claimed by the bus,
|
||||||
|
// so this driver takes exclusive ownership of it. Port IO still goes through
|
||||||
|
// the bus, which owns the shared 8042 ports.
|
||||||
|
if (!device.claim(mouse_device_descriptor.id)) {
|
||||||
|
writeLine("system/drivers/ps2-bus/mouse: unable to claim device for hid {s}\n", .{hid});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeLine("system/drivers/ps2-bus/mouse: claimed device for hid {s}\n", .{hid});
|
||||||
|
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n");
|
||||||
|
while (true) runtime.system.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const panic = runtime.panic;
|
||||||
|
comptime {
|
||||||
|
_ = &runtime.start._start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
//! The PS/2 Controller is located on the mainboard.
|
||||||
|
//! In the early days the controller was a single chip (Intel 8042).
|
||||||
|
//! As of today it is part of the Advanced Integrated Peripheral.
|
||||||
|
//!
|
||||||
|
//! It shows up in the device discovery as:
|
||||||
|
//! KBD_ [acpi_device] hid=PNP0303 (PS/2 Keyboard)
|
||||||
|
//! - io_port 0x60 len 0x1
|
||||||
|
//! - io_port 0x64 len 0x1
|
||||||
|
//! - irq 0x1 len 0x1
|
||||||
|
//! MOU_ [acpi_device] hid=PNP0F13 (PS/2 Mouse)
|
||||||
|
//! - irq 0xc len 0x1
|
||||||
|
const std = @import("std");
|
||||||
|
const runtime = @import("runtime");
|
||||||
|
const ps2 = @import("ps2-library.zig");
|
||||||
|
const device = runtime.device;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
const device_type = controller.identifyDevice(port) orelse {
|
||||||
|
writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const driver_name = device_type.driverName() orelse {
|
||||||
|
writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: out of memory\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var has_two_channels = false;
|
||||||
|
// 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.
|
||||||
|
const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, "PNP0303");
|
||||||
|
if (maybe_controller_device_descriptor) |controller_device_descriptor| {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: found PS/2 controller\n");
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: initializing controller\n");
|
||||||
|
|
||||||
|
if (!device.claim(controller_device_descriptor.id)) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: unable to claim controller \n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = ps2.Controller.init(controller_device_descriptor) orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.disablePort(.One);
|
||||||
|
controller.disablePort(.Two);
|
||||||
|
controller.flushOutputBuffer();
|
||||||
|
|
||||||
|
const current = controller.readConfigurationByte() orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = current & ~(ps2.configuration_first_port_interrupt |
|
||||||
|
ps2.configuration_second_port_interrupt |
|
||||||
|
ps2.configuration_first_port_translation);
|
||||||
|
|
||||||
|
if (controller.writeConfigurationByte(update) == null) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller.performSelfTest()) | reply | {
|
||||||
|
if (reply != ps2.response_controller_test_passed) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: perform controller self test failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller self test timed out\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
has_two_channels = controller.hasTwoChannels() orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller channels timed out\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (has_two_channels) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: has two channels\n");
|
||||||
|
// keep the bus quiet until we have tested the ports and are ready to use them
|
||||||
|
controller.disablePort(.Two);
|
||||||
|
} else {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: has one channel\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface tests: always test port 1, test port 2 only if it exists
|
||||||
|
const port_one_works = (controller.testPort(.One) orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: port 1 test timed out\n");
|
||||||
|
return;
|
||||||
|
}) == ps2.response_port_test_passed;
|
||||||
|
|
||||||
|
var port_two_works = false;
|
||||||
|
if (has_two_channels) {
|
||||||
|
port_two_works = (controller.testPort(.Two) orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: port 2 test timed out\n");
|
||||||
|
return;
|
||||||
|
}) == ps2.response_port_test_passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!port_one_works and !port_two_works) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: no usable ports\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable the working ports and, via a read-modify-write, their interrupts
|
||||||
|
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) {
|
||||||
|
if (controller.resetDevice(.One)) |passed| {
|
||||||
|
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset failed\n");
|
||||||
|
} else {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset timed out\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (port_two_works) {
|
||||||
|
if (controller.resetDevice(.Two)) |passed| {
|
||||||
|
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset failed\n");
|
||||||
|
} else {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset timed out\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: ok\n");
|
||||||
|
while (true) runtime.system.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const panic = runtime.panic;
|
||||||
|
comptime {
|
||||||
|
_ = &runtime.start._start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
//! shared definitions between the different PS/2 drivers
|
||||||
|
const std = @import("std");
|
||||||
|
const runtime = @import("runtime");
|
||||||
|
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). Bits 4 and 5
|
||||||
|
/// are 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
|
||||||
|
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.
|
||||||
|
pub const DeviceType = enum {
|
||||||
|
keyboard,
|
||||||
|
mouse,
|
||||||
|
unknown,
|
||||||
|
|
||||||
|
/// 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 => "PNP0303",
|
||||||
|
.mouse => "PNP0F13",
|
||||||
|
.unknown => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -700,6 +700,11 @@ fn systemIrqAck(state: *architecture.CpuState) void {
|
|||||||
if (irq.ack(gsi)) architecture.setSystemCallResult(state, 0) else fail(state);
|
if (irq.ack(gsi)) architecture.setSystemCallResult(state, 0) else fail(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the debug_write stream sits at the start of a line — the last emitted
|
||||||
|
/// byte was a newline (true at boot: nothing emitted yet). Guarded by the kernel
|
||||||
|
/// lock in `systemDebugWrite`, like the stream it describes.
|
||||||
|
var write_at_line_start: bool = true;
|
||||||
|
|
||||||
/// debug_write(ptr, len): copy bytes from user memory into the kernel log.
|
/// debug_write(ptr, len): copy bytes from user memory into the kernel log.
|
||||||
/// A bring-up diagnostic — real output goes through the VFS/console later.
|
/// A bring-up diagnostic — real output goes through the VFS/console later.
|
||||||
///
|
///
|
||||||
@@ -709,17 +714,27 @@ fn systemIrqAck(state: *architecture.CpuState) void {
|
|||||||
/// Known gap (fine for trusted user code): a pointer into an *unmapped* hole in
|
/// Known gap (fine for trusted user code): a pointer into an *unmapped* hole in
|
||||||
/// the user half passes the check and the read #PFs -> on_fault halts — a
|
/// the user half passes the check and the read #PFs -> on_fault halts — a
|
||||||
/// self-DoS, not an isolation break. Fault-recovering copy-in is a later item.
|
/// self-DoS, not an isolation break. Fault-recovering copy-in is a later item.
|
||||||
|
///
|
||||||
|
/// The emit runs under the kernel lock, so a message is atomic on the wire — two
|
||||||
|
/// processes writing from different cores can interleave *messages*, never bytes.
|
||||||
|
/// The "DANOS-INIT: " marker is emitted only at the start of a line (not per
|
||||||
|
/// call), so a process may assemble a line from several writes without the marker
|
||||||
|
/// (or, with the lock, another byte) landing in the middle. Cleanly-terminated
|
||||||
|
/// lines from concurrent writers stay whole either way.
|
||||||
fn systemDebugWrite(state: *architecture.CpuState) void {
|
fn systemDebugWrite(state: *architecture.CpuState) void {
|
||||||
const ptr = architecture.systemCallArg(state, 0);
|
const ptr = architecture.systemCallArg(state, 0);
|
||||||
const len = architecture.systemCallArg(state, 1);
|
const len = architecture.systemCallArg(state, 1);
|
||||||
if (len <= write_buffer.len and ptr < user_half_end and ptr + len <= user_half_end) {
|
if (len <= write_buffer.len and ptr < user_half_end and ptr + len <= user_half_end) {
|
||||||
const source: [*]const u8 = @ptrFromInt(ptr);
|
const source: [*]const u8 = @ptrFromInt(ptr);
|
||||||
|
const flags = sync.enter();
|
||||||
|
defer sync.leave(flags);
|
||||||
@memcpy(write_buffer[0..len], source[0..len]); // keep the latest message
|
@memcpy(write_buffer[0..len], source[0..len]); // keep the latest message
|
||||||
write_len = len;
|
write_len = len;
|
||||||
write_from_user = architecture.fromUser(state);
|
write_from_user = architecture.fromUser(state);
|
||||||
write_count += 1;
|
write_count += 1;
|
||||||
log.write("DANOS-INIT: ");
|
if (write_at_line_start) log.write("DANOS-INIT: ");
|
||||||
log.write(source[0..len]);
|
log.write(source[0..len]);
|
||||||
|
if (len != 0) write_at_line_start = source[len - 1] == '\n';
|
||||||
architecture.setSystemCallResult(state, len);
|
architecture.setSystemCallResult(state, len);
|
||||||
} else {
|
} else {
|
||||||
fail(state);
|
fail(state);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! hiding the trade-offs. Keeping them here makes them visible at a glance and gives
|
//! hiding the trade-offs. Keeping them here makes them visible at a glance and gives
|
||||||
//! one spot to change them. They're plain `comptime` constants (zero runtime cost);
|
//! one spot to change them. They're plain `comptime` constants (zero runtime cost);
|
||||||
//! any one can later be promoted to a `-D` build option if a target needs to vary it
|
//! any one can later be promoted to a `-D` build option if a target needs to vary it
|
||||||
//! (see build.zig's `-Dtest-case` for the pattern). This keeps root.zig to what it
|
//! (see build.zig's `-Dtest-case` for the pattern). This keeps ps2-library.zig to what it
|
||||||
//! actually is — the bootloader↔kernel handoff *contract* — with tunables living here.
|
//! actually is — the bootloader↔kernel handoff *contract* — with tunables living here.
|
||||||
|
|
||||||
/// Ceiling on logical CPUs the kernel tracks — the size of the per-CPU bookkeeping
|
/// Ceiling on logical CPUs the kernel tracks — the size of the per-CPU bookkeeping
|
||||||
|
|||||||
@@ -12,19 +12,34 @@
|
|||||||
//! HPET, decides `hpet` serves it, and brings that driver all the way up. (The
|
//! HPET, decides `hpet` serves it, and brings that driver all the way up. (The
|
||||||
//! kernel still auto-spawns the whole initial-ramdisk at boot; increment 3 removes
|
//! kernel still auto-spawns the whole initial-ramdisk at boot; increment 3 removes
|
||||||
//! that redundancy so the manager is the sole owner of driver spawning.)
|
//! that redundancy so the manager is the sole owner of driver spawning.)
|
||||||
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
|
const system = runtime.system;
|
||||||
|
|
||||||
/// The driver that serves each device class — the policy table. In a fuller system
|
/// Format one whole log line and emit it in a single `debug_write`, so output
|
||||||
|
/// from the drivers this manager starts (which run concurrently) can never land
|
||||||
|
/// in the middle of it.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The driver that serves each device — the policy table. In a fuller system
|
||||||
/// this comes from the drivers describing what they bind (or a manifest under
|
/// this comes from the drivers describing what they bind (or a manifest under
|
||||||
/// /system/drivers); for now it is a small static map, which is enough to prove the
|
/// /system/drivers); for now it is a small static map, which is enough to prove the
|
||||||
/// manager reads the tree and decides. `null` = no driver for this class yet.
|
/// manager reads the tree and decides. `null` = no driver for this class yet.
|
||||||
fn driverFor(class: u64) ?[]const u8 {
|
fn driverFor(d: device.DeviceDescriptor) ?[]const u8 {
|
||||||
if (class == @intFromEnum(device.DeviceClass.timer)) return "hpet"; // the HPET
|
// detect device via DeviceClass
|
||||||
|
if (d.class == @intFromEnum(device.DeviceClass.timer)) return "hpet";
|
||||||
|
// detect device via hid
|
||||||
|
const hid = d.hid[0..@intCast(d.hid_len)];
|
||||||
|
if (std.mem.eql(u8, hid, "PNP0303") or std.mem.eql(u8, hid, "PNP0F13")) return "ps2-bus";
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
// Enumerate into a heap buffer (too big for the one-page user stack).
|
// Enumerate into a heap buffer (too big for the one-page user stack).
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
@@ -36,16 +51,16 @@ pub fn main() void {
|
|||||||
|
|
||||||
var matched: usize = 0;
|
var matched: usize = 0;
|
||||||
for (buffer[0..count]) |descriptor| {
|
for (buffer[0..count]) |descriptor| {
|
||||||
const driver_name = driverFor(descriptor.class) orelse continue;
|
const driver_name = driverFor(descriptor) orelse continue;
|
||||||
matched += 1;
|
matched += 1;
|
||||||
|
if (!system.isProcessRunning(driver_name)) {
|
||||||
if (runtime.system.spawn(driver_name) != null) {
|
if (runtime.system.spawn(driver_name) != null) {
|
||||||
_ = runtime.system.write("device-manager: spawned ");
|
writeLine("device-manager: spawned {s}\n", .{driver_name});
|
||||||
_ = runtime.system.write(driver_name);
|
|
||||||
_ = runtime.system.write("\n");
|
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("device-manager: failed to spawn ");
|
writeLine("device-manager: failed to spawn {s}\n", .{driver_name});
|
||||||
_ = runtime.system.write(driver_name);
|
}
|
||||||
_ = runtime.system.write("\n");
|
} else {
|
||||||
|
writeLine("device-manager: already spawned {s}\n", .{driver_name});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user