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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user