WIP: ps2 bus driver

This commit is contained in:
Daniel Samson
2026-07-11 04:19:03 +01:00
parent e499f500c3
commit b42d572d78
8 changed files with 221 additions and 16 deletions
+19 -7
View File
@@ -238,7 +238,7 @@ pub fn build(b: *std.Build) void {
.abi = .none, .abi = .none,
}); });
const exe = b.addExecutable(.{ const kernel_exe = b.addExecutable(.{
.name = "kernel", .name = "kernel",
.root_module = b.createModule(.{ .root_module = b.createModule(.{
.root_source_file = b.path("system/kernel/kernel.zig"), .root_source_file = b.path("system/kernel/kernel.zig"),
@@ -262,24 +262,24 @@ pub fn build(b: *std.Build) void {
}, },
}), }),
}); });
exe.setLinkerScript(b.path("system/kernel/architecture/x86_64/linker.ld")); kernel_exe.setLinkerScript(b.path("system/kernel/architecture/x86_64/linker.ld"));
exe.entry = .{ .symbol_name = "_start" }; kernel_exe.entry = .{ .symbol_name = "_start" };
// The self-hosted linker ignores parts of the linker script (PHDRS, // The self-hosted linker ignores parts of the linker script (PHDRS,
// /DISCARD/, AT(), section order); the higher-half layout depends on the // /DISCARD/, AT(), section order); the higher-half layout depends on the
// script being authoritative, so pin the kernel to LLVM + LLD. // script being authoritative, so pin the kernel to LLVM + LLD.
exe.use_llvm = true; kernel_exe.use_llvm = true;
exe.use_lld = true; kernel_exe.use_lld = true;
// Higher-half virtual base (matches KERNEL_VIRT_BASE in linker.ld); the // Higher-half virtual base (matches KERNEL_VIRT_BASE in linker.ld); the
// linker's AT() clauses give each segment a low physical load address // linker's AT() clauses give each segment a low physical load address
// (.text at 1 MiB), which the loader allocates and copies into. // (.text at 1 MiB), which the loader allocates and copies into.
exe.image_base = 0xFFFFFFFF80100000; kernel_exe.image_base = 0xFFFFFFFF80100000;
// Everything installs into a FHS-shaped zig-out: it IS the danos filesystem *and* // Everything installs into a FHS-shaped zig-out: it IS the danos filesystem *and*
// the boot volume. Each binary lands at its addressed, leaf-collapsed path — the // the boot volume. Each binary lands at its addressed, leaf-collapsed path — the
// kernel at zig-out/system/kernel (from system/kernel/kernel.zig), init at // kernel at zig-out/system/kernel (from system/kernel/kernel.zig), init at
// zig-out/system/services/init, and so on (see docs/README.md). The bootloader // zig-out/system/services/init, and so on (see docs/README.md). The bootloader
// then loads these FHS paths off the volume. // then loads these FHS paths off the volume.
const kernel_install = b.addInstallArtifact(exe, .{ .dest_dir = .{ .override = .{ .custom = "system" } } }); const kernel_install = b.addInstallArtifact(kernel_exe, .{ .dest_dir = .{ .override = .{ .custom = "system" } } });
b.getInstallStep().dependOn(&kernel_install.step); b.getInstallStep().dependOn(&kernel_install.step);
// --- init: the first user-space program (a system service) --- // --- init: the first user-space program (a system service) ---
@@ -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, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig");
const ps2_bus_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-bus-keyboard", "system/drivers/ps2-bus/keyboard.zig");
const ps2_bus_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-bus-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");
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool // Pack the user binaries into the initial_ramdisk image with the host-side Python tool
@@ -314,6 +317,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-bus-keyboard");
mk_run.addFileArg(ps2_bus_keyboard_exe.getEmittedBin());
mk_run.addArg("ps2-bus-mouse");
mk_run.addFileArg(ps2_bus_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());
@@ -324,6 +333,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_bus_keyboard_exe, "system/drivers" },
.{ ps2_bus_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);
+17
View File
@@ -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");
@@ -51,6 +52,22 @@ pub fn register(parent_id: u64, descriptor: *const DeviceDescriptor) ?u64 {
return if (failed(r)) null else r; return if (failed(r)) null else r;
} }
/// 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;
}
/// Bind resource `resource_index` (which must be an IRQ) of claimed device `device_id` to /// Bind resource `resource_index` (which must be an IRQ) of claimed device `device_id` to
/// `endpoint`. From then on the interrupt arrives as an asynchronous notification: /// `endpoint`. From then on the interrupt arrives as an asynchronous notification:
/// `ipc.replyWait` on that endpoint returns with the high bit set in `badge` and the /// `ipc.replyWait` on that endpoint returns with the high bit set in `badge` and the
+12
View File
@@ -0,0 +1,12 @@
//! PS/2 Keyboard Driver
const std = @import("std");
const runtime = @import("runtime");
const ps2 = @import("root.zig");
const device = runtime.device;
pub fn main() void {
// TBD
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n");
while (true) runtime.system.sleep(1000);
}
+11
View File
@@ -0,0 +1,11 @@
//! PS/2 Mouse Driver
const std = @import("std");
const runtime = @import("runtime");
const ps2 = @import("root.zig");
const device = runtime.device;
pub fn main() void {
// TBD
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n");
while (true) runtime.system.sleep(1000);
}
+55
View File
@@ -0,0 +1,55 @@
//! 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("root.zig");
const device = runtime.device;
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;
};
const keyboardDeviceDescriptor = device.findDeviceDescriptorByHid(buffer, "PNP0303");
if (keyboardDeviceDescriptor != null) {
// load keyboard driver
_ = runtime.system.write("system/drivers/ps2-bus: found keyboard DeviceDescriptor\n");
_ = runtime.system.write("system/drivers/ps2-bus: probing controller\n");
if (!device.claim(keyboardDeviceDescriptor.?.id)) {
_ = runtime.system.write("system/drivers/ps2-bus: unable to claim controller \n");
return;
}
ps2.disablePorts(keyboardDeviceDescriptor.?);
ps2.flushOutputBuffer(keyboardDeviceDescriptor.?);
} else {
_ = runtime.system.write("system/drivers/ps2-bus: no keyboard\n");
}
const mouseDeviceDescriptor = device.findDeviceDescriptorByHid(buffer, "PNP0F13");
if (mouseDeviceDescriptor != null) {
// load mouse driver
_ = runtime.system.write("system/drivers/ps2-bus: found mouse DeviceDescriptor\n");
} else {
_ = runtime.system.write("system/drivers/ps2-bus: no mouse\n");
}
_ = runtime.system.write("system/drivers/ps2-bus: ok\n");
while (true) runtime.system.sleep(1000);
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start;
}
+87
View File
@@ -0,0 +1,87 @@
//! shared definitions between the different PS/2 drivers
const runtime = @import("runtime");
const device = runtime.device;
/// 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;
const spin_limit: u32 = 5000;
fn waitWritable(id: u64, cmd_index: u64) bool {
var tries: u32 = 0;
while (tries < spin_limit) : (tries += 1) {
if (status(id, cmd_index) & 0x02 == 0) return true; // IBF clear -> ok to write
}
return false; // timed out
}
fn waitReadable(id: u64, cmd_index: u64) bool {
var tries: u32 = 0;
while (tries < spin_limit) : (tries += 1) {
if (status(id, cmd_index) & 0x01 != 0) return true; // OBF set -> data ready
}
return false;
}
fn scanResourcesIndexes(d: device.DeviceDescriptor) struct { data_port_resource_index: u64, command_or_status_resource_index: u64 } {
const resources = d.resources;
const total_resources = d.resource_count;
var data_port_resource_index: ?u64 = null;
var command_or_status_resource_index: ?u64 = null;
for (resources, 0..total_resources) |resource, resource_index| {
if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and resource.start == dataPort) {
data_port_resource_index = @intCast(resource_index);
} else if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and resource.start == statusRegisterPort) {
command_or_status_resource_index = @intCast(resource_index);
}
}
return .{ .data_port_resource_index = data_port_resource_index orelse 0, .command_or_status_resource_index = command_or_status_resource_index orelse 0 };
}
fn status(id: u64, cmd_index: u64) u8 {
return @intCast(device.ioRead(id, cmd_index, 0, 1) orelse 0);
}
fn sendCommand(id: u64, cmd_index: u64, byte: u8) bool {
// wait IBF clear
if (!waitWritable(id, cmd_index)) return false;
return device.ioWrite(id, cmd_index, 0, 1, byte);
}
fn readData(id: u64, data_index: u64) u8 {
// wait OBF set
if (!waitReadable(id, data_index)) return null;
return @intCast(device.ioRead(id, data_index, 0, 1) orelse 0);
}
pub fn disablePorts(d: device.DeviceDescriptor) void {
const indexes = scanResourcesIndexes(d);
// disable port 1 (keyboard)
_ = sendCommand(d.id, indexes.data_port_resource_index, 0xAD);
// disable port 2 (aux/mouse)
_ = sendCommand(d.id, indexes.command_or_status_resource_index, 0xA7);
}
pub fn flushOutputBuffer(d: device.DeviceDescriptor) void {
const indexes = scanResourcesIndexes(d);
// flush any stale byte the controller buffered
_ = device.ioRead(d.id, indexes.data_port_resource_index, 0, 1);
}
+10
View File
@@ -162,6 +162,7 @@ fn system_call(state: *architecture.CpuState) void {
.msi_bind => systemMsiBind(state), .msi_bind => systemMsiBind(state),
.io_read => systemIoRead(state), .io_read => systemIoRead(state),
.io_write => systemIoWrite(state), .io_write => systemIoWrite(state),
.clock => systemClock(state),
_ => fail(state), _ => fail(state),
} }
} }
@@ -316,6 +317,15 @@ fn systemIoWrite(state: *architecture.CpuState) void {
architecture.setSystemCallResult(state, 0); architecture.setSystemCallResult(state, 0);
} }
/// clock() -> nanoseconds since boot: a monotonic time source. The kernel already owns
/// the scheduling timer and computes this for preemption, so surfacing it is pure
/// mechanism — no policy (wall-clock time, calendars, timezones are a user-space
/// service layered on top). It lets a driver bound a poll loop by real time instead of
/// a spin count, and time short delays.
fn systemClock(state: *architecture.CpuState) void {
architecture.setSystemCallResult(state, architecture.nanos());
}
/// dma_alloc(len, flags) -> vaddr (rax), paddr (rdx): grant `len` bytes (rounded up to /// dma_alloc(len, flags) -> vaddr (rax), paddr (rdx): grant `len` bytes (rounded up to
/// whole pages) of DMA-capable memory — physically contiguous, zeroed, pinned, and /// whole pages) of DMA-capable memory — physically contiguous, zeroed, pinned, and
/// strong-uncacheable (coherent) — mapping it into the caller's DMA arena and handing /// strong-uncacheable (coherent) — mapping it into the caller's DMA arena and handing
@@ -13,6 +13,7 @@
//! 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;
@@ -20,8 +21,13 @@ const device = runtime.device;
/// 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;
} }
@@ -34,10 +40,9 @@ pub fn main() void {
const total = device.enumerate(buffer); const total = device.enumerate(buffer);
const count = @min(total, buffer.len); const count = @min(total, buffer.len);
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;
if (runtime.system.spawn(driver_name)) { if (runtime.system.spawn(driver_name)) {
_ = runtime.system.write("device-manager: spawned "); _ = runtime.system.write("device-manager: spawned ");
_ = runtime.system.write(driver_name); _ = runtime.system.write(driver_name);
@@ -49,10 +54,6 @@ pub fn main() void {
} }
} }
if (matched == 0) {
_ = runtime.system.write("device-manager: no matchable devices\n");
return;
}
_ = runtime.system.write("device-manager: ok\n"); _ = runtime.system.write("device-manager: ok\n");
while (true) runtime.system.sleep(1000); while (true) runtime.system.sleep(1000);
} }