Commit Graph

392 Commits

Author SHA1 Message Date
Daniel Samson 6271278d4d
C6: update docs for the library/kernel + client split
Rewrote the repository-layout and driver-model docs to describe the new tree —
library/kernel (the kernel32-style system library: syscall surface split by
concern), library/device (mmio/model/pci/usb/acpi/driver/block), library/client
(display, input service clients), library/protocol (wire contracts) — and swept
the reference docs off the retired runtime shim:

  runtime.system.*  -> logging.* / time.* / process.* / memory.*
  runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.*
  runtime.ipc/process/time/service/Thread/block/display/input -> the module name
  runtime.device / runtime.device_manager -> driver
  library/runtime/<file>.zig -> its new home (kernel/ client/ device/)

docs/README.md (repository layout + source map), docs/driver-model.md (the module
graph + import lists), and the concern/reference docs (ipc, threading, timers,
logging, power, process-lifecycle, device-manager, display, vdso, sysv, drivers,
input, vfs-protocol, coding-standards, ...) now reflect the split. "runtime" that
remains is the userspace-library *concept*, which is still accurate.

The historical plan docs (display-plan, display-v2-plan, threading-plan) and the
zig-self-hosting design note are left as point-in-time snapshots.
2026-07-22 23:42:25 +01:00
Daniel Samson 37326c7664
C5: delete the runtime.zig and system.zig compatibility shims
With every consumer migrated (C2), nothing imports `runtime` or `system` any
longer. Removed both shim files and their build.zig module definitions, dropped
the `runtime` entry from `default_imports`, and rewrote the per-binary root shim
(library/kernel/root.zig) to import `start` (_start + panic) and `logging`
(std_options) directly instead of through `runtime`.

addUserBinary/addThreadedUserBinary/addUserBinaryImpl lose their `runtime_module`
parameter; the root module now pulls the two modules it needs out of
`default_imports` via a small findImport helper.

The runtime dumping ground is gone: the userspace library is library/kernel
(concern modules), library/device (driver/pci/usb/block/model/mmio/acpi data),
library/client (display, input service clients), and library/protocol (wire
contracts). Consumers @import concern modules by name.

Verified: zig build, zig build test, and QEMU (smoke, thread-spawn, thread-mutex,
logger, display-native, device-manager, usb-storage, input).
2026-07-22 23:32:05 +01:00
Daniel Samson 23bcd77c58
C2: migrate consumers off the runtime shim to direct concern-module imports
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).
2026-07-22 23:28:34 +01:00
Daniel Samson dded46726b
reorg: relocate device/service clients (C3/C4)
- device.zig + device-manager.zig -> library/device/driver/driver.zig (module
  "driver"): the driver author's whole interface — device access (claim/mmioMap/
  irqBind/...) plus the device-manager hello() handshake, folded into one import.
- block.zig -> library/device/block/block.zig (a device type).
- display.zig/input.zig -> library/client/{display,input}/ (userspace-service
  clients — they talk to services, not the kernel).

build.zig module graph updated; the runtime shim now maps runtime.device and
runtime.device_manager onto "driver", so consumers stay untouched (migrated in C2).

zig build green; driver-restart, usb-storage, display-native, input pass.
2026-07-22 23:02:04 +01:00
Daniel Samson 60f32ee9ff
reorg: split the runtime into library/kernel concern modules (C1)
The one giant `runtime` module (with a `system.zig` that was itself a dumping
ground of unrelated syscalls) is split into directly-importable, flat concern
modules under library/kernel/:

  system-call  ipc  memory  process  thread  time  logging  file-system  service  start
  (+ the device/service clients: device, device-manager, block, display, input)

system.zig is dissolved — its functions moved to their concern home (mmap ->
memory, spawn/kill/exit -> process, sleep/clock -> time, write/klog -> logging,
fs* -> file-system). `memory` merges heap+dma+shared-memory behind one flat API
(memory.allocator/dmaAlloc/sharedCreate/mmap), keeping heap's state and malloc
export single. The memory<->thread dependency cycle (heap needs Thread.Mutex,
thread needs mmap) is broken by having thread allocate its own stack via the raw
mmap syscall, so the module graph is a DAG.

This is the atomic step: all 42 internal cross-imports flip from relative to
module imports at once. `runtime.zig` and `system.zig` become thin re-export
SHIMS so the ~38 consumers keep compiling on `runtime.*` untouched; they migrate
to direct imports in C2, after which the shims are deleted (C5).

zig build + zig build test green; 14 QEMU cases pass (smoke, process,
process-kill, thread-spawn/join, logger, vfs, fat-mount, display-native,
usb-storage, virtio-gpu, device-manager, input, power-button).
2026-07-22 22:53:13 +01:00
Daniel Samson 3e69712b97
reorg: rename library/runtime -> library/kernel (groundwork)
First step of splitting the runtime dumping ground. Pure directory rename
(git mv library/runtime library/kernel) + the six build.zig path references
repointed. The module is still named "runtime" for now; the next commits split
it into concern modules (ipc, memory, process, time, logging, file-system, ...),
dissolve system.zig, and delete the runtime aggregator.

The library/kernel name follows the kernel32 model: it is the userspace library
that wraps the private kernel ABI, distinct from system/kernel/ (the kernel).

