//! /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 pci_class = @import("pci-class");
const usb_ids = @import("usb-ids");
const protocol = runtime.device_manager_protocol;
const device = runtime.device;
const system = runtime.system;

/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller —
/// Serial Bus Controller / USB Controller / XHCI — named from pci-class.zig rather
/// than written as the bare 0x0C0330 (docs/coding-standards.md, "Named values").
const xhci_pci_class: u64 = pci_class.ClassCode.pack(.{
    .base = @intFromEnum(pci_class.BaseClass.serial_bus),
    .subclass = @intFromEnum(pci_class.serial_bus.SubClass.usb),
    .prog_if = @intFromEnum(pci_class.serial_bus.usb.ProgIf.xhci),
});

/// The PCI class triple of a virtio-gpu — Display Controller / Other (0x80) / 0. The class
/// alone cannot tell it from any other display/other function, so the driver re-confirms
/// vendor 0x1AF4 / device 0x1050 from config space once spawned; this only gets it spawned.
const virtio_gpu_pci_class: u64 = pci_class.ClassCode.pack(.{
    .base = @intFromEnum(pci_class.BaseClass.display),
    .subclass = 0x80, // "Other" — no named SubClass member (PCI convention)
    .prog_if = 0,
});

/// The driver that serves a *reported* PCI function (M19.3: matching moved
/// from the boot snapshot to the bus reports), or null. A machine can carry
/// several identical controllers — one driver instance per reported device,
/// its registered id as argv[1].
fn pciDriverForIdentity(identity: u64) ?[]const u8 {
    return switch (identity) {
        xhci_pci_class => "/system/drivers/usb-xhci-bus",
        virtio_gpu_pci_class => "/system/drivers/virtio-gpu",
        else => null,
    };
}

/// The driver that serves a *reported* ACPI device by its `_HID` (M20.3:
/// ps2-bus now binds the PS/2 nodes the acpi service reports, not boot-snapshot
/// nodes the kernel used to build). ps2-bus is a singleton that finds both its
/// devices by hid once spawned, so keyboard and mouse map to the same name.
fn hidDriverFor(hid: []const u8) ?[]const u8 {
    if (std.mem.eql(u8, hid, "PNP0303")) return "/system/drivers/ps2-bus"; // PS/2 keyboard
    if (std.mem.eql(u8, hid, "PNP0F13")) return "/system/drivers/ps2-bus"; // PS/2 mouse
    return null;
}

/// The driver that serves a *reported* USB interface by its (class, subclass,
/// protocol) triple — the third bus after PCI and ACPI (docs/device-manager.md:
/// matching stays code until the third bus). The xHCI bus driver reports each
/// interface with this packed triple as its identity; the matched class driver is
/// spawned with the interface's registered id as argv[1], which it presents to the
/// bus driver to open the device.
fn usbDriverForIdentity(identity: u64) ?[]const u8 {
    const keyboard = comptime usb_ids.packTriple(
        @intFromEnum(usb_ids.Class.hid),
        @intFromEnum(usb_ids.hid.SubClass.boot),
        @intFromEnum(usb_ids.hid.Protocol.keyboard),
    );
    const mouse = comptime usb_ids.packTriple(
        @intFromEnum(usb_ids.Class.hid),
        @intFromEnum(usb_ids.hid.SubClass.boot),
        @intFromEnum(usb_ids.hid.Protocol.mouse),
    );
    const storage = comptime usb_ids.packTriple(
        @intFromEnum(usb_ids.Class.mass_storage),
        @intFromEnum(usb_ids.mass_storage.SubClass.scsi),
        @intFromEnum(usb_ids.mass_storage.Protocol.bulk_only),
    );
    return switch (identity) {
        keyboard => "/system/drivers/usb-hid-keyboard",
        mouse => "/system/drivers/usb-hid-mouse",
        storage => "/system/drivers/usb-storage",
        else => null,
    };
}

/// Whether some driver entry already serves registered device `device_id` —
/// a re-report after a bus restart must not spawn a second instance.
fn driverForDevice(device_id: u64) bool {
    for (&drivers) |*driver| {
        if (driver.used and driver.device_id == device_id) return true;
    }
    return false;
}

