Add input module: broadcast keyboard events over IPC
Programs can now subscribe to keyboard events (key_down/key_up/key_press) and drivers can broadcast them, through a new user-space input service. The delivery model is forced by danos IPC: a synchronous rendezvous holds one pending reply, so a server cannot park N subscribers blocked in a "wait for next event" call — delivery must be push. But a synchronous push has no timeout and the kernel never wakes a sender parked on a dead peer's endpoint, so one dying subscriber would hang all input. So this lands the roadmap's planned asynchronous buffered send and builds the service on it: - ipc_send (syscall 26): non-blocking post to an endpoint's bounded payload ring, delivered through reply_wait as a buffered message (notify_message_bit). A full ring drops the oldest. It can never hang on a dead/slow peer. - input-protocol + runtime.input helpers (subscribe/next, connectSource/ publish) — the first real consumer of M13 capability passing: a subscriber hands the service its own endpoint as a capability. - input service (fan-out via ipc_send, dead-subscriber pruning), a synthetic input-source, and input-test; the ps2-bus keyboard driver publishes to it. Real IRQ1 scancode decoding (which must live in the bus, the PNP0303 owner) is a documented follow-up; the source is synthetic for now. - build/init wiring, an `input` QEMU case, and docs/input.md. Full QEMU suite 48/48, including the new input case and every IPC/endpoint regression (ipc, ipc-call, ipc-cap, vfs, hpet, bus, irqfree).
This commit is contained in:
@@ -136,6 +136,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
initialRamdiskTest(boot_information);
|
||||
} else if (eql(case, "vfs")) {
|
||||
vfsTest(boot_information);
|
||||
} else if (eql(case, "input")) {
|
||||
inputTest(boot_information);
|
||||
} else if (eql(case, "hpet")) {
|
||||
hpetTest(boot_information);
|
||||
} else if (eql(case, "iopass")) {
|
||||
@@ -1585,6 +1587,50 @@ fn vfsTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// The full input path: spawn the input service, a synthetic keyboard source, and a
|
||||
/// subscriber from the initial_ramdisk. The source publishes key events; the service
|
||||
/// broadcasts them (with the asynchronous ipc_send); the subscriber receives them and —
|
||||
/// only once it has — heartbeats "input-test: ok". Seeing that marker proves an event
|
||||
/// travelled source -> service -> subscriber over IPC, exercising the async buffered-send
|
||||
/// primitive and capability-passing subscription. The source and service stay silent
|
||||
/// after startup so the subscriber's line is the one left in the shared evidence buffer.
|
||||
fn inputTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: input\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.write_count = 0;
|
||||
process.write_from_user = false;
|
||||
_ = spawnNamed(rd, "input"); // the fan-out service
|
||||
_ = spawnNamed(rd, "input-source"); // a synthetic keyboard publishing events
|
||||
_ = spawnNamed(rd, "input-test"); // the subscriber whose "ok" line is the marker
|
||||
|
||||
// Wait for the subscriber's success heartbeat (it beats once per received event).
|
||||
const prefix = "input-test: ok";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 12000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
scheduler.setPriority(4);
|
||||
|
||||
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
||||
check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok);
|
||||
check("events kept flowing (service + async send stay up)", process.write_count >= 2);
|
||||
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||
result();
|
||||
}
|
||||
|
||||
/// Process arguments, end to end: spawn args-echo bare (its argv[0] is the
|
||||
/// initial-ramdisk name). Instance 1 sees argc == 1 and respawns itself through
|
||||
/// `system_spawn` with the extra arguments "alpha beta-42" — the syscall argument
|
||||
|
||||
Reference in New Issue
Block a user