zig build green.
2026-07-22 22:27:39 +01:00
Daniel Samson 11f567ee20
kernel: flatten the device/discovery code, merge power into acpi, rename fdt
Follow-up cleanup of the just-moved kernel device code:

- power.zig -> acpi.zig. Its reboot() is built entirely on the FADT reset
  register (acpi.power_information) plus the legacy 0xCF9/8042 fallbacks — it is
  ACPI reboot, so it becomes acpi.reboot (the "P" in ACPI). platform.reboot
  still delegates; soft-off/S5 stays the ring-3 acpi service's job as before.

- device-tree.zig -> fdt.zig. It is a discovery *backend* (the ARM/FDT parser,
  a sibling of acpi.zig), not part of the model — renaming it to its actual
  subject kills the confusing device-tree / device-model DeviceTree name clash
  and makes acpi.zig + fdt.zig read as the two parallel backends.

- Flatten: device-model.zig, acpi.zig, fdt.zig, platform.zig move out of the
  system/kernel/devices/ subdir up into system/kernel/, joining devices-broker.zig
  (already flat). The kernel dir is a flat pile by convention (only architecture/
  is a subdir), so the subdir — and its poor "devices" name — is gone.

Pure restructure; "platform" module name unchanged, all cross-file deps are
relative siblings that moved together. zig build + test green; smoke, discovery,
acpi-parse, acpi-ps2, acpi-report, power-button, orderly-shutdown, reboot pass.
2026-07-22 21:49:20 +01:00
Daniel Samson 3a155cdc7d
reorg: move system/devices into system/kernel/devices
With the shared device data (device-abi, pci-class, usb-abi, usb-ids, acpi-ids,
aml) now in library/device/, what remained in system/devices/ was purely
kernel-internal: the firmware-discovery machinery and the rich pointer-based
device model (platform, device-model, acpi, device-tree, power), reached only
through the "platform" module by three kernel files. It belongs with the kernel.

Move it to system/kernel/devices/. A pure relocation: the "platform" module
name is unchanged and every cross-dir dependency is a module import, so only the
one b.path and some comments move. The top-level split is now clean —
system/kernel/ is the kernel, library/device/ the shared device libraries,
system/{drivers,services} the userspace. The runtime /system/devices concept
(the virtual device tree) is unaffected; system/kernel/devices/ is its
implementation.

Also fixes two comment refs that still pointed device-abi at its pre-Wave-1a
home (system/devices/) — it lives at library/device/model/ now.

zig build + test green; smoke, discovery, acpi-parse, acpi-ps2, acpi-report pass.
2026-07-22 21:35:30 +01:00
Daniel Samson 5b874fc756
reorg: move mmio into library/device and spell out its API
mmio is device-driver code, so it joins the other domains under
library/device/mmio/ (module name "mmio" unchanged — a pure relocation, only
the build paths move). And its abbreviated function names are spelled out per
docs/coding-standards.md:

  read  -> readRegister          mb  -> memoryBarrier
  write -> writeRegister         rmb -> readMemoryBarrier
                                 wmb -> writeMemoryBarrier

All call sites updated (virtio-gpu, usb-xhci-library, pci.Function); the two
display-driver placeholders import mmio but use nothing, so they're untouched.
Docs (driver-model graph, README layout, drivers.md, the FHS note) follow the
new path and names.

zig build + test green; virtio-gpu, display-native, display-reattach, usb-hid,
usb-hub, usb-storage, pci-scan pass.
2026-07-22 21:28:32 +01:00
Daniel Samson 7d540c4b2f
reorg: normalise every protocol alias to its module name
Finish the direct-import convention: a protocol is now aliased to its module
name in snake_case in every file that imports one — consumers and the runtime
client wrappers alike. This kills the last of the alias variety (the generic
`protocol`, plus `power` and `transfer`), so `device_manager_protocol.Hello`
means the same thing everywhere and grepping a module name finds all its uses.

Pure rename (identifier uses only; prose references left untouched via a
guarded pattern). 14 files, 299/299 lines.

zig build + test green; 16 QEMU cases pass (smoke, device-manager,
driver-restart, device-list, pci-scan, usb-hid, usb-storage, display-service,
display-native, display-reattach, input, acpi-ps2, vfs, fat-mount,
power-button, orderly-shutdown).
2026-07-22 21:17:54 +01:00
Daniel Samson ac2d102878
reorg: docs — library/device/ + library/protocol/ structure
Update the docs to match the reorganized tree:

- driver-model.md: the "Families" graph now shows library/device/<domain>/
  (data + logic split) and library/protocol/ instead of the old bus/ + proto/
  sketch; add the microkernel note (the kernel's one library/ import is the
  device-abi data module) and the rule that a driver's hardware command set is
  not a service-seam protocol.
- README.md "Repository layout": device-abi and the protocols move out of
  system/ into library/device/ and library/protocol/; rewrite the "public
  interface as a module" paragraph around the protocol tree and the device
  domains; fix the source-map paths.
- Sweep every remaining stale path reference across docs/ to the moved files.
- Fix vfs-protocol.zig's own header (the standalone VFS server retired; the fat
  server is the backend today).

