The application surface: enumerate, subscribe, and device-list (M18.3)
Applications ask the device manager for the tree (enumerate: a header plus ChildEntry records) and subscribe to published add/remove events by handing their endpoint over as the call's capability — the input-service pattern; events are the same ChildAdded/ChildRemoved structs the bus drivers send, one encoding in both directions. device-list is the first client: it prints the tree, subscribes, and narrates the events through a driver restart. The protocol's message maximum is capped at the kernel's IPC MESSAGE_MAXIMUM (256 bytes, ten entries per reply; paging joins the protocol when a tree outgrows one message). The startUserTask debug print is gone: it wrote to serial unserialized against user-space lines and sheared concurrent log markers in half — the root cause of the scenario flakes.
This commit is contained in:
@@ -342,6 +342,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
// A test fixture, not a real driver: hellos to the device manager, then faults —
|
// A test fixture, not a real driver: hellos to the device manager, then faults —
|
||||||
// what the driver-restart scenario drives the crash-loop cap with.
|
// what the driver-restart scenario drives the crash-loop cap with.
|
||||||
const crash_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "crash-test", "system/services/crash-test/crash-test.zig");
|
const crash_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "crash-test", "system/services/crash-test/crash-test.zig");
|
||||||
|
const device_list_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-list", "system/services/device-list/device-list.zig");
|
||||||
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||||
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
||||||
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
||||||
@@ -375,6 +376,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
mk_run.addFileArg(usb_xhci_bus_exe.getEmittedBin());
|
mk_run.addFileArg(usb_xhci_bus_exe.getEmittedBin());
|
||||||
mk_run.addArg("crash-test");
|
mk_run.addArg("crash-test");
|
||||||
mk_run.addFileArg(crash_test_exe.getEmittedBin());
|
mk_run.addFileArg(crash_test_exe.getEmittedBin());
|
||||||
|
mk_run.addArg("device-list");
|
||||||
|
mk_run.addFileArg(device_list_exe.getEmittedBin());
|
||||||
mk_run.addArg("device-manager");
|
mk_run.addArg("device-manager");
|
||||||
mk_run.addFileArg(device_manager_exe.getEmittedBin());
|
mk_run.addFileArg(device_manager_exe.getEmittedBin());
|
||||||
mk_run.addArg("input");
|
mk_run.addArg("input");
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ cap are in — usb-xhci-bus is the first conforming driver, and the
|
|||||||
Tree reports are built too (M18.2, 2026-07-13): the xHCI driver scans its
|
Tree reports are built too (M18.2, 2026-07-13): the xHCI driver scans its
|
||||||
root-hub ports and reports each connected device (`child_added`); the manager
|
root-hub ports and reports each connected device (`child_added`); the manager
|
||||||
mirrors them and prunes a dead reporter's children, and the `usb-report`
|
mirrors them and prunes a dead reporter's children, and the `usb-report`
|
||||||
scenario proves report → prune → respawn → re-report. The application surface
|
scenario proves report → prune → respawn → re-report. The application surface is built (M18.3, 2026-07-13):
|
||||||
(M18.3) remains design. The primitives underneath are real ([process-management.md](process-management.md):
|
`enumerate` and `subscribe` over IPC, with `device-list` as the first client —
|
||||||
|
the manager is now the one answer to "what devices exist" for applications.
|
||||||
|
The primitives underneath are real ([process-management.md](process-management.md):
|
||||||
spawn/supervise/kill/exit-notification; [driver-model.md](driver-model.md): the device
|
spawn/supervise/kill/exit-notification; [driver-model.md](driver-model.md): the device
|
||||||
table as a capability system; [drivers.md](drivers.md): claim/map/IRQ), and the first
|
table as a capability system; [drivers.md](drivers.md): claim/map/IRQ), and the first
|
||||||
per-device driver spawn works (the device manager matches the xHCI controller by PCI
|
per-device driver spawn works (the device manager matches the xHCI controller by PCI
|
||||||
|
|||||||
@@ -58,7 +58,12 @@ only when its definition of green holds.
|
|||||||
register BAR — resource 0 is ECAM — reads CAPLENGTH/HCSPARAMS1, scans
|
register BAR — resource 0 is ECAM — reads CAPLENGTH/HCSPARAMS1, scans
|
||||||
PORTSC, reports connected ports with speed-class identity; `usb-report`
|
PORTSC, reports connected ports with speed-class identity; `usb-report`
|
||||||
scenario proves report → prune → respawn → re-report; suite 53/53)
|
scenario proves report → prune → respawn → re-report; suite 53/53)
|
||||||
- [ ] **M18.3** — app surface: enumerate/subscribe + device-list
|
- [x] **M18.3** — app surface: enumerate/subscribe over IPC (subscriber
|
||||||
|
endpoint rides as the call's capability; events are the same structs the
|
||||||
|
buses send); device-list first client; protocol capped at the kernel's
|
||||||
|
IPC MESSAGE_MAXIMUM (256); the startUserTask debug print removed — it
|
||||||
|
sheared concurrent serial lines and was the scenario-flake root cause;
|
||||||
|
`device-list` scenario; suite 54/54)
|
||||||
- [ ] **merge** `feat/usb-xhci-bus` → main, push — **loop ends here**
|
- [ ] **merge** `feat/usb-xhci-bus` → main, push — **loop ends here**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ pub const Callbacks = struct {
|
|||||||
/// Return false to abort startup (the process exits).
|
/// Return false to abort startup (the process exits).
|
||||||
init: ?*const fn (endpoint: ipc.Handle) bool = null,
|
init: ?*const fn (endpoint: ipc.Handle) bool = null,
|
||||||
/// One protocol request from `sender` (a task id): write the reply into
|
/// One protocol request from `sender` (a task id): write the reply into
|
||||||
/// `reply`, return its length. The zero-length ping never reaches this.
|
/// `reply`, return its length. `capability` is the handle the request
|
||||||
on_message: *const fn (message: []const u8, reply: []u8, sender: u32) usize,
|
/// carried, if any (M13 cap passing — how a subscriber hands over its
|
||||||
|
/// endpoint). The zero-length ping never reaches this.
|
||||||
|
on_message: *const fn (message: []const u8, reply: []u8, sender: u32, capability: ?ipc.Handle) usize,
|
||||||
/// A notification that is not a signal — a subscribed exit event, a bound
|
/// A notification that is not a signal — a subscribed exit event, a bound
|
||||||
/// IRQ, a timer landing. The raw badge; decode with the ipc helpers.
|
/// IRQ, a timer landing. The raw badge; decode with the ipc helpers.
|
||||||
on_notification: ?*const fn (badge: u64) void = null,
|
on_notification: ?*const fn (badge: u64) void = null,
|
||||||
@@ -76,6 +78,6 @@ pub fn run(comptime maximum_message: usize, callbacks: Callbacks) void {
|
|||||||
reply_len = 0; // the universal ping: a zero-length reply, from the harness
|
reply_len = 0; // the universal ping: a zero-length reply, from the harness
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reply_len = callbacks.on_message(receive[0..got.len], &reply_buffer, got.senderTaskId());
|
reply_len = callbacks.on_message(receive[0..got.len], &reply_buffer, got.senderTaskId(), got.cap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,10 +146,11 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// No bus protocol to serve yet — transfer requests arrive with the USB track.
|
/// No bus protocol to serve yet — transfer requests arrive with the USB track.
|
||||||
fn onMessage(message: []const u8, reply: []u8, sender: u32) usize {
|
fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
|
||||||
_ = message;
|
_ = message;
|
||||||
_ = reply;
|
_ = reply;
|
||||||
_ = sender;
|
_ = sender;
|
||||||
|
_ = capability;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -350,8 +350,9 @@ pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, priority: Priority
|
|||||||
/// context switch and lock release.
|
/// context switch and lock release.
|
||||||
fn startUserTask() void {
|
fn startUserTask() void {
|
||||||
const t = current();
|
const t = current();
|
||||||
var buffer: [96]u8 = undefined;
|
// No serial chatter here: this runs on every spawn, unserialized against
|
||||||
architecture.serialWrite(std.fmt.bufPrint(&buffer, "DBG startUserTask ip=0x{x} sp=0x{x} aspace=0x{x} kstack=0x{x}\n", .{ t.user_ip, t.user_sp, t.aspace, t.kstack_top }) catch "");
|
// user-space writes, and its output used to shear concurrent log lines in
|
||||||
|
// half — the largest source of corrupted markers in the QEMU scenarios.
|
||||||
architecture.jumpToUser(t.user_ip, t.user_sp); // noreturn
|
architecture.jumpToUser(t.user_ip, t.user_sp); // noreturn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
|||||||
driverRestartTest(boot_information);
|
driverRestartTest(boot_information);
|
||||||
} else if (eql(case, "usb-report")) {
|
} else if (eql(case, "usb-report")) {
|
||||||
usbReportTest(boot_information);
|
usbReportTest(boot_information);
|
||||||
|
} else if (eql(case, "device-list")) {
|
||||||
|
deviceListTest(boot_information);
|
||||||
} else if (eql(case, "initial-ramdisk")) {
|
} else if (eql(case, "initial-ramdisk")) {
|
||||||
initialRamdiskTest(boot_information);
|
initialRamdiskTest(boot_information);
|
||||||
} else if (eql(case, "vfs")) {
|
} else if (eql(case, "vfs")) {
|
||||||
@@ -1730,6 +1732,39 @@ fn usbReportTest(boot_information: *const BootInformation) void {
|
|||||||
result();
|
result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// M18.3: the application surface. device-list enumerates the manager's tree
|
||||||
|
/// over IPC, subscribes with its endpoint as a capability, and prints every
|
||||||
|
/// published event; the manager's delayed test-kill of the reporter produces a
|
||||||
|
/// removed/added storm the subscriber must observe. The harness's ordered
|
||||||
|
/// expect regex is the assertion.
|
||||||
|
fn deviceListTest(boot_information: *const BootInformation) void {
|
||||||
|
log("DANOS-TEST-BEGIN: device-list\n", .{});
|
||||||
|
if (boot_information.initial_ramdisk_len == 0) {
|
||||||
|
check("bootloader handed over an initial_ramdisk", false);
|
||||||
|
result();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||||
|
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||||
|
check("initial_ramdisk image is valid", false);
|
||||||
|
result();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
process.setInitialRamdisk(image);
|
||||||
|
var manager: u32 = 0;
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < rd.count) : (i += 1) {
|
||||||
|
const item = rd.entry(i) orelse continue;
|
||||||
|
if (!eql(item.name, "device-manager")) continue;
|
||||||
|
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
check("device-manager spawned in test-usb-restart mode", manager != 0);
|
||||||
|
check("device-list spawned", spawnNamed(rd, "device-list"));
|
||||||
|
result();
|
||||||
|
}
|
||||||
|
|
||||||
/// The whole user-side surface at once: spawn process-test's supervisor role,
|
/// The whole user-side surface at once: spawn process-test's supervisor role,
|
||||||
/// which — entirely from ring 3 — creates an exit endpoint, spawns its two
|
/// which — entirely from ring 3 — creates an exit endpoint, spawns its two
|
||||||
/// children supervised, sees them in process_enumerate, kills them (one blocked,
|
/// children supervised, sees them in process_enumerate, kills them (one blocked,
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//! device-list — the `ps` analog for the device tree (docs/device-manager.md
|
||||||
|
//! M18.3): asks the device manager for the tree over IPC, prints it, then
|
||||||
|
//! subscribes and prints every published add/remove event. The manager is the
|
||||||
|
//! one answer to "what devices exist" for user space; nothing here touches a
|
||||||
|
//! device_* system call.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const runtime = @import("runtime");
|
||||||
|
const protocol = runtime.device_manager_protocol;
|
||||||
|
|
||||||
|
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||||
|
var line: [96]u8 = undefined;
|
||||||
|
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
var manager: ?runtime.ipc.Handle = null;
|
||||||
|
var tries: u32 = 0;
|
||||||
|
while (manager == null and tries < 200) : (tries += 1) {
|
||||||
|
manager = runtime.ipc.lookup(.device_manager);
|
||||||
|
if (manager == null) runtime.system.sleep(20);
|
||||||
|
}
|
||||||
|
const h = manager orelse {
|
||||||
|
_ = runtime.system.write("device-list: no device manager\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The snapshot — polled briefly, because at boot the bus drivers may still
|
||||||
|
// be scanning: an empty first answer usually just means "too early".
|
||||||
|
var reply: [protocol.message_maximum]u8 = undefined;
|
||||||
|
var count: u32 = 0;
|
||||||
|
var length: usize = 0;
|
||||||
|
tries = 0;
|
||||||
|
while (tries < 20) : (tries += 1) {
|
||||||
|
const request = protocol.Enumerate{};
|
||||||
|
length = runtime.ipc.call(h, std.mem.asBytes(&request), &reply) catch 0;
|
||||||
|
if (length >= @sizeOf(protocol.EnumerateReply)) {
|
||||||
|
count = std.mem.bytesToValue(protocol.EnumerateReply, reply[0..@sizeOf(protocol.EnumerateReply)]).count;
|
||||||
|
if (count != 0) break;
|
||||||
|
}
|
||||||
|
runtime.system.sleep(100);
|
||||||
|
}
|
||||||
|
writeLine("device-list: {d} devices\n", .{count});
|
||||||
|
var offset: usize = @sizeOf(protocol.EnumerateReply);
|
||||||
|
var index: u32 = 0;
|
||||||
|
while (index < count and offset + @sizeOf(protocol.ChildEntry) <= length) : (index += 1) {
|
||||||
|
const entry = std.mem.bytesToValue(protocol.ChildEntry, reply[offset..][0..@sizeOf(protocol.ChildEntry)]);
|
||||||
|
writeLine("device-list: device {d} port {d} identity {d}\n", .{ entry.parent, entry.bus_address, entry.identity });
|
||||||
|
offset += @sizeOf(protocol.ChildEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The subscription: our endpoint rides as the call's capability; events
|
||||||
|
// arrive as buffered messages carrying the same structs the bus sends.
|
||||||
|
const endpoint = runtime.ipc.createIpcEndpoint() orelse {
|
||||||
|
_ = runtime.system.write("device-list: no endpoint\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const subscribe = protocol.Subscribe{};
|
||||||
|
_ = runtime.ipc.callCap(h, std.mem.asBytes(&subscribe), &reply, endpoint) catch {
|
||||||
|
_ = runtime.system.write("device-list: subscribe failed\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
_ = runtime.system.write("device-list: subscribed\n");
|
||||||
|
|
||||||
|
var receive: [protocol.message_maximum]u8 = undefined;
|
||||||
|
while (true) {
|
||||||
|
const got = runtime.ipc.replyWait(endpoint, &.{}, &receive, null);
|
||||||
|
if (!got.isMessage() or got.len < 1) continue;
|
||||||
|
switch (receive[0]) {
|
||||||
|
@intFromEnum(protocol.Operation.child_added) => {
|
||||||
|
if (got.len < protocol.child_added_size) continue;
|
||||||
|
const event = std.mem.bytesToValue(protocol.ChildAdded, receive[0..protocol.child_added_size]);
|
||||||
|
writeLine("device-list: added (device {d} port {d})\n", .{ event.parent, event.bus_address });
|
||||||
|
},
|
||||||
|
@intFromEnum(protocol.Operation.child_removed) => {
|
||||||
|
if (got.len < protocol.child_removed_size) continue;
|
||||||
|
const event = std.mem.bytesToValue(protocol.ChildRemoved, receive[0..protocol.child_removed_size]);
|
||||||
|
writeLine("device-list: removed (device {d} port {d})\n", .{ event.parent, event.bus_address });
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const panic = runtime.panic;
|
||||||
|
comptime {
|
||||||
|
_ = &runtime.start._start; // pull the runtime entry shim into the image
|
||||||
|
}
|
||||||
@@ -19,11 +19,13 @@ pub const Role = enum(u8) {
|
|||||||
device = 2,
|
device = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The message kinds. `enumerate`/`subscribe` land in M18.3.
|
/// The message kinds.
|
||||||
pub const Operation = enum(u8) {
|
pub const Operation = enum(u8) {
|
||||||
hello = 1,
|
hello = 1,
|
||||||
child_added = 2,
|
child_added = 2,
|
||||||
child_removed = 3,
|
child_removed = 3,
|
||||||
|
enumerate = 4,
|
||||||
|
subscribe = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `Hello.device_id` for a driver that serves no enumerated device (a test
|
/// `Hello.device_id` for a driver that serves no enumerated device (a test
|
||||||
@@ -97,5 +99,41 @@ pub const ReportReply = extern struct {
|
|||||||
reserved: u32 = 0,
|
reserved: u32 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An application asking for the tree (M18.3): the reply is an EnumerateReply
|
||||||
|
/// header followed by `count` ChildEntry records.
|
||||||
|
pub const Enumerate = extern struct {
|
||||||
|
operation: u8 = @intFromEnum(Operation.enumerate),
|
||||||
|
reserved0: u8 = 0,
|
||||||
|
reserved1: u16 = 0,
|
||||||
|
reserved2: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const EnumerateReply = extern struct {
|
||||||
|
status: i32,
|
||||||
|
/// ChildEntry records following this header.
|
||||||
|
count: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ChildEntry = extern struct {
|
||||||
|
parent: u64,
|
||||||
|
bus_address: u64,
|
||||||
|
identity: u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An application subscribing to published add/remove events (the input-service
|
||||||
|
/// pattern): the subscriber's endpoint rides as the call's **capability**, and
|
||||||
|
/// events arrive on it as buffered messages whose payload is the same
|
||||||
|
/// ChildAdded / ChildRemoved struct the bus drivers send — one encoding, both
|
||||||
|
/// directions.
|
||||||
|
pub const Subscribe = extern struct {
|
||||||
|
operation: u8 = @intFromEnum(Operation.subscribe),
|
||||||
|
reserved0: u8 = 0,
|
||||||
|
reserved1: u16 = 0,
|
||||||
|
reserved2: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
/// Upper bound on any message in this protocol — sizes the endpoint buffers.
|
/// Upper bound on any message in this protocol — sizes the endpoint buffers.
|
||||||
pub const message_maximum = 64;
|
/// Capped by the kernel's IPC MESSAGE_MAXIMUM (256): an EnumerateReply carries
|
||||||
|
/// up to ten ChildEntry records per call, plenty for the mirror's current
|
||||||
|
/// bounds; paging joins the protocol if a tree ever outgrows one message.
|
||||||
|
pub const message_maximum = 256;
|
||||||
|
|||||||
@@ -108,6 +108,25 @@ var manager_endpoint: runtime.ipc.Handle = 0;
|
|||||||
var test_restart_mode = false;
|
var test_restart_mode = false;
|
||||||
var test_usb_restart_mode = false;
|
var test_usb_restart_mode = false;
|
||||||
var test_usb_killed = false;
|
var test_usb_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
|
/// 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
|
/// tree"): the children, keyed by (parent, bus address), each remembering which
|
||||||
@@ -144,11 +163,14 @@ fn addChild(parent: u64, bus_address: u64, identity: u64, reporter: u32) bool {
|
|||||||
/// Prune every child a dead driver instance reported: the children describe
|
/// Prune every child a dead driver instance reported: the children describe
|
||||||
/// protocol state (slots, rings) that died with the process — keeping the nodes
|
/// protocol state (slots, rings) that died with the process — keeping the nodes
|
||||||
/// would be keeping a lie. The restarted instance rediscovers and re-reports.
|
/// 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 {
|
fn pruneChildrenOf(reporter: u32) void {
|
||||||
for (&children) |*child| {
|
for (&children) |*child| {
|
||||||
if (child.used and child.reporter == reporter) {
|
if (child.used and child.reporter == reporter) {
|
||||||
writeLine("device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
|
writeLine("device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
|
||||||
child.used = false;
|
child.used = false;
|
||||||
|
const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address };
|
||||||
|
publishEvent(std.mem.asBytes(&event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +281,11 @@ fn onDriverExit(driver: *Driver) void {
|
|||||||
/// sweep serves every armed deadline.
|
/// sweep serves every armed deadline.
|
||||||
fn sweepDeadlines() void {
|
fn sweepDeadlines() void {
|
||||||
const now = system.clock();
|
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| {
|
for (&drivers) |*driver| {
|
||||||
if (!driver.used) continue;
|
if (!driver.used) continue;
|
||||||
switch (driver.state) {
|
switch (driver.state) {
|
||||||
@@ -318,11 +345,13 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onMessage(message: []const u8, reply: []u8, sender: u32) usize {
|
fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
|
||||||
if (message.len < 1) return 0;
|
if (message.len < 1) return 0;
|
||||||
switch (message[0]) {
|
switch (message[0]) {
|
||||||
@intFromEnum(protocol.Operation.child_added) => return onChildAdded(message, reply, sender),
|
@intFromEnum(protocol.Operation.child_added) => return onChildAdded(message, reply, sender),
|
||||||
@intFromEnum(protocol.Operation.child_removed) => return onChildRemoved(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) => {},
|
@intFromEnum(protocol.Operation.hello) => {},
|
||||||
else => return 0,
|
else => return 0,
|
||||||
}
|
}
|
||||||
@@ -355,15 +384,19 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
|
|||||||
if (driverByProcess(sender)) |driver| {
|
if (driverByProcess(sender)) |driver| {
|
||||||
if (!addChild(report.parent, report.bus_address, report.identity, sender)) status = -1;
|
if (!addChild(report.parent, report.bus_address, report.identity, 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() });
|
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]);
|
||||||
} else {
|
} else {
|
||||||
status = -1;
|
status = -1;
|
||||||
}
|
}
|
||||||
const report_reply = protocol.ReportReply{ .status = status };
|
const report_reply = protocol.ReportReply{ .status = status };
|
||||||
@memcpy(reply[0..@sizeOf(protocol.ReportReply)], std.mem.asBytes(&report_reply));
|
@memcpy(reply[0..@sizeOf(protocol.ReportReply)], std.mem.asBytes(&report_reply));
|
||||||
if (test_usb_restart_mode and !test_usb_killed and childCountOf(sender) >= 2) {
|
if (test_usb_restart_mode and !test_usb_killed and childCountOf(sender) >= 2) {
|
||||||
|
// Delayed, not immediate: the device-list scenario's subscriber needs a
|
||||||
|
// window to enumerate and subscribe before the events start.
|
||||||
test_usb_killed = true;
|
test_usb_killed = true;
|
||||||
writeLine("device-manager: test mode: killing the reporter\n", .{});
|
test_kill_pid = sender;
|
||||||
_ = system.kill(sender);
|
test_kill_due_ns = system.clock() + 2_000_000_000;
|
||||||
|
_ = system.timerOnce(manager_endpoint, 2100);
|
||||||
}
|
}
|
||||||
return @sizeOf(protocol.ReportReply);
|
return @sizeOf(protocol.ReportReply);
|
||||||
}
|
}
|
||||||
@@ -386,6 +419,40 @@ fn onChildRemoved(message: []const u8, reply: []u8, sender: u32) usize {
|
|||||||
return @sizeOf(protocol.ReportReply);
|
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 {
|
fn onNotification(badge: u64) void {
|
||||||
if (badge & runtime.ipc.notify_exit_bit != 0) {
|
if (badge & runtime.ipc.notify_exit_bit != 0) {
|
||||||
const dead: u32 = @intCast(badge & ~(runtime.ipc.notify_badge_bit | runtime.ipc.notify_exit_bit));
|
const dead: u32 = @intCast(badge & ~(runtime.ipc.notify_badge_bit | runtime.ipc.notify_exit_bit));
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ fn awaitChildExit(endpoint: runtime.ipc.Handle) u32 {
|
|||||||
/// The harness-run child of the signals test: echoes requests, logs the two
|
/// The harness-run child of the signals test: echoes requests, logs the two
|
||||||
/// signals it handles. Terminate makes run() return, and returning from main is
|
/// signals it handles. Terminate makes run() return, and returning from main is
|
||||||
/// the clean exit the parent reads as ExitReason.exited.
|
/// the clean exit the parent reads as ExitReason.exited.
|
||||||
fn echo(message: []const u8, reply: []u8, sender: u32) usize {
|
fn echo(message: []const u8, reply: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
|
||||||
_ = sender;
|
_ = sender;
|
||||||
|
_ = capability;
|
||||||
const n = @min(message.len, reply.len);
|
const n = @min(message.len, reply.len);
|
||||||
@memcpy(reply[0..n], message[0..n]);
|
@memcpy(reply[0..n], message[0..n]);
|
||||||
return n;
|
return n;
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ fn releaseClientHandles(client: u32) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle one request from `sender`; write the reply into `out`, return its length.
|
/// Handle one request from `sender`; write the reply into `out`, return its length.
|
||||||
fn handle(message: []const u8, out: []u8, sender: u32) usize {
|
fn handle(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
|
||||||
|
_ = capability;
|
||||||
if (message.len < protocol.request_size) return fail(out);
|
if (message.len < protocol.request_size) return fail(out);
|
||||||
const request = std.mem.bytesToValue(protocol.Request, message[0..protocol.request_size]);
|
const request = std.mem.bytesToValue(protocol.Request, message[0..protocol.request_size]);
|
||||||
const payload = message[protocol.request_size..];
|
const payload = message[protocol.request_size..];
|
||||||
|
|||||||
@@ -274,6 +274,21 @@ CASES = [
|
|||||||
r"device-manager: restarting usb-xhci-bus[\s\S]*"
|
r"device-manager: restarting usb-xhci-bus[\s\S]*"
|
||||||
r"device-manager: child added",
|
r"device-manager: child added",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
|
# M18.3: the application surface — device-list enumerates the tree over IPC,
|
||||||
|
# subscribes (endpoint as capability), and observes the removed/added events
|
||||||
|
# the reporter's test-kill produces (docs/device-manager.md).
|
||||||
|
{"name": "device-list",
|
||||||
|
"smp": 4,
|
||||||
|
"timeout": 90,
|
||||||
|
"qemu_extra": ["-device", "qemu-xhci,id=xhci",
|
||||||
|
"-device", "usb-kbd,bus=xhci.0",
|
||||||
|
"-device", "usb-mouse,bus=xhci.0"],
|
||||||
|
"expect": r"device-list: 2 devices[\s\S]*"
|
||||||
|
r"device-list: subscribed[\s\S]*"
|
||||||
|
r"device-manager: test mode: killing the reporter[\s\S]*"
|
||||||
|
r"device-list: removed \(device[\s\S]*"
|
||||||
|
r"device-list: added \(device",
|
||||||
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# M18.1: the device manager's hello + restart policy — xHCI hellos clean and
|
# M18.1: the device manager's hello + restart policy — xHCI hellos clean and
|
||||||
# stays; crash-test faults, is restarted with backoff (re-claiming its device
|
# stays; crash-test faults, is restarted with backoff (re-claiming its device
|
||||||
# each time), and hits the crash-loop cap (docs/device-manager.md).
|
# each time), and hits the crash-loop cap (docs/device-manager.md).
|
||||||
|
|||||||
Reference in New Issue
Block a user