Commit Graph
7 Commits
Author SHA1 Message Date
Daniel Samson f4eb88e7d2 build: a new compile-time ceiling declares itself or does not land
The convention that tunables live in system/parameters.zig with their
reasoning attached predates this and got 2% compliance — 5 of 235. A
convention with no teeth is how a bare `const maximum_devices = 64` reached
an AMD desktop and cost it USB and storage. This is the same rule with a
gate behind it.

tools/check-bounds.py finds every bound-shaped declaration — a `maximum_*`
const with a literal value, or a type with a literal array length — and
requires the five-field block above it: what it counts, who decides its
size, what it protects, what happens at the limit, and how anyone finds out.

The at-limit vocabulary is closed: refuse, degrade, truncate, grow. There is
deliberately no way to spell "silent", no way to spell "drop", and nothing
meaning "allow", so the behaviours that did the damage cannot be written
down. Truncation is legal only carrying a marker the reader can see, which
is why klog_maximum_message qualifies and a USB descriptor cut at 512 bytes
does not.

An array length that names a declared bound is not itself a bound; only
literal lengths are flagged, which pushes ceilings toward having names.

The 273 that predate the rule are allowlisted so this lands without a
tree-wide sweep in front of it, and that list may only shrink: declaring a
bound means deleting its line, and the check fails on a stale entry too.
Nothing may be added.

Wired into `zig build test` and available alone as `zig build bounds`. Not
in the default build — it reads the whole tree, and a red bounds check
should not stop you booting a kernel.

Five are now declared rather than allowlisted. Writing them out is its own
argument: maximum_devices reads "protects: nothing — this is a sizing guess
about someone else's computer", and maximum_tasks now carries the fact that
it has been raised twice, each time by something that outgrew it.

Verified the gate refuses an undeclared bound, a declared one using
forbidden vocabulary, and an allowlist entry that has since been declared.
Suite 115/115.
2026-08-08 11:27:27 +01:00
Daniel Samson a86559648e kernel: a refusal names its rule, and two bounds stop failing open
An AMD Ryzen booted to a working compositor with no USB and no storage,
and the log said only "register refused". A tree-wide audit of every
compile-time ceiling followed: 235 of them, 139 on quantities the machine
or a file decides rather than us, 5 documented anywhere, 171 silent when
reached. docs/fixed-bounds-audit.md has the inventory.

Errno attribution. The errno space was split between the kernel and the
envelope, free to drift; it is now one list in system/abi.zig, restated on
both sides, with a comptime check in library/device/driver where the two
halves are visible. device_register's six refusals and device_claim's three
are distinct codes, so a bus driver can say which rule stopped it, and
BadParent splits into NoSuchParent and NotYourParent. pci-bus reconciles
found against registered instead of counting refused functions as found.

Idempotency ordering. The child cap was checked before the identity match,
so a restarted bus was refused its own devices — the supervision restart the
system leans on ratcheted toward a degraded machine. A re-registration
consumes no slot and is now admitted first.

IOMMU fail-closed. confineDevice returned success for a device id past the
confinement table, leaving the device outside every domain while the caller
believed it confined — unreachable only while ids stop at 64, which both the
inventory move and a hardware-reported domain count would change. It refuses
now, and the coupling to the broker's device cap is a comptime assert rather
than a sentence in a comment.

PCI apertures. The bridge's MMIO apertures are derived from the holes in the
firmware memory map, and the derivation copied sub-4 GiB entries into a
fixed [64] array and skipped the rest. A skipped region is not merely lost:
the gap finder concludes it is free, so a real machine's 60-200 entry map
yields an aperture over live RAM, and containment then admits a child BAR
covering kernel memory. Rewritten to walk the map in place, with the hole
finder extracted as a pure function and driven by a synthetic 100-entry map
in a new test case. Both new tests were verified to fail on the old code.

parameters.zig gains the rationale it was missing and loses a stale sentence
pointing at the wrong file; vdso.md documents the errno space, including
EPEER, which had no written meaning anywhere.

docs/os-development/bounds.md is how a ceiling is declared from here.
docs/bounds-track-plan.md is the plan to remove the ones we invented.

Suite 114 -> 115.
2026-08-08 11:09:54 +01:00
Daniel Samson 3fb9d5936a USB driver stack: xHCI transfers, HID keyboard/mouse, mass storage
Flesh out the xHCI host-controller driver into a full transfer engine and build
the three USB class drivers on top, all verified end to end under QEMU.

- xHCI engine (usb-xhci-library.zig): controller reset, command/event rings with
  cycle-bit bookkeeping (gated on a No-Op-command proof), device slots, Address
  Device, control transfers, full chapter-9 enumeration, Configure Endpoint, and
  interrupt/bulk transfers. Each interface is device_registered with its
  (class,subclass,protocol) identity, unique per (port,interface).
- Bus<->class transfer protocol (usb-transfer-protocol.zig + runtime.usb): open /
  control / interrupt-subscribe (async report pump on a poll timer) / bulk-by-
  physical-address, so sector data never crosses the 256-byte IPC limit.