zig build + test green.
2026-07-22 21:09:48 +01:00
Daniel Samson 3c9475e33a
reorg: move power-protocol to library/protocol; finish the protocol tree
power-protocol -> library/protocol/power/power-protocol.zig. Its two consumers
(init and the acpi discovery service) import it directly; the
runtime.power_protocol re-export and the runtime module import are dropped (no
runtime client speaks it). The now-empty system/services/power/ is removed.

This completes library/protocol/: every driver<->service wire contract lives
there and is imported by module name, no protocol is re-exported through
runtime, and the alias chaos (dp/sp, mixed protocol/<x>_protocol) is resolved.
virtio-gpu-protocol stays a driver-private relative import (a hardware command
set, not a driver<->service seam — like virtio-pci.zig beside it).

zig build + test green; power-button, orderly-shutdown pass.
2026-07-22 21:04:37 +01:00
Daniel Samson 44122bd44d
reorg: move display + scanout protocols to library/protocol
display-protocol -> library/protocol/display/, scanout-protocol ->
library/protocol/scanout/. Consumers import both directly, killing the cryptic
dp/sp aliases in virtio-gpu (now display_protocol / scanout_protocol) and the
runtime.display_protocol / runtime.scanout_protocol re-exports. runtime.display
(the client) still imports display-protocol by name; scanout has no runtime
client, so its runtime module import is dropped too.

zig build + test green; display-service, virtio-gpu, display-native,
display-reattach pass.
2026-07-22 21:02:31 +01:00
Daniel Samson 9ef22d6e55
reorg: move device-manager-protocol to library/protocol + direct import
device-manager-protocol -> library/protocol/device-manager/. Its consumers
(pci-bus, usb-xhci-bus, the acpi discovery service, device-manager, device-list,
crash-test, and the not-yet-built intel-integrated display sub-driver) import the
module directly instead of through runtime.device_manager_protocol, which is
deleted. runtime.device_manager (the hello client) already imported the module
by name and is unchanged.

zig build + test green; device-manager, driver-restart, device-list, pci-scan pass.
2026-07-22 20:59:43 +01:00
Daniel Samson 07c901c18c
reorg: move input-protocol to library/protocol + direct import
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.
2026-07-22 20:54:53 +01:00
Daniel Samson 25cc4d610e
reorg: move vfs-protocol to library/protocol + direct import
vfs-protocol -> library/protocol/vfs/vfs-protocol.zig. fat, its only consumer,
now imports the module directly (const vfs_protocol = @import("vfs-protocol"))
instead of through runtime.vfs_protocol, and the runtime re-export is deleted.
The runtime's own client (runtime.fs) still imports the module by name.

zig build + test green; vfs, fat-mount pass.
2026-07-22 20:52:09 +01:00
Daniel Samson f90dc6c121
reorg: begin library/protocol/ — move block + usb-transfer
Start the protocol tree: wire protocols move to library/protocol/<name>/,
entry file <name>-protocol.zig, module name unchanged. block and usb-transfer
are pure moves — every consumer already imports them by module name, so only
the b.addModule paths change and the empty system/services/block/ is removed.

  block-protocol         -> library/protocol/block/block-protocol.zig
  usb-transfer-protocol  -> library/protocol/usb-transfer/usb-transfer-protocol.zig

zig build + test green.
2026-07-22 20:49:07 +01:00
Daniel Samson 1d1234c963
reorg: move the USB client to library/device/usb (drop runtime.usb)
The USB class-driver transfer client was library/runtime/usb.zig, re-exported
as runtime.usb — which compiled the USB client and usb-transfer-protocol into
every user binary (init, fat, the compositor…), none of which speak USB. It is
bus-family logic, not core runtime.

Move it to its domain home, library/device/usb/usb.zig (module "usb"), alongside
the usb-abi and usb-ids data modules; fix its internal imports to go through the
runtime module; and re-export usb.abi / usb.ids so a class driver reaches the
whole USB domain through one import. runtime.usb and runtime's
usb-transfer-protocol import are removed; the three class drivers
(usb-hid keyboard/mouse, usb-storage) import module "usb" directly.

zig build + test green; usb-hid, usb-storage pass.
2026-07-22 20:46:51 +01:00
Daniel Samson a7f0c1a450
reorg: extract library/device/pci — the claimed-function view
New pci logic module (library/device/pci/pci.zig): a device driver's view of
the one PCI function it has claimed — Function.map (config space = resource 0),
vendorId/deviceId/command/status, enableMemoryAndBusMaster, mapBar (BAR decode
+ resource correlation + mmio_map, cached), and a capabilities() iterator. The
generic PCI mechanics every leaf PCI driver used to re-derive inline.

