library: five protocols speak the envelope

The folded header stops being a rule in a document and becomes the layout
on the wire. Verbs number from sixteen, leaving describe, enumerate,
subscribe and unsubscribe reserved and answered the same way by every
provider — none of them writes a line to do it. What each protocol used to
carry in a field of its own now travels in the header: a vfs node and a
display layer are the packet's target, and a reply opens with a status the
envelope stamps rather than one each protocol spelled for itself.

Display gains the most. One forty-byte request had served eleven verbs, so
attach_scanout smuggled stride through x, refresh through y and format
through colour, and every coordinate crossed as a bitcast. Per-operation
structs end all three: the fields have their own names and their own signs,
and the tile payload grows to 224 bytes because the prefix shrank. Scanout
loses a message maximum of 64 it had no business declaring — it answers
calls, and the floor for a call is 256 — and virtio-gpu stops hard-coding
that number at its harness.

Two changes are semantic rather than notational. A directory now ends at an
entry with no name, because the fixed part of a reply always travels and a
zero-length reply no longer exists to mean anything. And input joins the
service harness, the last loop in the tree that answered no ping and heard
no terminate; its subscriber table, its pruning and its fan-out are the
same code, and a shutdown now asks it to stop instead of killing it.

A new conformance case reads the registry's own listing and asks every
protocol it finds for its name, its version and its verb count, then offers
a verb nobody defines and requires -ENOSYS — the envelope's promise,
checked against providers rather than against itself. What it cannot reach
in that boot it names on the serial line instead of passing quietly.

Suite 110/110.
This commit is contained in:
Daniel Samson
2026-08-01 06:15:25 +01:00
parent b004b9c3eb
commit d2dfbcabf8
40 changed files with 1882 additions and 1008 deletions
+20 -7
View File
@@ -22,12 +22,22 @@ event:
- `JoystickEvent` — `axis` moves (a signed value on a `control` index) and
`button_down`/`button_up`.
All three travel in one **`InputEvent` envelope** tagged with a `DeviceKind`, so the
fan-out is a single code path and a subscriber can take a mix of classes on one stream.
Decode an envelope with `asKeyboard()` / `asMouse()` / `asJoystick()` (each returns null
unless the tag matches). A subscriber names the classes it wants with a **`device_mask`**,
and the service routes each event only to subscribers whose mask includes its class — so a
mouse-only listener never wakes for keystrokes.
A source publishes any of the three as one **`InputEvent`** tagged with a `DeviceKind`, so
`publish` is a single verb; decode one with `asKeyboard()` / `asMouse()` / `asJoystick()`
(each returns null unless the tag matches). On the *delivery* wire the class is the
packet's own operation instead — the protocol declares one event per class
([protocol-namespace.md](../os-development/protocol-namespace.md)), so a pushed packet is
the 16-byte header plus the typed event and nothing carries a tag twice. The client
helpers re-tag what arrives back into an `InputEvent`, so a subscriber can still take a
mix of classes on one stream. A subscriber names the classes it wants with a
**`device_mask`**, and the service routes each event only to subscribers whose mask
includes its class — so a mouse-only listener never wakes for keystrokes.
**`subscribe` is not this protocol's verb.** Its shape — a synchronous call whose attached
capability is the subscriber's own endpoint — is what the envelope's *reserved* subscribe
means at every provider in the system, so the input protocol adopts it rather than
defining a second spelling of the same thing. The interest mask rides as the packet's
tail. `publish` is the one verb the protocol defines for itself.
## Why this needed a new kernel primitive
@@ -103,7 +113,10 @@ This is the async counterpart of `ipc_call`, and the input service is its first
event to every subscriber whose mask includes the event's device class. On `subscribe` it
stores the passed capability and mask and, as housekeeping, prunes any slot whose owning
process has exited (checked against `process_enumerate`) — not for correctness (an async
send to an orphaned endpoint is harmless) but to reclaim the slot.
send to an orphaned endpoint is harmless) but to reclaim the slot. The service runs on the
shared harness ([service.zig](../../library/kernel/service.zig)) like every other, so it
answers the universal ping and exits on `terminate`; it was the last hand-rolled receive
loop in the tree, and the last service a shutdown had to kill rather than ask.
Publisher and subscriber must be **separate processes**: a single thread that both
published and serviced its own subscription would deadlock (its `publish` call blocks until