A protocol is reached by name now, not by a compile-time integer. Init is PID 1 and already knows which binary it started, so init serves /protocol as a vfs backend: bind claims a contract with the provider's endpoint attached, open answers with that endpoint as the reply's capability, and readdir lists what is bound with the task and binary behind it. The kernel reserves the prefix — nothing may mount over it, under it, or unmount it — and ServiceId, ipc_register and ipc_lookup are gone, their syscall numbers left vacant. A bind is authorized by who the caller *is*: the kernel-stamped binary together with the supervising task's identity, matched against /system/configuration/protocol.csv. Identity, not spelling — spawn is ungated, so an attacker can run any bundled binary, and a name-only rule would have let it launder grants through an init of its own making. A name a live process holds is refused to everyone else; a dead one's is released. Three review rounds against a hostile ring-3 process found what 108 green tests could not, because the suite contains no attacker. Publishing init's supervision endpoint as the registry put PID 1's mailbox in every process's hands, where two forged bytes reached the shutdown path: privileged traffic is now believed only from the task that holds the contract it speaks for. A capability arriving on a request outlived every path that ignored it, one handle per call until the table was full — in init, and in the harness ten services share — so the arriving capability is owned by the turn and released unless a handler says otherwise. And the kernel let anyone holding an endpoint handle aim signals, timers, exit notices and interrupts at it: binding now requires having created it. Suite 108/108. The new protocol-registry case asserts eleven properties, each one an attack that must fail.
577 lines
26 KiB
Zig
577 lines
26 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 device = @import("driver");
|
|
const ipc = @import("ipc");
|
|
const process = @import("process");
|
|
const service = @import("service");
|
|
const time = @import("time");
|
|
const memory = @import("memory");
|
|
const logging = @import("logging");
|
|
const device_manager_protocol = @import("device-manager-protocol");
|
|
const registry = @import("device-registry");
|
|
const fs = @import("file-system");
|
|
|
|
// --- the device registry ------------------------------------------------------
|
|
// Driver matching is data-driven and authoritative: /system/configuration/devices.csv (parsed by
|
|
// the device-registry module) names, per bus, which driver binds a reported
|
|
// device, the most-specific match winning. There is no compiled-in fallback — a
|
|
// device no row matches goes unbound and is logged. This retired the hand-kept
|
|
// pciDriverForIdentity / hidDriverFor / usbDriverForIdentity switch tables
|
|
// (docs/device-manager.md: "matching stays code until the third bus").
|
|
|
|
/// The CSV bytes, held for the life of the process because the parsed rules'
|
|
/// string fields (hid, driver) slice into this buffer.
|
|
var registry_source: [8192]u8 = undefined;
|
|
var registry_rules: [64]registry.Rule = undefined;
|
|
var registry_count: usize = 0;
|
|
|
|
/// Read and parse /system/configuration/devices.csv once at boot. The file lives in the initial
|
|
/// ramdisk, which the kernel serves directly — no filesystem service need be up
|
|
/// (fat is spawned after the manager), so this is a plain fs.open + read.
|
|
fn loadRegistry() void {
|
|
var file = fs.open("/system/configuration/devices.csv", .{}) orelse {
|
|
_ = logging.write("/system/services/device-manager: /system/configuration/devices.csv missing — nothing will match\n");
|
|
return;
|
|
};
|
|
defer file.close();
|
|
var used: usize = 0;
|
|
while (used < registry_source.len) {
|
|
const n = file.read(registry_source[used..]) orelse break;
|
|
if (n == 0) break;
|
|
used += n;
|
|
}
|
|
const result = registry.parse(registry_source[0..used], ®istry_rules);
|
|
registry_count = result.count;
|
|
if (result.malformed != 0) std.log.info("/system/configuration/devices.csv: {d} malformed line(s) skipped", .{result.malformed});
|
|
if (result.truncated) _ = logging.write("/system/services/device-manager: /system/configuration/devices.csv has more rules than the table holds\n");
|
|
std.log.info("/system/configuration/devices.csv: {d} rule(s) loaded", .{registry_count});
|
|
}
|
|
|
|
/// Build a registry Identity from a bus driver's report: the bus it named, the
|
|
/// class triple unpacked from `identity` (0xCCSSPP — the same packing for a PCI
|
|
/// class code and a USB class triple), the widened numeric ids, and the ACPI hid.
|
|
fn identityFromReport(report: device_manager_protocol.ChildAdded) registry.Identity {
|
|
const bus: registry.Bus = switch (report.bus) {
|
|
@intFromEnum(device_manager_protocol.BusKind.pci) => .pci,
|
|
@intFromEnum(device_manager_protocol.BusKind.usb) => .usb,
|
|
@intFromEnum(device_manager_protocol.BusKind.acpi) => .acpi,
|
|
else => .unknown,
|
|
};
|
|
const hid_len = std.mem.indexOfScalar(u8, &report.hid, 0) orelse report.hid.len;
|
|
return .{
|
|
.bus = bus,
|
|
.base = @truncate(report.identity >> 16),
|
|
.subclass = @truncate(report.identity >> 8),
|
|
.prog_if = @truncate(report.identity),
|
|
.vendor = report.vendor,
|
|
.device = report.device,
|
|
.subsystem = report.subsystem,
|
|
.hid = report.hid[0..hid_len],
|
|
};
|
|
}
|
|
|
|
/// 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 device_manager_protocol.no_device.
|
|
device_id: u64 = device_manager_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: 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]?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 (!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 device_manager_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 = device_manager_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 != device_manager_protocol.no_device) {
|
|
arguments[0] = std.fmt.bufPrint(&id_text, "{d}", .{driver.device_id}) catch return;
|
|
argument_count = 1;
|
|
}
|
|
const child = process.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 = time.clock();
|
|
if (driver.speaks_protocol) {
|
|
driver.state = .awaiting_hello;
|
|
driver.hello_deadline_ns = driver.spawn_ns + hello_deadline_ms * 1_000_000;
|
|
_ = time.timerOnce(manager_endpoint, hello_deadline_ms + 100);
|
|
} else {
|
|
driver.state = .running;
|
|
}
|
|
if (driver.device_id != device_manager_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 = 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 = time.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) });
|
|
_ = time.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 = time.clock();
|
|
if (test_kill_pid != 0 and now >= test_kill_due_ns) {
|
|
std.log.info("test mode: killing the reporter", .{});
|
|
_ = process.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()});
|
|
_ = process.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: ipc.Handle) bool {
|
|
manager_endpoint = endpoint;
|
|
|
|
// Load the authoritative driver-match registry before any bus driver can
|
|
// report a device to match against it.
|
|
loadRegistry();
|
|
|
|
// Enumerate into a heap buffer (too big for the one-page user stack).
|
|
const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
|
_ = logging.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", device_manager_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) {
|
|
_ = logging.write("/system/services/device-manager: no matchable devices\n");
|
|
} else {
|
|
_ = logging.write("/system/services/device-manager: ok\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arrival) usize {
|
|
if (message.len < 1) return 0;
|
|
switch (message[0]) {
|
|
@intFromEnum(device_manager_protocol.Operation.child_added) => return onChildAdded(message, reply, sender),
|
|
@intFromEnum(device_manager_protocol.Operation.child_removed) => return onChildRemoved(message, reply, sender),
|
|
@intFromEnum(device_manager_protocol.Operation.enumerate) => return onEnumerate(reply),
|
|
@intFromEnum(device_manager_protocol.Operation.subscribe) => return onSubscribe(reply, arrived),
|
|
@intFromEnum(device_manager_protocol.Operation.hello) => {},
|
|
else => return 0,
|
|
}
|
|
if (message.len < device_manager_protocol.hello_size) return 0;
|
|
const hello = std.mem.bytesToValue(device_manager_protocol.Hello, message[0..device_manager_protocol.hello_size]);
|
|
|
|
var status: i32 = 0;
|
|
if (hello.version != device_manager_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 = time.clock() + 1_500_000_000;
|
|
_ = time.timerOnce(manager_endpoint, 1600);
|
|
}
|
|
} else {
|
|
status = -1;
|
|
std.log.info("hello from unknown process {d}", .{sender});
|
|
}
|
|
const hello_reply = device_manager_protocol.HelloReply{ .status = status };
|
|
@memcpy(reply[0..device_manager_protocol.reply_size], std.mem.asBytes(&hello_reply));
|
|
return device_manager_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 < device_manager_protocol.child_added_size) return 0;
|
|
const report = std.mem.bytesToValue(device_manager_protocol.ChildAdded, message[0..device_manager_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..device_manager_protocol.child_added_size]);
|
|
// Matching from reports (M19.3), now data-driven via the /system/configuration/devices.csv
|
|
// registry: a registered child gets the most-specific driver its identity
|
|
// matches, once — re-reports after a bus restart dedupe on the registered
|
|
// id, exactly like the registrations do.
|
|
if (status == 0 and report.device_id != device_manager_protocol.no_device) {
|
|
const id = identityFromReport(report);
|
|
if (registry.matchDriver(registry_rules[0..registry_count], id)) |match| {
|
|
if (match.ambiguous)
|
|
std.log.info("/system/configuration/devices.csv: multiple equally-specific rules match the device {s} reported; binding {s}", .{ driver.name(), match.driver });
|
|
if (id.bus == .acpi) {
|
|
// An hid-matched driver (ps2-bus) is a singleton that finds its
|
|
// own devices once spawned — spawn it once, no device assignment.
|
|
if (!alreadySupervised(match.driver)) addDriver(match.driver, device_manager_protocol.no_device, false);
|
|
} else {
|
|
// A per-device driver: one instance, the registered id as argv[1].
|
|
if (!driverForDevice(report.device_id)) addDriver(match.driver, report.device_id, true);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
status = -1;
|
|
}
|
|
const report_reply = device_manager_protocol.ReportReply{ .status = status };
|
|
@memcpy(reply[0..@sizeOf(device_manager_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 = time.clock() + 1_000_000_000;
|
|
_ = time.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 = time.clock() + 2_000_000_000;
|
|
_ = time.timerOnce(manager_endpoint, 2100);
|
|
}
|
|
}
|
|
}
|
|
return @sizeOf(device_manager_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 < device_manager_protocol.child_removed_size) return 0;
|
|
const report = std.mem.bytesToValue(device_manager_protocol.ChildRemoved, message[0..device_manager_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 = device_manager_protocol.ReportReply{ .status = status };
|
|
@memcpy(reply[0..@sizeOf(device_manager_protocol.ReportReply)], std.mem.asBytes(&report_reply));
|
|
return @sizeOf(device_manager_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(device_manager_protocol.EnumerateReply);
|
|
for (&children) |*child| {
|
|
if (!child.used) continue;
|
|
if (offset + @sizeOf(device_manager_protocol.ChildEntry) > reply.len) break;
|
|
const entry = device_manager_protocol.ChildEntry{ .parent = child.parent, .bus_address = child.bus_address, .identity = child.identity };
|
|
@memcpy(reply[offset..][0..@sizeOf(device_manager_protocol.ChildEntry)], std.mem.asBytes(&entry));
|
|
offset += @sizeOf(device_manager_protocol.ChildEntry);
|
|
count += 1;
|
|
}
|
|
const header = device_manager_protocol.EnumerateReply{ .status = 0, .count = count };
|
|
@memcpy(reply[0..@sizeOf(device_manager_protocol.EnumerateReply)], std.mem.asBytes(&header));
|
|
return offset;
|
|
}
|
|
|
|
/// An application subscribed: its endpoint arrived as the call's capability. The
|
|
/// table taking a slot is what claims it (`take`); a full table refuses and lets
|
|
/// the turn close it, so a subscribe storm cannot spend the handle table too.
|
|
fn onSubscribe(reply: []u8, arrived: *ipc.Arrival) usize {
|
|
var status: i32 = -1;
|
|
if (arrived.peek() != null) {
|
|
for (&subscribers) |*slot| {
|
|
if (slot.* == null) {
|
|
slot.* = arrived.take(); // claimed: the table holds it from here
|
|
status = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const report_reply = device_manager_protocol.ReportReply{ .status = status };
|
|
@memcpy(reply[0..@sizeOf(device_manager_protocol.ReportReply)], std.mem.asBytes(&report_reply));
|
|
return @sizeOf(device_manager_protocol.ReportReply);
|
|
}
|
|
|
|
fn onNotification(badge: u64) void {
|
|
if (badge & ipc.notify_exit_bit != 0) {
|
|
const dead: u32 = @intCast(badge & ~(ipc.notify_badge_bit | ipc.notify_exit_bit));
|
|
if (driverByProcess(dead)) |driver| onDriverExit(driver);
|
|
return;
|
|
}
|
|
if (badge & ipc.notify_timer_bit != 0) sweepDeadlines();
|
|
}
|
|
|
|
pub fn main(init: 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");
|
|
}
|
|
service.run(device_manager_protocol.message_maximum, .{
|
|
.service = "device-manager",
|
|
.init = initialise,
|
|
.on_message = onMessage,
|
|
.on_notification = onNotification,
|
|
});
|
|
}
|