The config-space layout it needs — header offsets, the command MEM|bus-master
bits, the status capabilities-list bit, the capability-pointer mask, and the
BAR bit fields — is named in the pci-class data module (a "Configuration-space
layout" section), so the bus enumerator can share the same constants later.

virtio-gpu is the first consumer: its inline mapBar + walkCapabilities + config
header reads are gone, replaced by pci.Function; only the virtio-specific
cfg_type dispatch (and the virtio common-config cfgRead/cfgWrite, which are NOT
PCI config space) stay in the driver. The generic display driver will use the
same module. pci-bus's enumerator (arbitrary-function probing) is untouched.

zig build + test green; virtio-gpu, display-native, display-reattach, pci-scan pass.
2026-07-22 20:43:47 +01:00
Daniel Samson f86f2987d5
reorg: decouple the microkernel from the device taxonomies
The kernel's only dependency on pci-class and acpi-ids was cosmetic: the
boot-time device-tree dump (DeviceTree.dump) decoded class codes and _HID
strings to human names. That is device decoding — a user-space concern in a
microkernel, and the last thing pinning two userspace taxonomies into the
kernel's compile.

dumpNode now prints raw values (the class tag, the raw _HID string, and the
packed PCI class code as hex); a user-space tool that owns the taxonomies
(the device manager already imports them) can pretty-print when wanted. The
pci-class / acpi-ids imports are dropped from device-model.zig and from the
platform module.

The kernel's sole remaining library/device/ import is now device-abi (pure
data — the device-model types the broker marshals across the syscall
boundary), the one intentional kernel->library crossing.

zig build + test green; smoke, discovery, acpi-parse, acpi-ps2 pass.
2026-07-22 20:38:29 +01:00
Daniel Samson 794a8b5782
reorg: move device data modules into library/device/<domain>/
First step of the device-code reorganization (plan: group device code by
domain, split each domain into a shareable data module + a logic module).
This moves the pure-data modules — the enums, wire types, and taxonomies
that any layer including the kernel can import cheaply — out of
system/devices/ and into their domain home:

  device-abi  -> library/device/model/device-abi.zig
  pci-class   -> library/device/pci/pci-class.zig
  usb-abi     -> library/device/usb/usb-abi.zig
  usb-ids     -> library/device/usb/usb-ids.zig
  acpi-ids    -> library/device/acpi/acpi-ids.zig
  aml/        -> library/device/acpi/aml/

Module names are unchanged, so this is a pure file move: only the
b.addModule paths and the host-test file list in build.zig change; no
importer is touched. system/devices/ now holds only the kernel-internal
device model (device-model, platform, acpi, device-tree, power).

The device *logic* (the pci Function helper, the usb transfer client) and
the kernel's cosmetic taxonomy dependency are handled in following commits.

zig build + zig build test green.
2026-07-22 20:36:24 +01:00
Daniel Samson ea470afe84
runtime: consolidate the device-manager hello into runtime.device_manager
Every supervised driver owes the device manager a hello at startup, and
the retry-lookup-call-check for it had been copied into five drivers:
usb.helloManager (misfiled in the USB client) plus hand-rolled twins in
display, virtio-gpu, pci-bus, and usb-xhci-bus.

Extract it once as a runtime client, runtime.device_manager.hello(role,
device_id) ?Handle — returns the manager endpoint (bus drivers keep it to
report children through), null when there is no manager or it refused the
handshake, and logs the outcome itself so each call site is one line.

Also correct two roles while collapsing their calls: virtio-gpu and the
display driver each claim one PCI function and report no children, so they
are Role.device, not Role.bus. The manager ignores role today, so this is
cosmetic, but it matches the protocol's own definition (bus = reports
children via child_added).

Behavior-preserving otherwise: pci-bus and usb-xhci-bus move their hello
logging from raw serial writes to std.log, which the kernel renders with
the same "<path>: " prefix, so driver-restart still matches
"usb-xhci-bus: hello acknowledged". zig build clean; 8 QEMU cases pass
(driver-restart, pci-scan, usb-hid, usb-storage, virtio-gpu,
display-reattach, device-list, device-manager).
2026-07-22 19:27:20 +01:00
Daniel Samson d27377ab1e
add placeholders for display driver 2026-07-22 17:36:55 +01:00
Daniel Samson cacbacd76b
docs: amd-gpus.md — RDNA2 display-driver feasibility, the AMD companion survey 2026-07-22 12:25:07 +01:00
Daniel Samson e63d6ef5ef
Merge shared-fate: whole-process death (thread groups die together)
A fault in any thread, exit from any thread, or process_kill on any member id
now takes down the entire thread group; only a worker's voluntary thread_exit
stays per-thread. Leader id (tgid-style), dying latch on the address space,
group notification at space destruction, shared-memory mapping references,
per-space DMA/shm arena cursors, and the shm/DMA page-table walks brought
under the big kernel lock. Design + red-team + review in
docs/shared-fate-plan.md. QEMU suite: 100/100.
2026-07-22 11:25:32 +01:00
Daniel Samson c8191570e1
kernel: shared-fate review fixes — lock the shm/dma walks, contract edges
The adversarial review of the branch confirmed the big one: the shm/DMA
page-table walks and their pmm/heap calls ran outside the big kernel lock —
pre-existing, but fatal once the per-space cursors invited sibling threads to
race them (two concurrent creates could orphan a page table: one thread's
region silently unmapped, the frame leaked — a plausible root for the
long-standing intermittent AP ring-3 fault at the shm base). All three paths
now follow the mmap discipline: allocation, object build, record, and handle
under one lock hold with full rollback; the map itself per-page under brief
holds; dma_free's translate/unmap/free per-page likewise.

