The module-to-domain table in build-support duplicated what each
domain's build.zig already states with its addModule exports. userBinary
now resolves each named import by searching the packages the binary
declared in its own build.zig.zon (b.available_deps), which also makes
the zon the literal include path: an import can only be satisfied by a
domain the binary claims, and naming a module whose domain is missing
fails the build graph with the domain to declare. build-support is down
to the recipe alone. All build variants green; manifest unchanged.
display-client and input-client (files and module names), so a service,
its wire contract, and its client never share a name: `display` the
service, `display-protocol` the contract, `display-client` a program's
view of it. The nine consumers' imports and their packages' declared
lists follow (regenerated from the source scan); build-support's
module_homes table carries the new names.
Every binary's build.zig now names precisely the modules its source
imports (derived by scanning each artifact's sources, transitively
through same-directory files), and its zon carries only the domains
those come from — kernel stays implicit (the root shim + link script
live there). build-support's userBinary resolves each name through one
module-to-domain table (module_homes); Domains/domains()/defaultImports
and the raw recipe entry point are deleted. An undeclared @import is a
compile error (verified: injecting @import("xkeyboard-config") into
logger fails with 'no module named ... available within module
program'), and e.g. xkeyboard-config now appears in exactly two
manifests — the two keyboard drivers. Availability never bloated the
emitted binaries (Zig compiles only what a program imports); this makes
the declared interfaces honest. Production and -Dtest-case manifests
byte-identical; all build variants and standalone package builds
green.
init, fat, display, display-demo, device-manager, input, logger, and
the two discovery fillers (acpi, fdt — each exporting an artifact named
"discovery"; the root -Ddiscovery picks which ships) convert to binary
packages on the pci-bus template. init's serial heartbeat flag rides
the dependency options (the root forwards its -Dserial). fat's and
display's unit tests move into their packages and the root aggregate
delegates to them. Boot-image file list unchanged.
Every user binary and the two device-logic library modules (pci, usb) now
`@import` the concern modules directly instead of aliasing through `runtime`:
runtime.ipc/process/time/service/input/block/display -> @import("<module>")
runtime.device / runtime.device_manager -> @import("driver")
runtime.fs -> @import("file-system")
runtime.Thread -> @import("thread").Thread
runtime.system.{write,writeRecord,klog*} -> logging.*
runtime.system.{sleep,timerOnce,wallClock,clock} -> time.*
runtime.system.{spawn*,kill,exit,yield,processes,...}-> process.*
runtime.system.{mmap,munmap,PROT_*} -> memory.*
runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.*
Each consumer keeps its own alias name (e.g. `const device = @import("driver")`),
so call sites are unchanged and there are no collisions with local `driver`
variables. build.zig now injects the concern modules into every user binary via
`default_imports`; pci/usb module import lists were updated to match.
The `runtime` and `system` shims remain for one more step (root.zig still uses
runtime); they are deleted in C5. Nothing but root.zig imports `runtime` now.
Verified: zig build, zig build test, and 17 QEMU cases (smoke, device-manager,
logger, fat-mount, fat-mutations, usb-storage, usb-hid, display-native,
virtio-gpu, input, thread-spawn, thread-mutex, process-kill, shared-memory,
driver-restart, acpi-ps2, pci-scan).
input-protocol -> library/protocol/input/input-protocol.zig. Its five
consumers (usb-hid keyboard/mouse, ps2 keyboard/mouse, the input service) now
import the module directly and alias it input_protocol, replacing the
runtime.input_protocol re-export (deleted) and the inconsistent local `protocol`
aliases. runtime.input (the client) still imports the module by name.
zig build + test green; input, acpi-ps2, usb-hid pass.
library/runtime/log.zig wires std.log to the tagged ring: the root shim
installs std_options for every binary (programs may override), logFn
formats one line per record and emits it with its level via
debug_write — the payload no longer carries the process's name; the
kernel stamps identity structurally and the serial renderer prints the
'<binary path>: ' prefix, so the transcript keeps its shape.
Migrate every writeLine/logLine/log helper family (init, vfs, fat,
device-manager, acpi, pci-bus, ps2-bus x3, usb-hid x2, usb-storage,
usb-xhci-bus, virtio-gpu — 104 call sites) to std.log.info, dropping
the hand-written prefixes. A leveled record is a complete line by
contract (raw emissions may still build lines from pieces). Test
fixtures and the display/input services keep raw writes for now — their
ring records are attributed by the kernel regardless.
Harness regexes follow the renamed prefixes (usb-hid-keyboard,
discovery) and path-named restart lines.
Turn acpi-ids.zig's flat name table into a HardwareId enum modeled on
ps2-library's Port: one entry() switch holds the registry (variant ->
_HID string + human-readable name), with hid(), description(), and
fromHid() methods. The free description(hid) lookup the kernel's
device-tree dump uses survives, implemented over the enum, and a new
test round-trips every variant through fromHid.
Callers now name the device instead of quoting its id:
- ps2-library's DeviceType.hid() and ps2-bus's descriptor lookups use
HardwareId.ps2_keyboard / .ps2_mouse.
- device-manager's driverFor parses the HID once with fromHid and
switches on named values.
- acpi.zig's isPciRootNode carried the same ids twice, as strings and
as packed-EISA integers (0x030AD041/0x080AD041); both branches now
decode to the string form and answer through one isPciRootHid helper
using .pci_bus / .pci_express_root_bridge.
- build.zig threads the acpi-ids module (previously kernel-only) into
every user binary, like xkeyboard-config.
Add an AttachStatus enum to ps2-library.zig for AttachReply.status,
distinguishing the three failure causes handleAttach previously
collapsed into a bare -1: invalid_request (message too short),
missing_endpoint (no capability passed), and no_such_device (no port
identified the requested device type). The keyboard and mouse drivers
check against AttachStatus.ok rather than a literal 0.
Replace the mouse driver's synthetic stream with the real path, the
way the keyboard was wired:
- The auxiliary port's IRQ12 is enumerated on the mouse's own ACPI
node (PNP0F13), and the kernel only lets a device's claimer bind or
ack its IRQs — so ps2-bus now claims that node alongside the
controller whenever port 2 carries a device, binds IRQ12 to its one
endpoint, and re-arms whichever line the notification's badge names.
The forwarding loop already routed auxiliary bytes by status bit 5.
- mouse-packet.zig (new, pure, host-tested): three-byte stream-mode
packet assembly — bit-3 sync with resynchronization, ACK/BAT bytes
dropped at packet start, nine-bit two's-complement movement,
overflow packets discarded, and PS/2 positive-Y-up converted to the
screen convention (positive down).
- mouse.zig mirrors the keyboard driver: no hardware claim, attaches
to the bus as its mouse, and publishes button_down/button_up per
changed button plus motion events with the pressed-button mask.
Verified end to end in QEMU via monitor mouse_move/mouse_button:
motion round-trips in screen coordinates, buttons transition with the
right mask, and keyboard events keep flowing alongside. The follow-up
is the IntelliMouse magic-knock for a scroll wheel (four-byte packets)
and scroll events.
Replace the keyboard driver's synthetic stream with the real path:
- ps2-bus binds IRQ1 (interrupt bits set only after the bind), drains
port 0x60 on each interrupt, and forwards every byte to the attached
child driver over async ipc_send, routed by the status register's
auxiliary-output bit. Children attach via the new well-known ps2_bus
service, handing over their endpoint as a capability.
- scancode.zig (new, pure, host-tested): scancode set 2 -> USB HID
usage decoding (F0/E0/E1 prefix state machine) plus keyboard state —
pressed-key bitmap, typematic-repeat classification, modifier and
caps-lock tracking.
- keyboard.zig decodes the forwarded stream and publishes real
key_down/key_press/key_up events, filling key_press characters via
xkeyboard-config (layout from argv[2], default us) and synthesizing
ASCII control characters for Enter/Tab/Backspace/Escape.
- protocol.zig names the full HID usage set in Keycode; build.zig
threads the xkeyboard-config module into user binaries.
Verified end to end in QEMU via monitor sendkey: shift, caps lock,
and control-character synthesis all decode correctly. The mouse
driver still publishes its synthetic stream; attaching it to the
bus the same way is the follow-up.
Extend the input service beyond the keyboard so mouse and joystick/gamepad
drivers can broadcast too, with per-device publish and subscribe methods.
- protocol: KeyEvent joins MouseEvent (motion/buttons/scroll) and
JoystickEvent (axes/buttons), all carried in a common InputEvent envelope
tagged with a DeviceKind. A subscribe request carries a device_mask, so a
subscriber names the classes it wants and the service routes each event only
to interested subscribers (a mouse-only listener never wakes for keystrokes).
- runtime: per-device publish methods (publishKeyboardEvent/publishMouseEvent/
publishJoystickEvent) and subscribe helpers (subscribeKeyboard/Mouse/Joystick,
each typed, plus subscribe(mask)/subscribeAll returning the tagged envelope).
- service: subscriber table gains a device_mask; broadcast routes by the
event's device class.
- mouse driver now publishes (synthetic) mouse events like the keyboard driver;
input-source cycles all three classes; input-test subscribes to all and only
emits its "ok" marker once it has received one of each class — so the passing
test proves per-device routing, not just delivery. Real HID decoding stays a
follow-up.
No kernel changes: ipc_send is generic and the 36-byte InputEvent fits its
64-byte payload. Full QEMU suite 48/48; serial log confirms keyboard, mouse,
and joystick all reach one subscription.
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).