diff --git a/build.zig b/build.zig index 6515810..6ce4387 100644 --- a/build.zig +++ b/build.zig @@ -238,7 +238,7 @@ pub fn build(b: *std.Build) void { .abi = .none, }); - const exe = b.addExecutable(.{ + const kernel_exe = b.addExecutable(.{ .name = "kernel", .root_module = b.createModule(.{ .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")); - exe.entry = .{ .symbol_name = "_start" }; + kernel_exe.setLinkerScript(b.path("system/kernel/architecture/x86_64/linker.ld")); + kernel_exe.entry = .{ .symbol_name = "_start" }; // The self-hosted linker ignores parts of the linker script (PHDRS, // /DISCARD/, AT(), section order); the higher-half layout depends on the // script being authoritative, so pin the kernel to LLVM + LLD. - exe.use_llvm = true; - exe.use_lld = true; + kernel_exe.use_llvm = true; + kernel_exe.use_lld = true; // Higher-half virtual base (matches KERNEL_VIRT_BASE in linker.ld); the // linker's AT() clauses give each segment a low physical load address // (.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* // 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 // zig-out/system/services/init, and so on (see docs/README.md). The bootloader // 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); // --- 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 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 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"); // 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.addArg("bus"); 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.addFileArg(device_manager_exe.getEmittedBin()); @@ -324,6 +333,9 @@ pub fn build(b: *std.Build) void { .{ device_manager_exe, "system/services" }, .{ hpet_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| { const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } }); b.getInstallStep().dependOn(&step.step); diff --git a/library/runtime/device.zig b/library/runtime/device.zig index 6502314..ea21347 100644 --- a/library/runtime/device.zig +++ b/library/runtime/device.zig @@ -3,6 +3,7 @@ //! ownership of its hardware; the claim is the capability the kernel checks before //! mapping registers or routing an IRQ. +const std = @import("std"); const abi = @import("abi"); const device_abi = @import("device-abi"); 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; } +/// 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 /// `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 diff --git a/system/drivers/ps2-bus/keyboard.zig b/system/drivers/ps2-bus/keyboard.zig new file mode 100644 index 0000000..41c4a94 --- /dev/null +++ b/system/drivers/ps2-bus/keyboard.zig @@ -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); +} \ No newline at end of file diff --git a/system/drivers/ps2-bus/mouse.zig b/system/drivers/ps2-bus/mouse.zig new file mode 100644 index 0000000..89ebd1a --- /dev/null +++ b/system/drivers/ps2-bus/mouse.zig @@ -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); +} \ No newline at end of file diff --git a/system/drivers/ps2-bus/ps2-bus.zig b/system/drivers/ps2-bus/ps2-bus.zig new file mode 100644 index 0000000..8a1faa5 --- /dev/null +++ b/system/drivers/ps2-bus/ps2-bus.zig @@ -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; +} diff --git a/system/drivers/ps2-bus/root.zig b/system/drivers/ps2-bus/root.zig new file mode 100644 index 0000000..b946452 --- /dev/null +++ b/system/drivers/ps2-bus/root.zig @@ -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); +} diff --git a/system/kernel/process.zig b/system/kernel/process.zig index 222221e..e5b75d0 100644 --- a/system/kernel/process.zig +++ b/system/kernel/process.zig @@ -162,6 +162,7 @@ fn system_call(state: *architecture.CpuState) void { .msi_bind => systemMsiBind(state), .io_read => systemIoRead(state), .io_write => systemIoWrite(state), + .clock => systemClock(state), _ => fail(state), } } @@ -316,6 +317,15 @@ fn systemIoWrite(state: *architecture.CpuState) void { 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 /// whole pages) of DMA-capable memory — physically contiguous, zeroed, pinned, and /// strong-uncacheable (coherent) — mapping it into the caller's DMA arena and handing diff --git a/system/services/device-manager/device-manager.zig b/system/services/device-manager/device-manager.zig index 6671bd6..a43e652 100644 --- a/system/services/device-manager/device-manager.zig +++ b/system/services/device-manager/device-manager.zig @@ -13,6 +13,7 @@ //! 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.) +const std = @import("std"); const runtime = @import("runtime"); 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 /// /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. -fn driverFor(class: u64) ?[]const u8 { - if (class == @intFromEnum(device.DeviceClass.timer)) return "hpet"; // the HPET +fn driverFor(d: device.DeviceDescriptor) ?[]const u8 { + // 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; } @@ -34,10 +40,9 @@ pub fn main() void { const total = device.enumerate(buffer); const count = @min(total, buffer.len); - var matched: usize = 0; for (buffer[0..count]) |descriptor| { - const driver_name = driverFor(descriptor.class) orelse continue; - matched += 1; + const driver_name = driverFor(descriptor) orelse continue; + if (runtime.system.spawn(driver_name)) { _ = runtime.system.write("device-manager: spawned "); _ = 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"); while (true) runtime.system.sleep(1000); }