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:
@@ -0,0 +1,87 @@
|
||||
//! The input wire protocol — the message format spoken between the user-space input
|
||||
//! service ([input.zig](input.zig)) and the two kinds of process that reach it: a
|
||||
//! **source** (a keyboard driver) that `publish`es events, and a **subscriber** (any
|
||||
//! program) that `subscribe`s and is then pushed each event.
|
||||
//!
|
||||
//! Two message shapes ride over one endpoint, tagged by `Operation`, exactly like the
|
||||
//! [VFS protocol](../vfs/protocol.zig):
|
||||
//!
|
||||
//! - **subscribe / publish**: a synchronous `ipc_call` carrying a `Request`. `subscribe`
|
||||
//! hands the service the subscriber's own endpoint as a capability (`send_cap`);
|
||||
//! `publish` carries a `KeyEvent`. The reply is a `Reply`.
|
||||
//! - **delivery**: the service pushes each `KeyEvent` to every subscriber with the
|
||||
//! asynchronous `ipc_send` — no reply owed, and a dead subscriber can never stall the
|
||||
//! broadcast (the reason the async primitive exists). The wire form is a bare
|
||||
//! `KeyEvent`, received in the subscriber's buffer with `Received.isMessage()` set.
|
||||
//!
|
||||
//! This is a danos-native contract; shared by the input service, the `runtime.input`
|
||||
//! client helpers, and every source/subscriber. Everything fits one IPC message.
|
||||
|
||||
/// What happened to a key. `key_down`/`key_up` are the physical make/break; `key_press`
|
||||
/// is the higher-level "a character was produced" event a source emits alongside a
|
||||
/// `key_down` for keys that map to a character (carrying it in `KeyEvent.character`).
|
||||
pub const EventKind = enum(u32) {
|
||||
key_down = 0, // a key was pressed (make)
|
||||
key_up = 1, // a key was released (break)
|
||||
key_press = 2, // a character-producing press; `character` is the Unicode scalar
|
||||
};
|
||||
|
||||
/// One keyboard event, as broadcast to subscribers. Fixed layout (`extern`) because it
|
||||
/// crosses the IPC boundary by memory copy. A hardware-independent `keycode` names the
|
||||
/// physical key; `character` is the Unicode scalar for `key_press` (else 0); `modifiers`
|
||||
/// is a bitmask of the shift/ctrl/alt state (`modifier_*`), 0 until a source tracks it.
|
||||
pub const KeyEvent = extern struct {
|
||||
kind: u32, // an EventKind
|
||||
keycode: u32, // a Keycode — the physical key, layout-independent
|
||||
character: u32, // Unicode scalar for key_press, else 0
|
||||
modifiers: u32, // OR of modifier_* bits
|
||||
};
|
||||
|
||||
/// Modifier bits for `KeyEvent.modifiers`.
|
||||
pub const modifier_shift: u32 = 1 << 0;
|
||||
pub const modifier_control: u32 = 1 << 1;
|
||||
pub const modifier_alt: u32 = 1 << 2;
|
||||
|
||||
/// A minimal danos-native keycode namespace — enough for the synthetic source and to
|
||||
/// show the shape. A real set (USB HID usage-style) fills in with the scancode decoder.
|
||||
pub const Keycode = enum(u32) {
|
||||
unknown = 0,
|
||||
a = 4, // deliberately USB-HID-usage-aligned so a real decoder can extend this
|
||||
b = 5,
|
||||
c = 6,
|
||||
d = 7,
|
||||
e = 8,
|
||||
enter = 40,
|
||||
_,
|
||||
};
|
||||
|
||||
/// Which side of a request this is.
|
||||
pub const Operation = enum(u32) {
|
||||
subscribe = 0, // register the caller's endpoint (passed as send_cap) to receive events
|
||||
publish = 1, // a source submits `event` to broadcast to every subscriber
|
||||
};
|
||||
|
||||
/// Request header. For `subscribe`, `event` is ignored and the caller's receive endpoint
|
||||
/// travels as the call's capability. For `publish`, `event` is the event to broadcast.
|
||||
pub const Request = extern struct {
|
||||
operation: u32, // an Operation
|
||||
_padding: u32 = 0,
|
||||
event: KeyEvent,
|
||||
};
|
||||
|
||||
/// Reply header. `status` is 0 on success or a negative errno.
|
||||
pub const Reply = extern struct {
|
||||
status: i32,
|
||||
_padding: u32 = 0,
|
||||
};
|
||||
|
||||
pub const request_size: usize = @sizeOf(Request);
|
||||
pub const reply_size: usize = @sizeOf(Reply);
|
||||
pub const event_size: usize = @sizeOf(KeyEvent);
|
||||
|
||||
comptime {
|
||||
// The delivery path posts a bare KeyEvent through ipc_send, so it must fit an
|
||||
// endpoint's async payload slot (abi has no dependency the other way, so the bound
|
||||
// lives here where the wire form is defined: POST_MAXIMUM is 64).
|
||||
if (event_size > 64) @compileError("KeyEvent must fit the ipc_send payload (POST_MAXIMUM)");
|
||||
}
|
||||
Reference in New Issue
Block a user