// --- 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: [64]u8 = undefined, // fits a full binary path (abi.maximum_process_name)
    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 (e.g. 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;
var test_usb_restart_mode = false;
var test_usb_killed = false;
var test_pci_restart_mode = false;
var test_scanout_restart_mode = false;
var test_scanout_killed = false;
var test_kill_pid: u32 = 0;
var test_kill_due_ns: u64 = 0;

/// The application subscribers (M18.3, the input-service pattern): endpoints
/// handed over as capabilities, each receiving every child add/remove as a
/// buffered message. A subscriber whose endpoint stops accepting (it died) is
/// dropped on the failed send.
const maximum_subscribers = 8;
var subscribers: [maximum_subscribers]?runtime.ipc.Handle = .{null} ** maximum_subscribers;

/// Publish one event (a ChildAdded or ChildRemoved struct, the same encoding
/// the bus drivers send) to every subscriber.
fn publishEvent(event: []const u8) void {
    for (&subscribers) |*slot| {
        if (slot.*) |handle| {
            if (!runtime.ipc.send(handle, event)) slot.* = null; // dead subscriber
        }
    }
}

/// The manager's mirror of what bus drivers report (docs/device-manager.md "the
/// tree"): the children, keyed by (parent, bus address), each remembering which
/// driver instance reported it — that is what death-pruning sweeps by.
const Child = struct {
    used: bool = false,
    parent: u64 = 0,
    bus_address: u64 = 0,
    identity: u64 = 0,
    // The kernel device id (registered by the reporter), or protocol.no_device.
    device_id: u64 = 0,
    reporter: u32 = 0, // the reporting driver instance's process id
};

const maximum_children = 64; // ACPI adds ~34 device nodes (M20.2), plus PCI + USB
var children: [maximum_children]Child = .{Child{}} ** maximum_children;

/// Record (or refresh) a reported child. Refreshing matters: a restarted bus
/// driver re-reports what it rediscovers, and the same (parent, port) must not
/// duplicate.
fn addChild(parent: u64, bus_address: u64, identity: u64, device_id: u64, reporter: u32) bool {
    var free: ?*Child = null;
    for (&children) |*child| {
        if (child.used and child.parent == parent and child.bus_address == bus_address) {
            child.identity = identity;
            child.device_id = device_id;
            child.reporter = reporter;
            return true;
        }
        if (!child.used and free == null) free = child;
    }
    const slot = free orelse return false;
    slot.* = .{ .used = true, .parent = parent, .bus_address = bus_address, .identity = identity, .device_id = device_id, .reporter = reporter };
    return true;
}

/// Prune every child a dead driver instance reported: the children describe
/// protocol state (slots, rings) that died with the process — keeping the nodes
/// would be keeping a lie. The restarted instance rediscovers and re-reports.
/// Watchers hear the honest story: removed now, added again on rediscovery.
fn pruneChildrenOf(reporter: u32) void {
    for (&children) |*child| {
        if (child.used and child.reporter == reporter) {
            std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address });
            child.used = false;
            const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address };
            publishEvent(std.mem.asBytes(&event));
        }
    }
}