Contract edges from the same review: thread_spawn into a dying group returns
-ESRCH (was generic -1); process_kill during the condemned window answers
from the latch's stashed supervisor (0 or -EPERM, was -ESRCH once the leader
slot was reaped); exit derives the group reason from its own argument rather
than the racy exit_code global; a worker's thread_exit no longer overwrites a
concurrent group-kill stamp; checkGroupDead now asserts exactly-one
notification via the drained ring. Full suite: 100/100.
2026-07-22 11:23:32 +01:00
Daniel Samson 2bc2a0d70d
kernel+tests: M4 shared-fate — nine group-death test cases, per-space arena cursors
Eight new QEMU cases (thread-fault-group, kill-threaded-group,
kill-via-worker-tid, racing-triggers, exit-group, leader-thread-exit,
thread-exit-solo, shm-mapping-ref) driving seven new thread-test modes; a
shared checkGroupDead asserts the contract everywhere: one notification,
badged with the leader, reason on the leader's record, no member listed,
claims released first.

The shm-mapping-ref case flushed out the per-task DMA/shared-memory arena
cursor bug directly (a sibling's regions mapped over the worker's), so both
cursors moved to the AddressSpaceRef like the mmap/MMIO cursors before them
(threading M7 pattern). Runtime gains Thread.tryExitCurrent for the leader
-EPERM refusal path. Docs updated: threading.md's shared-fate gap is closed,
process-management.md and process-lifecycle.md describe the leader re-key,
plan status = implemented. Full suite: 100/100.
2026-07-22 10:49:46 +01:00
Daniel Samson 1882161cb4
docs: system-image.md — the boot capsule documented in full
system.img was only mentioned in passing (efi.md, system-requirements.md);
its format, builder, fallback chain, and kernel-side life were spread across
pack-system-image.py, build.zig, efi.zig, initial-ramdisk.zig, and vfs.zig.
New dedicated page covers all of it, cross-linked from every prior mention
and added to the docs index.
2026-07-22 10:37:55 +01:00
Daniel Samson 08e139ebba
kernel: M3 shared-fate — shared-memory frames live while any mapping does
Each address space that maps a shared-memory region now holds its own
reference, recorded on the AddressSpaceRef and dropped when the space is
destroyed — so 'last reference' means no handles AND no mappings, and a
region's frames can no longer be freed out from under a sibling thread (or
any other live mapper) when the handle-holding task dies. The group-death
notification still posts after every mapping release. (docs/shared-fate-plan.md M3)
2026-07-22 10:34:03 +01:00
Daniel Samson b09a62bc36
kernel: M2 shared-fate — group fan-out, dying latch, deferred leader notification
All process deaths (exit from any thread, ring-3 fault, process_kill) now kill
the whole thread group via killGroupLocked: latch the AddressSpaceRef as dying
(refusing new members, closing the thread_spawn escape), stamp every member
(leader carries the group reason — the record the supervisor reads), reap
parked members to fixpoint, condemn running ones. The leader's exit
notification and subscriber broadcast move to the group-death moment — the
last address-space reference drop — via scheduler.group_exit_hook, which
re-stamps the leader's exit record first. Leader thread_exit is refused with
-EPERM. kill_pending is atomic; exit_reason and fault_kill_count writes moved
under the big kernel lock. (docs/shared-fate-plan.md M2)
2026-07-22 10:23:41 +01:00
Daniel Samson daca0d9216
kernel: M1 shared-fate — Task.leader id, kill/signal re-keyed to the leader
Every task carries its process leader's id (main task: own id; threads:
copied from the spawner; kernel tasks: 0, never followed). process_kill and
process_signal resolve any member id to the leader and authorize against the
leader's supervisor, making both capabilities per-process. ProcessDescriptor
gains the leader field. No fan-out yet (docs/shared-fate-plan.md M1).
2026-07-22 10:17:13 +01:00
Daniel Samson e78810195d
docs: shared-fate plan — approved design for whole-process death
Red-teamed draft: leader id (tgid-style), AddressSpaceRef dying latch,
killGroupLocked fan-out, group notification at address-space destruction,
shared-memory mapping refs. Leader thread_exit refused with -EPERM (decided
at sign-off). Milestones M1-M4.
2026-07-22 10:12:42 +01:00
Daniel Samson e854f65623
docs: full docs-vs-code audit — fix every stale claim across 40 docs
Every doc verified claim-by-claim against the code by parallel audit agents,
then fixed and adversarially re-verified. Two waves of staleness corrected:
the originally audited findings (higher-half boot handoff, kernel VFS
takeover, fault isolation + claim release + driver restart, AML/S5 moving to
ring 3, threading's shipped design, USB+FAT landing) and a second pass of
adjacent claims the verifiers caught (smp.md 'not built yet' intro,
system-requirements' PS/2-only and no-storage claims, halting.md's red-panic
and no-IDT text, testing.md's serial mirroring, router-era vfs-protocol
wording, capsule-first boot loading).

threading.md now documents the shared-fate gap explicitly: the design says a
process dies whole, the kernel today kills only the offending thread.

Also fixes three stale code comments (isr.s exceptionHandler, acpi.zig
sleepValue, build.zig boot-volume) — comments only, no behavior change.
2026-07-22 09:09:53 +01:00
Daniel Samson 52df2ba6f6
docs: audit all 'what's next' sections against the code; fix stale comments
Verified every deferred item in the nine docs with a what's-next section and
marked what has since landed (reaper, per-process CR3, higher-half kernel,
kernel heap, contiguous frame alloc, RSDP capture, cap-passing, driver restart
with backoff, PS/2 keyboard) while keeping the genuinely open items. Also
corrects interrupts.md's claim that the keyboard skipped the IO-APIC, and
updates scheduler.zig/heap.zig comments that predated the reaper and the big
kernel lock.
2026-07-22 02:23:51 +01:00
Daniel Samson a081f69def
docs: mark interrupts.md 'what's next' items as done (IO-APIC, SSE state) 2026-07-22 02:15:12 +01:00
Daniel Samson f9dea7790a
docs: rename arch.md to architecture.md and update all references
Rename the file to match current naming conventions and update all
cross-references throughout the documentation. Update the content to
reflect the current state of the codebase:

- Module is now called "architecture", not "arch"
- Directory is system/kernel/architecture/, not system/kernel/arch/
- Generic kernel file is kernel.zig, not main.zig
- Updated file listing for x86_64 architecture module (added smp.zig,
  per-cpu.zig, ioapic.zig, trampoline.s)
- Updated function list in cpu.zig to include enterUser/userExit and
  address-space management
- Clarified that _start lives in architecture-specific isr.s
- Expanded architecture-specific vs generic comparison table with
  actual current modules

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-22 02:10:00 +01:00
Daniel Samson 7e4071a065
fat: a small write-through block cache for metadata sectors (B5b)
Directory scans and FAT-chain walks re-read the same sectors constantly:
resolving many paths under one directory (a logging burst opening dozens
of files under /var/log/<stamp>/) re-read that directory's sectors and the
FAT from the device every time. Add a 16-line write-through cache under the
single-sector blockRead/blockWrite path, keyed by filesystem-relative LBA
with round-robin eviction, so repeated metadata reads come from RAM instead
of a USB round trip each.

Disciplines that keep it safe:
- Write-through: every write reaches the device immediately and refreshes
  the cache, so it never holds dirty-only data — the crash-safe
  data->FAT->directory write order and the flush-on-close are unchanged.
- Bulk file data (the B5a multi-sector run path) bypasses the cache and a
  run write invalidates any overlapping cached sector, so metadata that
  shares a range can never go stale.
- Cluster-zeroing writes uncached (write-once bulk that would only evict
  live metadata), invalidating any stale copy.

A new engine test proves a repeated resolve does zero device reads and that
a write is coherent both in-cache and against a cold-mounted filesystem
(it really reached the device).

Full QEMU suite green (the one intermittent `logger` miss is the documented
pre-existing AP ring-3 fault: 16/16 logger reruns pass on this change and
every other storage case is green; the fat engine is single-threaded and
the cache is bounded global state, so it cannot itself fault intermittently).
2026-07-22 01:08:34 +01:00
Daniel Samson 6e261ccda1
fat: multi-sector block transfers (B5a)
The FAT engine read and wrote one sector per device command — one SCSI
READ(10)/WRITE(10) over USB Bulk-Only Transport per 512 bytes, so every
file read, log write, and cluster fill paid a full USB round trip per
sector. The lower stack (runtime.block, the block protocol, usb-storage's
read10/write10 + count*block_size data stage) already carried a
multi-sector count; only the engine's BlockDevice interface and IpcBlock
were single-sector.

Widen BlockDevice to move a run of `count` contiguous sectors per call
(readBlock/writeBlock kept as count=1 wrappers, so metadata call sites —
FAT sectors, directory entries — are untouched). readFile and writeFile
now coalesce the aligned full-sector middle of a transfer into one command
(capped at the cluster boundary and the 4 KiB bounce = 8 sectors), reading
straight into / writing straight from the caller's buffer with no staging
copy. Full-sector writes skip the read-modify-write entirely, since they
overwrite the whole sector. Partial head/tail sectors keep the per-sector
RMW path.

IpcBlock passes count through to the block driver and sizes its bounce from
engine.max_transfer_sectors (already 4 KiB — no new allocation). A new
spc=8 engine test proves runs coalesce (a 21-sector overwrite drops from
>=21 writes to <=5) and that reads/writes round-trip byte-identical,
including an unaligned offset spanning a cluster boundary.

Full QEMU suite 92/92.
2026-07-22 00:11:51 +01:00
Daniel Samson ded555961c
Merge usb-superspeed-hub: hubs work on real hardware (M22)
USB hub support, root-caused and validated on the user's real machine:
a Keychron keyboard and ROG mouse now enumerate through a Genesys
compound USB3 hub (its USB2 companion + transaction translators) and
type on danos.

Fixes found by reading logs off the stick, each invisible to QEMU:
- SuperSpeed hubs have change bits a USB2 hub lacks; not clearing them
  spun the driver. Clear them (gated on speed) + a per-tick service cap.
- The boot scan runs ~3 ms after the controller reset, too early for a
  USB2 connection to debounce; the SuperSpeed devices train instantly
  and hid the problem. Poll the root ports on the tick (edge-triggered).
- Full-speed devices failed Address Device with a USB Transaction Error:
  USB 2.0 requires a 10 ms reset-recovery interval before SET_ADDRESS.
  Wait it out, and retry Address Device on a transaction error.

Plus richer logs: readable PORTSC decode, USB link-state and speed
names, device string descriptors (maker/product), and a readable name
for every class/subclass/protocol enum in usb-ids. Full suite 92/92.

Open follow-up: 'port 11 enumeration failed' — a third device that did
not come up; to be investigated separately.
2026-07-21 23:48:29 +01:00
Daniel Samson 802d51ba74
usb-ids: human-readable name for every class/subclass/protocol enum
Every enum in usb-ids now has a name function, so any code (logs, tools)
can print the readable value instead of a raw byte: hub.protocolName,
hid.subclassName/protocolName, mass_storage.subclassName/protocolName,
communications.subclassName, wireless_controller.subclassName/
protocolName, miscellaneous.subclassName/protocolName,
application_specific.subclassName (alongside the existing className,
speedName, linkStateName).

interfaceName is now comprehensive across classes — it decodes the
well-known triples a log shows: 'HID boot keyboard', 'Mass Storage
(Bulk-Only)', 'Hub (SuperSpeed)', 'Bluetooth' (E0/01/01), etc. So the
bus log reads e.g.

  interface 0: Mass Storage (Bulk-Only) (8/6/80) registered as device 30
  interface 0: HID boot keyboard (3/1/1) registered as device 33

Host-tested with usb-ids (the name decodings are pinned). Vendor-ID
naming deliberately left out (needs a data table); this is pure standard
class-code decoding.
2026-07-21 23:43:01 +01:00
Daniel Samson 570f6f545c
usb: quiet the investigation diagnostics now that hubs work on real HW
The compound-hub investigation is resolved (Keychron keyboard + ROG
mouse enumerate through the Genesys USB2 companion hub and type on real
hardware). Turn the debugging spam back down for main: the full 22-port
PORTSC dump runs only when a scan finds NOTHING (a 'why is this empty'
aid), not every boot; a downstream hub port logs only when a device
actually appears or leaves, not for every empty seed-sweep port. The
readable device names, class names, and port-change events stay.
2026-07-21 23:33:12 +01:00
Daniel Samson da5f404041
usb: post-reset recovery delay + Address Device retry on transaction error
Real-hardware root-cause, from the now-readable log: the tick-poll fix
DID catch the user's full-speed devices (USB2 root ports 3, 9, 11
'device appeared — Full-speed'), but every one failed 'Address Device
completion code 4' = USB Transaction Error. Cause: danos addressed the
device immediately after the port reset, but USB 2.0 (spec 7.1.7.5)
requires a reset-recovery interval (TRSTRCY, 10 ms) before a device
answers SET_ADDRESS. QEMU tolerates the omission; real full-speed
devices do not.

