527 lines
23 KiB
Zig
527 lines
23 KiB
Zig
//! /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 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 => "usb-xhci-bus",
|
|
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: [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;
|
|
var test_usb_restart_mode = false;
|
|
var test_usb_killed = false;
|
|
var test_pci_restart_mode = 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 = 32;
|
|
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) {
|
|
writeLine("device-manager: child removed (device {d} port {d})\n", .{ 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;
|
|
}
|
|
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. 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;
|
|
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();
|
|
if (test_kill_pid != 0 and now >= test_kill_due_ns) {
|
|
writeLine("device-manager: test mode: killing the reporter\n", .{});
|
|
_ = 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) {
|
|
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 (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;
|
|
}
|
|
// PCI functions no longer appear in the boot snapshot (M19.3): the
|
|
// pci-bus driver reports them, and onChildAdded matches from reports.
|
|
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, 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;
|
|
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;
|
|
}
|
|
|
|
/// 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;
|
|
writeLine("device-manager: child added (device {d} port {d}, identity {d}) by {s}\n", .{ 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);
|
|
}
|
|
}
|
|
} 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(), "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) {
|
|
writeLine("device-manager: child removed (device {d} port {d})\n", .{ 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");
|
|
}
|
|
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
|
|
}
|