/// How many children a driver instance has reported (the test-usb-restart
/// trigger counts these).
fn childCountOf(reporter: u32) u32 {
    var n: u32 = 0;
    for (&children) |*child| {
        if (child.used and child.reporter == reporter) n += 1;
    }
    return n;
}

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;
    }
    std.log.info("driver table full; cannot supervise {s}", .{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 {
        std.log.info("failed to spawn {s}", .{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) {
        std.log.info("spawned {s} for device {d}", .{ driver.name(), driver.device_id });
    } else {
        std.log.info("spawned {s}", .{driver.name()});
    }
}

/// A driver died. Prune what it reported first — then the exit reason (M17.2)
/// is the whole restart decision: a clean exit meant to stop; anything else
/// restarts with backoff until the crash-loop cap.
fn onDriverExit(driver: *Driver) void {
    pruneChildrenOf(driver.process_id);
    const reason = runtime.process.exitReason(driver.process_id) orelse .fault;
    if (reason == .exited) {
        driver.state = .stopped;
        std.log.info("{s} exited cleanly; not restarting", .{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;
        std.log.info("{s} is failing repeatedly (crash loop); giving up", .{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;
    std.log.info("restarting {s} in {d} ms (died: {s})", .{ 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();
    if (test_kill_pid != 0 and now >= test_kill_due_ns) {
        std.log.info("test mode: killing the reporter", .{});
        _ = system.kill(test_kill_pid);
        test_kill_pid = 0;
    }
    for (&drivers) |*driver| {
        if (!driver.used) continue;
        switch (driver.state) {
            .awaiting_hello => if (now >= driver.hello_deadline_ns) {
                std.log.info("{s} missed its hello deadline", .{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("/system/services/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 (descriptor.class == @intFromEnum(device.DeviceClass.pci_host_bridge)) {
            // The PCI bus driver: enumeration in ring 3 (M19), one instance
            // per bridge, the bridge id as its assignment.
            matched += 1;
            addDriver("pci-bus", descriptor.id, true);
            continue;
        }
        // Nothing else is matched from the boot snapshot today. The kernel-seeded
        // HPET timer node is served by the kernel's own clock (docs/timers.md), not
        // a user-space driver; PCI functions and PS/2 _HID devices arrive later as
        // pci-bus / acpi-service reports and match in onChildAdded (docs/discovery.md).
        // A fuller system's static class->driver manifest (docs/device-manager.md)
        // would slot in here.
    }

    // The discovery service (docs/discovery.md): one per firmware, packed
    // under the neutral name "discovery", spawned once at startup. It finds and
    // claims the acpi-tables (or devicetree-blob) node itself. Not a per-device
    // match — it is the discoverer, not a driver bound to one device.
    addDriver("discovery", 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("/system/services/device-manager: no matchable devices\n");
    } else {
        _ = runtime.system.write("/system/services/device-manager: ok\n");
    }
    return true;
}

fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
    if (message.len < 1) return 0;
    switch (message[0]) {
        @intFromEnum(protocol.Operation.child_added) => return onChildAdded(message, reply, sender),
        @intFromEnum(protocol.Operation.child_removed) => return onChildRemoved(message, reply, sender),
        @intFromEnum(protocol.Operation.enumerate) => return onEnumerate(reply),
        @intFromEnum(protocol.Operation.subscribe) => return onSubscribe(reply, capability),
        @intFromEnum(protocol.Operation.hello) => {},
        else => return 0,
    }
    if (message.len < protocol.hello_size) return 0;
    const hello = std.mem.bytesToValue(protocol.Hello, message[0..protocol.hello_size]);

    var status: i32 = 0;
    if (hello.version != protocol.version) {
        status = -1;
        std.log.info("refused hello (version {d}) from process {d}", .{ hello.version, sender });
    } else if (driverByProcess(sender)) |driver| {
        driver.state = .running;
        std.log.info("hello from {s} (device {d})", .{ driver.name(), hello.device_id });
        // Resilience drill (V6): once, kill the virtio-gpu driver a moment after it hellos, so
        // the normal restart policy respawns it — the compositor must survive and re-attach.
        if (test_scanout_restart_mode and !test_scanout_killed and std.mem.eql(u8, driver.name(), "/system/drivers/virtio-gpu")) {
            test_scanout_killed = true;
            test_kill_pid = sender;
            test_kill_due_ns = system.clock() + 1_500_000_000;
            _ = system.timerOnce(manager_endpoint, 1600);
        }
    } else {
        status = -1;
        std.log.info("hello from unknown process {d}", .{sender});
    }
    const hello_reply = protocol.HelloReply{ .status = status };
    @memcpy(reply[0..protocol.reply_size], std.mem.asBytes(&hello_reply));
    return protocol.reply_size;
}

/// A bus driver reported a discovered device: mirror it, and in
/// test-usb-restart mode kill the reporter once after its second child — the
/// deterministic trigger for prune -> backoff -> respawn -> re-report.
fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
    if (message.len < protocol.child_added_size) return 0;
    const report = std.mem.bytesToValue(protocol.ChildAdded, message[0..protocol.child_added_size]);
    var status: i32 = 0;
    if (driverByProcess(sender)) |driver| {
        if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1;
        std.log.info("child added (device {d} port {d}, identity {d}) by {s}", .{ report.parent, report.bus_address, report.identity, driver.name() });
        if (status == 0) publishEvent(message[0..protocol.child_added_size]);
        // Matching from reports (M19.3): a registered child whose identity
        // names a driver gets one, once — re-reports after a bus restart
        // dedupe on the registered id, exactly like the registrations do.
        if (status == 0 and report.device_id != protocol.no_device) {
            if (pciDriverForIdentity(report.identity)) |child_driver| {
                if (!driverForDevice(report.device_id)) addDriver(child_driver, report.device_id, true);
            }
            // USB interface match: the reported identity is the packed class triple,
            // and the class driver is spawned with the interface's registered id.
            if (usbDriverForIdentity(report.identity)) |usb_driver| {
                if (!driverForDevice(report.device_id)) addDriver(usb_driver, report.device_id, true);
            }
            // ACPI _HID match (M20.3): ps2-bus is a singleton that finds its own
            // devices by hid, so spawn it once, without a device assignment.
            const hid_len = std.mem.indexOfScalar(u8, &report.hid, 0) orelse report.hid.len;
            if (hid_len != 0) {
                if (hidDriverFor(report.hid[0..hid_len])) |hid_driver| {
                    if (!alreadySupervised(hid_driver)) addDriver(hid_driver, protocol.no_device, false);
                }
            }
        }
    } else {
        status = -1;
    }
    const report_reply = protocol.ReportReply{ .status = status };
    @memcpy(reply[0..@sizeOf(protocol.ReportReply)], std.mem.asBytes(&report_reply));
    if (test_pci_restart_mode and !test_usb_killed) {
        if (driverByProcess(sender)) |driver| {
            if (std.mem.eql(u8, driver.name(), "pci-bus") and childCountOf(sender) >= 3) {
                // The pci restart drill: kill the enumerator after it has
                // reported; the respawn must re-register without duplicates
                // (M19.0 idempotence, proven end to end by pci-scan).
                test_usb_killed = true;
                test_kill_pid = sender;
                test_kill_due_ns = system.clock() + 1_000_000_000;
                _ = system.timerOnce(manager_endpoint, 1100);
            }
        }
    }
    if (test_usb_restart_mode and !test_usb_killed and childCountOf(sender) >= 2) {
        // Only the xHCI reporter is the drill's victim — pci-bus also reports
        // now, and whichever finishes second must not trigger the kill.
        if (driverByProcess(sender)) |driver| {
            if (std.mem.eql(u8, driver.name(), "/system/drivers/usb-xhci-bus")) {
                // Delayed, not immediate: the device-list scenario's subscriber
                // needs a window to enumerate and subscribe before the events.
                test_usb_killed = true;
                test_kill_pid = sender;
                test_kill_due_ns = system.clock() + 2_000_000_000;
                _ = system.timerOnce(manager_endpoint, 2100);
            }
        }
    }
    return @sizeOf(protocol.ReportReply);
}

/// A bus driver reported a device gone (hot-unplug; no sender exists yet, but
/// the handler is protocol-complete — death-pruning covers removal until then).
fn onChildRemoved(message: []const u8, reply: []u8, sender: u32) usize {
    if (message.len < protocol.child_removed_size) return 0;
    const report = std.mem.bytesToValue(protocol.ChildRemoved, message[0..protocol.child_removed_size]);
    var status: i32 = -1;
    for (&children) |*child| {
        if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == sender) {
            std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address });
            child.used = false;
            status = 0;
        }
    }
    const report_reply = protocol.ReportReply{ .status = status };
    @memcpy(reply[0..@sizeOf(protocol.ReportReply)], std.mem.asBytes(&report_reply));
    return @sizeOf(protocol.ReportReply);
}

/// An application asked for the tree: the mirror, as a header plus entries.
fn onEnumerate(reply: []u8) usize {
    var count: u32 = 0;
    var offset: usize = @sizeOf(protocol.EnumerateReply);
    for (&children) |*child| {
        if (!child.used) continue;
        if (offset + @sizeOf(protocol.ChildEntry) > reply.len) break;
        const entry = protocol.ChildEntry{ .parent = child.parent, .bus_address = child.bus_address, .identity = child.identity };
        @memcpy(reply[offset..][0..@sizeOf(protocol.ChildEntry)], std.mem.asBytes(&entry));
        offset += @sizeOf(protocol.ChildEntry);
        count += 1;
    }
    const header = protocol.EnumerateReply{ .status = 0, .count = count };
    @memcpy(reply[0..@sizeOf(protocol.EnumerateReply)], std.mem.asBytes(&header));
    return offset;
}

/// An application subscribed: its endpoint arrived as the call's capability.
fn onSubscribe(reply: []u8, capability: ?runtime.ipc.Handle) usize {
    var status: i32 = -1;
    if (capability) |handle| {
        for (&subscribers) |*slot| {
            if (slot.* == null) {
                slot.* = handle;
                status = 0;
                break;
            }
        }
    }
    const report_reply = protocol.ReportReply{ .status = status };
    @memcpy(reply[0..@sizeOf(protocol.ReportReply)], std.mem.asBytes(&report_reply));
    return @sizeOf(protocol.ReportReply);
}

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");
        test_usb_restart_mode = std.mem.eql(u8, mode, "test-usb-restart");
        test_pci_restart_mode = std.mem.eql(u8, mode, "test-pci-restart");
        test_scanout_restart_mode = std.mem.eql(u8, mode, "test-scanout-restart");
    }
    runtime.service.run(protocol.message_maximum, .{
        .service = .device_manager,
        .init = initialise,
        .on_message = onMessage,
        .on_notification = onNotification,
    });
}