setupDevice now waits 10 ms after a port reset before addressing, and
addressDeviceCommand retries up to 3 times on a transaction error,
re-resetting a root-port device between attempts (xHCI 4.6.5 recovery).
QEMU USB cases still green. Real-hardware confirmation pending — this is
the specific fix for the user's keyboard/mouse failing to address.
2026-07-21 23:25:19 +01:00
Daniel Samson ab732dc455
usb: readable PORTSC decode, link-state names, and device string descriptors
Make the USB diagnostic logs scannable by eye:
- usb-ids gains speedName + linkStateName (USB3 PORTSC link states: U0,
  RxDetect, Polling, ...), host-tested with usb-ids.
- The PORTSC dump decodes the register instead of printing hex flags:
    PORTSC[3] 0x00021203: connected, enabled, link=U0, power=on, SuperSpeed
    PORTSC[5] 0x00020ee1: connected, disabled, link=Polling, power=on, High-speed
  (the raw word stays for reference). A USB2 device reads connected+
  disabled+Polling until reset — so the user's next log shows at a glance
  whether the companion port ever reaches that state.
- The library reads STRING descriptors (readString, UTF-16LE -> ASCII,
  English langid), and the device line now names the maker + product:
    port 5 device: Hub "Genesys Logic USB3.0 Hub" (0x05e3:0x0626), 1 interface(s)
  instead of a bare vendor/product id pair.

