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:
Daniel Samson
2026-07-11 15:03:24 +01:00
parent 2a583d55a8
commit 65244e3103
19 changed files with 765 additions and 5 deletions
+11
View File
@@ -52,6 +52,7 @@ pub const SystemCall = enum(u64) {
clock = 23, // clock() -> nanoseconds since boot: a monotonic time source (for timeouts/delays)
process_enumerate = 24, // process_enumerate(buffer, maximum) -> total: snapshot the task table
process_kill = 25, // process_kill(id) -> 0/-errno: end a process this process spawned
ipc_send = 26, // ipc_send(handle, message_ptr, message_len) -> 0/-errno: post a payload to an endpoint's async queue without blocking
_,
};
@@ -83,6 +84,15 @@ pub const notify_badge_bit: u64 = 1 << 63;
/// notifications, which never set this bit). The microkernel's SIGCHLD.
pub const notify_exit_bit: u64 = 1 << 62;
/// Set (alongside `notify_badge_bit`) in the badge of a **buffered message** — a payload
/// posted to an endpoint's async queue by `ipc_send`, delivered through `ipc_reply_wait`
/// like a notification (no reply owed) but carrying bytes in the receive buffer, not just
/// a badge. This is what distinguishes a payload-bearing async message from a bare IRQ /
/// child-exit notification (which sets neither this nor `notify_exit_bit`). The low bits
/// carry the sender's task id. The async counterpart of the synchronous `ipc_call`, for
/// broadcasts where a rendezvous is the wrong shape (the input service is the first user).
pub const notify_message_bit: u64 = 1 << 61;
/// Capacity of `ProcessDescriptor.name` — matches the longest name `system_spawn`
/// accepts, so a process's recorded name (its argv[0]) is never truncated.
pub const maximum_process_name = 64;
@@ -113,6 +123,7 @@ pub const ProcessDescriptor = extern struct {
/// during bring-up. The VFS server registers under `vfs`; clients look it up.
pub const ServiceId = enum(u32) {
vfs = 1,
input = 2,
_,
};