//! /system/services/device-manager — the ring-3 process that turns the device //! tree into a running system: **the matcher and the supervisor** //! (docs/device-manager.md). The kernel enumerates the hardware and enforces the //! claim capability (mechanism); this decides which driver serves which device, //! spawns it, and keeps it alive (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. //! //! M18.1 (this increment): the manager is a harness service on the well-known //! `.device_manager` endpoint. Every driver is spawned **supervised** — exit //! notifications land in the same loop as protocol messages. Drivers with an //! assignment must `hello` within a deadline or be stopped; a driver that dies //! is restarted with backoff, and a crash loop (three fast deaths) marks it //! failed instead of respawning forever. Exit reasons (M17.2) drive the //! decision: a clean exit meant to stop; only faults and missed deadlines //! restart. Tree reports (`child_added`) land in M18.2. const std = @import("std"); const runtime = @import("runtime"); const acpi_ids = @import("acpi-ids"); const protocol = runtime.device_manager_protocol; 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 a manifest (docs/device-manager.md: the third bus type /// triggers it); for now a static map. `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. A machine can carry /// several identical controllers — one driver instance per device, the id as /// argv[1]. These drivers speak the protocol: a hello is expected. 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, }; } // --- supervision ------------------------------------------------------------- /// How long a protocol driver has to hello after its spawn. const hello_deadline_ms: u64 = 3000; /// Deaths faster than this count toward the crash loop; slower ones reset it. const fast_death_ns: u64 = 2_000_000_000; /// Consecutive fast deaths before the manager gives up on a driver. const crash_loop_cap: u32 = 3; /// Restart backoff: base << (restarts - 1), so 300 ms, 600 ms, 1200 ms. const backoff_base_ms: u64 = 300; const DriverState = enum { awaiting_hello, // spawned; the deadline is armed (protocol drivers only) running, restarting, // dead; respawn due at restart_due_ns stopped, // exited cleanly — it meant to; not restarted failed, // crash loop, or unspawnable; the manager gave up }; const Driver = struct { used: bool = false, name_buffer: [24]u8 = undefined, name_len: usize = 0, // The assigned device id (becomes argv[1]), or protocol.no_device. device_id: u64 = protocol.no_device, // Whether this driver speaks the protocol (hello expected, deadline // enforced). Legacy drivers (hpet, ps2-bus) are supervised and restarted // but not yet required to hello. speaks_protocol: bool = false, process_id: u32 = 0, state: DriverState = .running, restarts: u32 = 0, spawn_ns: u64 = 0, hello_deadline_ns: u64 = 0, restart_due_ns: u64 = 0, fn name(driver: *const Driver) []const u8 { return driver.name_buffer[0..driver.name_len]; } }; const maximum_drivers = 16; var drivers: [maximum_drivers]Driver = .{Driver{}} ** maximum_drivers; var manager_endpoint: runtime.ipc.Handle = 0; var test_restart_mode = false; fn driverByProcess(process_id: u32) ?*Driver { for (&drivers) |*driver| { if (driver.used and driver.process_id == process_id) return driver; } return null; } /// Whether a singleton driver is already in the table (two ACPI nodes can both /// map to ps2-bus; one instance serves both). fn alreadySupervised(name: []const u8) bool { for (&drivers) |*driver| { if (driver.used and std.mem.eql(u8, driver.name(), name)) return true; } return false; } /// Record a driver in the table and spawn its first instance. fn addDriver(name: []const u8, device_id: u64, speaks_protocol: bool) void { for (&drivers) |*driver| { if (driver.used) continue; const n = @min(name.len, driver.name_buffer.len); @memcpy(driver.name_buffer[0..n], name[0..n]); driver.name_len = n; driver.device_id = device_id; driver.speaks_protocol = speaks_protocol; driver.used = true; spawnDriver(driver); return; } writeLine("device-manager: driver table full; cannot supervise {s}\n", .{name}); } /// (Re)spawn a driver instance: supervised on the manager's own endpoint, the /// device id as argv[1] when it has one, the hello deadline armed when it /// speaks the protocol. fn spawnDriver(driver: *Driver) void { var id_text: [20]u8 = undefined; var arguments: [1][]const u8 = undefined; var argument_count: usize = 0; if (driver.device_id != protocol.no_device) { arguments[0] = std.fmt.bufPrint(&id_text, "{d}", .{driver.device_id}) catch return; argument_count = 1; } const child = system.spawnSupervised(driver.name(), arguments[0..argument_count], manager_endpoint) orelse { writeLine("device-manager: failed to spawn {s}\n", .{driver.name()}); driver.state = .failed; return; }; driver.process_id = child; driver.spawn_ns = system.clock(); if (driver.speaks_protocol) { driver.state = .awaiting_hello; driver.hello_deadline_ns = driver.spawn_ns + hello_deadline_ms * 1_000_000; _ = system.timerOnce(manager_endpoint, hello_deadline_ms + 100); } else { driver.state = .running; } if (driver.device_id != protocol.no_device) { writeLine("device-manager: spawned {s} for device {d}\n", .{ driver.name(), driver.device_id }); } else { writeLine("device-manager: spawned {s}\n", .{driver.name()}); } } /// A driver died. The exit reason (M17.2) is the whole decision: a clean exit /// meant to stop; anything else restarts with backoff until the crash-loop cap. fn onDriverExit(driver: *Driver) void { const reason = runtime.process.exitReason(driver.process_id) orelse .fault; if (reason == .exited) { driver.state = .stopped; writeLine("device-manager: {s} exited cleanly; not restarting\n", .{driver.name()}); return; } const now = system.clock(); const alive_ns = now - driver.spawn_ns; driver.restarts = if (alive_ns < fast_death_ns) driver.restarts + 1 else 1; if (driver.restarts >= crash_loop_cap) { driver.state = .failed; writeLine("device-manager: {s} is failing repeatedly (crash loop); giving up\n", .{driver.name()}); return; } const delay_ms = backoff_base_ms << @intCast(driver.restarts - 1); driver.state = .restarting; driver.restart_due_ns = now + delay_ms * 1_000_000; writeLine("device-manager: restarting {s} in {d} ms (died: {s})\n", .{ driver.name(), delay_ms, @tagName(reason) }); _ = system.timerOnce(manager_endpoint, delay_ms + 50); } /// A timer landed: sweep every deadline. Overdue hellos are killed (the exit /// notification then routes through the normal restart policy); due restarts /// respawn. Timers carry no id on purpose — the table is the state, and one /// sweep serves every armed deadline. fn sweepDeadlines() void { const now = system.clock(); for (&drivers) |*driver| { if (!driver.used) continue; switch (driver.state) { .awaiting_hello => if (now >= driver.hello_deadline_ns) { writeLine("device-manager: {s} missed its hello deadline\n", .{driver.name()}); _ = system.kill(driver.process_id); // The exit notification finishes the job via onDriverExit. }, .restarting => if (now >= driver.restart_due_ns) spawnDriver(driver), else => {}, } } } // --- the harness callbacks ----------------------------------------------------- fn initialise(endpoint: runtime.ipc.Handle) bool { manager_endpoint = endpoint; // 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 false; }; 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; addDriver(driver_name, descriptor.id, true); continue; } const driver_name = driverFor(descriptor) orelse continue; matched += 1; // Skip a singleton that is already alive (the initial-ramdisk sweep test // starts every bundled binary bare, this manager included) — spawning a // second instance would only lose the claim race and churn the log. if (!alreadySupervised(driver_name) and !system.isProcessRunning(driver_name)) { addDriver(driver_name, protocol.no_device, false); } } if (test_restart_mode) { // The driver-restart scenario's fixture: claims device 0 (the tree // root, otherwise unclaimed), hellos, then faults — driving backoff, // re-claim-after-death, and the crash-loop cap deterministically. addDriver("crash-test", 0, true); } if (matched == 0) { _ = runtime.system.write("device-manager: no matchable devices\n"); } else { _ = runtime.system.write("device-manager: ok\n"); } return true; } fn onMessage(message: []const u8, reply: []u8, sender: u32) usize { if (message.len < protocol.hello_size) return 0; const hello = std.mem.bytesToValue(protocol.Hello, message[0..protocol.hello_size]); if (hello.operation != @intFromEnum(protocol.Operation.hello)) return 0; var status: i32 = 0; if (hello.version != protocol.version) { status = -1; writeLine("device-manager: refused hello (version {d}) from process {d}\n", .{ hello.version, sender }); } else if (driverByProcess(sender)) |driver| { driver.state = .running; writeLine("device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id }); } else { status = -1; writeLine("device-manager: hello from unknown process {d}\n", .{sender}); } const hello_reply = protocol.HelloReply{ .status = status }; @memcpy(reply[0..protocol.reply_size], std.mem.asBytes(&hello_reply)); return protocol.reply_size; } fn onNotification(badge: u64) void { if (badge & runtime.ipc.notify_exit_bit != 0) { const dead: u32 = @intCast(badge & ~(runtime.ipc.notify_badge_bit | runtime.ipc.notify_exit_bit)); if (driverByProcess(dead)) |driver| onDriverExit(driver); return; } if (badge & runtime.ipc.notify_timer_bit != 0) sweepDeadlines(); } pub fn main(init: runtime.process.Init) void { if (init.arguments.get(1)) |mode| { test_restart_mode = std.mem.eql(u8, mode, "test-restart"); } runtime.service.run(protocol.message_maximum, .{ .service = .device_manager, .init = initialise, .on_message = onMessage, .on_notification = onNotification, }); } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; // pull the runtime entry shim into the image }