Full USB case set green (hub regexes follow the new 'device: <class>
"<maker> <product>"' format). Branch diagnostics for the SuperSpeed
compound-hub investigation.
2026-07-21 23:19:18 +01:00
Daniel Samson 4ea4a040d2
usb-ids: className/interfaceName helpers; readable device classes in the bus log
The logs read 'class 9/0/0' where they could say 'Hub'. usb-ids gains
className (a device/interface class byte -> 'Hub', 'HID', 'Mass Storage',
...) and interfaceName (a HID boot interface -> 'HID boot keyboard' /
'HID boot mouse'), both host-tested with usb-ids. The bus driver's
enumerate + register lines now read, e.g.:

  port 21 device: Hub vendor 0x05e3 product 0x0626, 1 interface(s)
  port 21 interface 0: Hub (9/0/0) registered as device 47
  port 5 interface 0: HID boot keyboard (3/1/1) registered as device 32

so a real-hardware log is scannable by eye. Full USB case set green
(hub regexes follow the new 'device: <name>' format).
2026-07-21 23:15:16 +01:00
Daniel Samson 0b10a637ae
usb: poll root ports on the tick — catch a late USB2 connection (edge-triggered)
Real-hardware root-cause: the user's keyboard works in the firmware boot
menu but dies under danos. The port dump shows why — danos scans the
root ports ~3 ms after the controller reset, but a USB2 connection needs
~100 ms to debounce, so the boot scan sees the USB2 companion hub's port
empty (all USB2 ports ccs=0), while the SuperSpeed devices (hub, storage)
train instantly and DO show up. danos then relied on a Port Status
Change EVENT to catch the late USB2 connection, which does not fire
reliably on this hardware.

The tick now polls every root port and reconciles on the empty->connected
EDGE (previous-state tracked per port, seeded from the boot scan so
already-up ports never re-fire), bringing up a device that appears after
the scan without depending on the event. Edge-triggered so a port that
fails to enumerate is not retried every 8 ms. Branch-only until the
keyboard is confirmed on real hardware; the diagnostic dumps stay for
the next boot.
2026-07-21 23:06:49 +01:00
Daniel Samson 10956c6660
usb: [diagnostic] dump the xECP USB2/USB3 port map + all PORTSC
The USB2 companion hub carrying the user's full-speed keyboard never
appears — only the SuperSpeed hub (port 19) and SS storage (port 21)
connect. To find where the USB2 root ports are and whether the companion
is presenting on one, walk the xECP Supported Protocol capabilities
(logging each USB 2.0 / 3.0 root-port range) and dump every port's raw
PORTSC unconditionally (ccs/ped/pls/pp/speed). QEMU confirms the dump
(USB2 ports 5-8, USB3 ports 1-4). Branch-only diagnostic.
2026-07-21 22:56:25 +01:00
Daniel Samson 18408b0666
usb: [diagnostic] dump all root-port PORTSC + log port-change events
Branch-only debugging for the real SuperSpeed compound hub: the spin fix
(27f87cb) stopped the hang — the SS hub's 4 SuperSpeed ports now enumerate
(all empty, correct: the full-speed keyboard isn't a SuperSpeed device) —
but the USB2 COMPANION hub, where the keyboard actually lives, never
appears. Only root ports 19 (SS hub) and 21 (SS storage) connect.

Dump every root port's raw PORTSC at scan (connect/enable/link-state/
speed) and log every root port-change event, so the next boot shows
whether the companion is connected on a USB2 port we misread, connects
late as a port-change event, or is simply absent. Diagnostic logging —
to be removed once the companion path works.
2026-07-21 22:50:11 +01:00
Daniel Samson 27f87cb5ba
usb: don't spin on a SuperSpeed hub's change bits; log hub port status
Real-hardware finding: a SuperSpeed hub (the user's Genesys, 4 SS
downstream ports) HUNG the bus driver at boot — it stopped logging with
no fault, and the hub's port power dropped. Cause: a SuperSpeed hub has
change bits a USB2 hub lacks (link-state, BH-reset), and the B4 servicing
— written and tested against QEMU's USB2 hub — never cleared them, so
the hub's status-change endpoint re-reported the same port forever and
the driver spun servicing it, never reaching the USB2 companion hub
where the full-speed keyboard actually lives.

hubPortStatusAck now clears the SuperSpeed-only change features too
(gated on hub speed; a USB2 hub is unaffected), and the tick services a
bounded batch of hub changes (32) before yielding — so even if a hub's
change bits misbehave, the driver cannot spin the machine. A per-port
status log line surfaces exactly what a hub reports, so the next real
boot shows the SS hub's port states and whether the USB2 companion
enumerates. QEMU usb-hub cases still green (the SS clears are gated off
for QEMU's USB2 hub).
2026-07-21 22:44:46 +01:00
Daniel Samson 7ee6033fa4
usb-hid: echo typed characters to the log — a simple keyboard check
There was no way to confirm a keyboard actually registers keys on real
hardware (enumeration binding the driver only proves the device came
up). The keyboard driver now echoes each decoded printable character
(and newline) to its log: type a known phrase and read it back from
usb-hid-keyboard.log, or watch it appear live on screen in a -Ddiagnose
boot (where the kernel console is a log sink). It exercises the whole
path — HID report -> diff -> layout -> character — not just enumeration,
and works identically for a keyboard behind a hub.

A usb-key-echo QEMU case injects a phrase via QMP send-key and asserts
it echoes (stable across repeated runs). Full suite 92/92.
2026-07-21 22:27:47 +01:00
Daniel Samson e52ae242fc
usb: hub-downstream disconnect teardown + hub-behind-hub recursion (B4c)
Completes hub support (docs/usb-hub.md). On the bus tick, a downstream
hub-port change now dispatches: a connect enumerates the new device
(B4b), a disconnect tears the old one down — recursively, since a hub
that leaves takes its whole subtree with it (children first), reporting
each interface ChildRemoved and Disable-Slotting the device. Route
strings compose across tiers, so a device two hubs deep enumerates with
a two-tier route.

QEMU's hub — unlike root-port hot-plug — DOES raise downstream
status-change events, so both paths are harness-tested: usb-hub (a
keyboard behind a hub binds usb-hid-keyboard), usb-hub-nested (a
keyboard two hubs deep), usb-hub-unplug (device_del behind the hub tears
it down). Full suite 91/91.

Real-hardware validation of the user's SuperSpeed Genesys hub with
full-speed devices on its USB2 companion is flagged for the user (QEMU's
USB2 hub does not model the compound USB3 hub).
2026-07-21 22:16:42 +01:00