The name list and its predicate existed so drivers could move to delegation one at a time with the suite green throughout. Every driver is delegated now, so the manager simply hands over whatever device a driver was assigned. Deleting it caught a real consequence: crash-test finally got delegated too, and it was still claiming its device — so it got AlreadyClaimed because it already held it, exited, and the restart drill had nothing to restart. Its own comment named what the case was really checking: "the respawn only reaches this line because the kernel released the previous instance's claim at death". That property still holds, by a different mechanism — the device reverts to the manager on death and is handed to the replacement, which is the same guarantee without the race it used to rely on. All four delegation paths verified: the xHCI controller, the PCI bridge, the PS/2 two-node singleton, and virtio-gpu's restart re-attach. Run 3 complete. Suite 118/118.
55 lines
2.6 KiB
Zig
55 lines
2.6 KiB
Zig
//! crash-test — a test fixture, not a driver: claims the device it is assigned,
|
|
//! hellos the device manager, announces itself, then faults on purpose. The
|
|
//! driver-restart scenario drives the manager's whole restart machinery with
|
|
//! it: fault → exit reason → backoff → respawn → the **same claim succeeding
|
|
//! again** (claim release on death, M17.1, through the manager's path) → the
|
|
//! crash-loop cap. Spawned bare (the initial-ramdisk sweep starts every bundled
|
|
//! binary), it exits silently so it cannot derange other tests.
|
|
|
|
const std = @import("std");
|
|
const channel = @import("channel");
|
|
const ipc = @import("ipc");
|
|
const process = @import("process");
|
|
const time = @import("time");
|
|
const device = @import("driver");
|
|
const logging = @import("logging");
|
|
const device_manager_protocol = @import("device-manager-protocol");
|
|
|
|
pub fn main(init: process.Init) void {
|
|
const argument = init.arguments.get(1) orelse return; // bare: stay silent
|
|
const assigned = std.fmt.parseInt(u64, argument, 10) catch return;
|
|
|
|
// The device arrived with the spawn — this fixture is delegated its hardware like
|
|
// any other driver, so it holds `assigned` before its first instruction and has
|
|
// nothing to claim (docs/os-development/device-authority.md).
|
|
//
|
|
// The property this scenario checks is unchanged, only its mechanism: a respawned
|
|
// instance still gets the device its predecessor held. It used to arrive because
|
|
// the kernel released the dead instance's claim and this one re-took it, racing
|
|
// anyone else who wanted it; now the device reverts to the manager on death and is
|
|
// handed to the replacement, which is the same guarantee without the race.
|
|
|
|
var manager: ?ipc.Handle = null;
|
|
var tries: u32 = 0;
|
|
while (manager == null and tries < 100) : (tries += 1) {
|
|
manager = channel.openEndpoint("device-manager");
|
|
if (manager == null) time.sleepMillis(20);
|
|
}
|
|
const h = manager orelse return;
|
|
// The assigned device is the packet's target, the manager's object addressing.
|
|
var packet: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
const framed = device_manager_protocol.Protocol.encodeRequest(
|
|
.hello,
|
|
assigned,
|
|
.{ .role = @intFromEnum(device_manager_protocol.Role.device) },
|
|
&.{},
|
|
&packet,
|
|
) orelse return;
|
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
_ = ipc.call(h, framed, &reply) catch return;
|
|
|
|
_ = logging.write("crash-test: faulting now\n");
|
|
const poison: *volatile u32 = @ptrFromInt(0xdead0000);
|
|
poison.* = 1; // the restart machinery's fuel: a real segmentation fault
|
|
}
|