- USB HID keyboard + mouse (usb-hid/): decode boot-protocol reports and publish
  to the input service. A USB usage is already the input protocol's keycode.
- USB mass storage (usb-storage/): Bulk-Only Transport + transparent SCSI,
  serving a block device under the new .block service id (block-protocol).
- device-manager matches USB interfaces to class drivers (usbDriverForIdentity).
- usb-abi / usb-ids made importable modules; add HID and mass-storage class
  requests, packTriple, and a usb_device DeviceClass.
- Fix test/qemu_test.py on macOS: the QMP unix-socket path was built from the
  deep worktree path and exceeded the 104-byte sun_path limit, so QEMU exited
  before booting. It now lives under a short temp path.

Tests: usb-report, usb-hid, usb-storage pass under python3 test/qemu_test.py;
host units (usb-abi, usb-ids, hid-report, bulk-only-transport, scsi) green.
2026-07-13 14:11:00 +01:00
Daniel Samson 3cc1d38dd0 The device manager supervises: hello, backoff, and the crash-loop cap (M18.1)
The manager is now a harness service on the well-known .device_manager
endpoint. Every driver spawns supervised; drivers with an assignment must
hello (device-manager-protocol, versioned) within a deadline enforced by
a timer sweep. Exit reasons drive the restart decision: clean exits stay
down, faults restart with 300/600/1200ms backoff, and three fast deaths
mark a driver failed instead of respawning forever. usb-xhci-bus is the
first conforming driver; the crash-test fixture claims a device, hellos,
and faults on purpose — each respawn re-proving claim release on death
through the manager's own path. maximum_tasks grows 16 -> 32: the
initial-ramdisk sweep (15 binaries at once) was intermittently
overflowing the static pool.
2026-07-13 00:19:30 +01:00
Daniel Samson 2a583d55a8 Finishing PS/2 bus driver 2026-07-11 14:12:33 +01:00
Daniel Samson a5fe63c1dd Pass argv to processes on a SysV entry stack; grow the user stack to 32 KiB
Processes now start with C-compatible arguments: the kernel builds the
System V AMD64 entry block (argc, argv, empty envp, auxiliary vector)
at the top of the stack, argv[0] is the path or initial-ramdisk name
the process was spawned as, and system_spawn carries an optional
NUL-separated blob that becomes argv[1..]. The runtime parses the block
(runtime.argumentCount/argument) and its spawn wrappers pass arguments
through. The name is also recorded on the task, so a fault report says
which binary died, not just its id.

The user stack grows from one page to eight (32 KiB,
parameters.user_stack_pages), with the page below left unmapped as a
guard so an overflow faults into a clean process kill rather than
corrupting the image. Task.name_buffer is zero-initialised, not
undefined: an undefined default is materialised as a 0xAA fill that
moved the static task pool out of .bss and made the whole kernel ~7x
slower under QEMU TCG (caught by the affinity test).

Proven end to end by the new args test: args-echo respawns itself with
arguments via the syscall blob, burns more stack than one page could
hold, and echoes its argv intact. Full suite: 44/44.
2026-07-11 08:33:12 +01:00
Daniel Samson 8754d4e46a Re-organize the source tree as a monorepo mirroring the FHS
The source layout now mirrors the runtime filesystem hierarchy
(docs/danos-file-system-hierarchy-FSH.md): what lives under system/ in the
source is what a running danos represents under /system. Each service and
driver is a sub-project directory that is its own Zig module — cross-project
references go by module name, never by a path into another project's files.

Moves (all git mv, history preserved):
- src/            -> system/            (danos internals; the self-representation)
    root.zig      -> danos.zig          (the kernel<->user contract module)
    kernel/arch/  -> kernel/architecture/   (arch -> architecture)
    device/       -> devices/           (what /system/devices reflects)
    boot/         -> /boot              (the loaders, top level)
- sbin/           -> split by role:
    init, vfs     -> system/services/<name>/<name>.zig
    hpetd, busd   -> system/drivers/<name>/<name>.zig
    vfs-test      -> system/services/vfs/vfs-test.zig  (inside the vfs project)
- lib/            -> library/runtime/   (room for other libraries beside runtime)

The VFS wire protocol becomes its own module, system/services/vfs/protocol.zig
("vfs-protocol"): the vfs sub-project exposes its interface, and the runtime's
file layer imports it by name. First instance of the "protocol module" pattern
(docs/driver-model.md); usb/block will expose theirs the same way.

Also: fix a naming-standard violation in the protocol — Op -> Operation (and
req -> request, _pad -> _padding). Docs updated: /system/services added to the
FHS doc, a repository-layout section added to the docs index, and stale source
paths swept across comments and docs.

Runtime boot paths are unchanged (the bootloader still loads /sbin/init);
aligning the runtime filesystem to the FHS is a separate follow-up. Suite 35/35
plus host tests green.
2026-07-10 12:55:56 +01:00