//! /system/services/device-manager — the ring-3 process that turns the device //! tree into a running system. The kernel enumerates the hardware and enforces the //! claim capability (mechanism); this decides *which driver serves which device* //! and, eventually, spawns it (policy). Keeping that split in user space is the //! whole point of the microkernel: the manager is an ordinary, restartable process //! with no special privilege — it uses the same `device_*` system calls any process //! could ([drivers.md](../../../docs/drivers.md), [driver-model.md]). //! //! Increment 2 (this file): enumerate /system/devices, *match* each device to a //! driver, and *spawn* it with `system_spawn` — the kernel loads the named binary //! from the initial-ramdisk as a fresh ring-3 process. On QEMU this discovers 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 //! that redundancy so the manager is the sole owner of driver spawning.) const std = @import("std"); const runtime = @import("runtime"); const acpi_ids = @import("acpi-ids"); const device = runtime.device; const system = runtime.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 /// /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(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)]; const id = acpi_ids.HardwareId.fromHid(hid) orelse return null; return switch (id) { .ps2_keyboard, .ps2_mouse => "ps2-bus", else => null, }; } /// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller: /// Serial Bus Controller (0x0C) / USB Controller (0x03) / XHCI (0x30) — the names /// pci-class.zig decodes. const xhci_pci_class: u64 = 0x0C_03_30; /// The bus driver that serves a PCI function, or null. Unlike the singleton drivers /// in `driverFor`, a machine can carry several identical controllers — so the caller /// spawns one driver instance *per device*, passing the device id as argv[1] for the /// instance to claim. fn pciDriverFor(d: device.DeviceDescriptor) ?[]const u8 { if (d.class != @intFromEnum(device.DeviceClass.pci_device)) return null; return switch (d.pci_class) { xhci_pci_class => "usb-xhci-bus", else => null, }; } /// Spawn one instance of `driver_name` to serve the specific device `id` — the id /// arrives as argv[1]. No isProcessRunning gate here: the name alone cannot tell two /// instances apart, and this manager is the sole spawner of drivers. fn spawnForDevice(driver_name: []const u8, id: u64) void { var text: [20]u8 = undefined; const id_text = std.fmt.bufPrint(&text, "{d}", .{id}) catch return; if (system.spawnWithArguments(driver_name, &.{id_text}) != null) { writeLine("device-manager: spawned {s} for device {d}\n", .{ driver_name, id }); } else { writeLine("device-manager: failed to spawn {s} for device {d}\n", .{ driver_name, id }); } } pub fn main() void { // Enumerate into a heap buffer (too big for the one-page user stack). const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { _ = runtime.system.write("device-manager: out of memory\n"); return; }; const total = device.enumerate(buffer); const count = @min(total, buffer.len); var matched: usize = 0; for (buffer[0..count]) |descriptor| { if (pciDriverFor(descriptor)) |driver_name| { matched += 1; spawnForDevice(driver_name, descriptor.id); continue; } const driver_name = driverFor(descriptor) orelse continue; matched += 1; if (!system.isProcessRunning(driver_name)) { if (runtime.system.spawn(driver_name) != null) { writeLine("device-manager: spawned {s}\n", .{driver_name}); } else { writeLine("device-manager: failed to spawn {s}\n", .{driver_name}); } } else { writeLine("device-manager: already spawned {s}\n", .{driver_name}); } } 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); } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; // pull the runtime entry shim into the image }