M13: IPC capability passing

ipc_call and ipc_reply_wait grow a `send_cap` argument (r9) and a `received_cap`
return (r8): an endpoint travels alongside a message, installed into the receiver's
handle table. The transfer is a share, not a move — the endpoint's refcount is bumped
and the sender keeps its handle. If the receiver's table is full the call fails
-ENOSPC and the message is NOT delivered (a half-delivered capability is worse than a
failed send); a bad handle fails -EBADF. Both directions carry a cap: a client's call
hands one to the server (seen in the server's replyWait), and the server's reply hands
one back (seen in the client's call return).

This is the "open" primitive the driver model was blocked on: a bus driver mints a
per-device endpoint and hands it to a class driver, giving it a private channel to one
device without the 8-slot global name registry.

Kernel: shareCapability in ipc-synchronous.zig at both copy points; new
setSystemCallResult3 (r8, saved/restored by the syscall stub); Task gains
ipc_send_cap / ipc_received_cap. Runtime: callCap + Reply, replyWait gains send_cap
and Received.cap; plain call/replyWait delegate with no_cap. New abi.no_cap.

New ipc-cap test (two kernel tasks exercise both directions, each verifying the
endpoint it received is the same object shared, refcount bumped to 2). No class driver
consumes callCap yet — it lands with the first one. Suite 37/37 plus host tests.
This commit is contained in:
Daniel Samson
2026-07-10 19:23:19 +01:00
parent 8eb4210251
commit a581712b09
12 changed files with 243 additions and 53 deletions
+11 -1
View File
@@ -132,6 +132,12 @@ If a class driver needs `mmio`, it has become an HCD and should be one.
- **M11** — `irq_bind` / `irq_ack`. IRQ delivered as an IPC notification; mask before
EOI; `irq_ack` is the unmask.
- **M12** — `parent` in `DeviceDesc`, `device_register` with resource containment.
- **M13** — capability passing. `ipc_call` / `ipc_reply_wait` grew a `send_cap` argument
and a `received_cap` return (r8): an endpoint travels with a message, installed into
the receiver's handle table (shared, refcount-bumped — a copy, not a move). A full
table fails `-ENOSPC` and does not half-deliver. This is the "open" primitive — a bus
driver mints a per-device endpoint and hands it to a class driver. The runtime exposes
`callCap` and `replyWait(..., send_cap)`; no class driver consumes it yet.
- **`system_spawn`** — a user-space supervisor starts a driver: `system_spawn(name)`
loads a binary bundled in the initial-ramdisk as a fresh ring-3 process. This is what
turned the device manager from "log the match" into "run the driver": the kernel now
@@ -146,7 +152,11 @@ HCDs and class drivers do not work yet. Here is exactly why, and exactly what wo
# Proposed ABI
## M13 — capability passing, for class drivers
## M13 — capability passing, for class drivers ✅ done
*Implemented as described below (see "What exists today"). The signatures landed
verbatim: `send_cap` in r9, `received_cap` returned in r8, `-ENOSPC` on a full receiver
table with no delivery. The rest of this section is the original design note.*
**The blocker.** A class driver has to reach *its* device. Today the only way to find
an endpoint is the name registry: `ipc_register(service_id, h)` / `ipc_lookup(id)`,