Commit Graph
100 Commits
Author SHA1 Message Date
Daniel Samson 3af0110483 update docs/os-development/README.MD 2026-07-23 01:41:47 +01:00
Daniel Samson 757c6f14c3 re-org docs 2026-07-23 00:25:34 +01:00
Daniel Samson 52d6e372fd re-org docs 2026-07-23 00:24:01 +01:00
Daniel Samson f023f1cfd6 reorg: move test fixtures to test/system/services (source + boot volume)
The 11 QEMU-suite fixtures lived mixed into system/services/ with their
binaries bundled at /system/tests/<name>. Now the repo path is the boot
path, like every real service: test/system/services/<name>. fat-test
moves out of the fat server's directory into its own; display-demo
stays a boot service.

- kernel VFS: setInitialRamdisk derives one read-only initrd mount per
  top-level tree named by the ramdisk entry paths (/system, /test),
  registers ancestors generically with self-parented roots, and refuses
  backend shadowing of any initrd tree
- EFI loader: the fallback walk also enumerates \test (optional — a
  volume without fixtures still boots); manifest and capsule unchanged
- path literals: vfs-test self-open + create probe, process-test
  process_enumerate matches, the args-echo argv[0] expectation; the
  kvfs case now covers the /test root end to end
- docs: DFHS /test rows, tree diagrams, loader prose, and the location
  convention gain the third home; fixed the input-source link

100/100 QEMU cases pass.
2026-07-23 00:11:07 +01:00
Daniel Samson b9cec7d1be docs: fix two review findings from the reorg audit
An adversarial review of the reorg surfaced two doc inaccuracies:

- timers.md: a `time` code example still did `@import("runtime").time`, which
  no longer compiles — the runtime aggregator was deleted. Now `@import("time")`.
  (Reorg regression: the sweep rewrote `runtime.x` member access but not the
  `@import("runtime")` form inside a fenced code block.)

- sysv.md: `callconv(system.kernel_abi)` named a non-existent `system` module;
  `kernel_abi` lives in the boot-handoff contract (system/boot-handoff.zig),
  imported as `boot_handoff`. Now `boot_handoff.kernel_abi`. (Pre-existing bug —
  "system" was the file-path prefix mistaken for a module namespace — found and
  fixed opportunistically.)
2026-07-23 00:02:27 +01:00
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 SamsonandClaude Haiku 4.5 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
Daniel Samson 9da1a9899c usb: enumerate devices behind a hub — route strings + transaction translators (B4b)
The core of hub support (docs/usb-hub.md): a device on a hub's
downstream port now reaches its class driver exactly like one on a root
port. The hub's interrupt status-change endpoint is armed with an
in-process subscription — completions set the hub's pending-port mask
instead of queuing a class-driver report — and setupHub seeds every
downstream port pending so a STATIC topology (a device present at
power-on) enumerates without waiting on the initial interrupt edge.

On the bus tick, each pending hub port is serviced: GET_STATUS + clear
the change bits, and on a connect reset the port, read the speed, then
enable a slot and Address Device with the composed route string
((parent_route<<4)|port), the inherited root port, and the parent hub's
slot/port as the TRANSACTION TRANSLATOR — so the controller routes a
full/low-speed device's split transactions through the hub's TT. The
device then enumerates and registers its interfaces through the normal
path (a compact topology-unique port key keeps the id tag within its
8-byte cap), recursing setupHub if it is itself a hub.

Verified in QEMU (a keyboard behind a USB2 hub on a second controller):
'hub slot 1 port 1 device vendor 0x0627 ... usb-hid-keyboard: ok
(device 35)'. Full suite 89/89. The user's SuperSpeed Genesys hub +
full-speed keyboard/mouse on its USB2 companion is the real-hardware
target, flagged separately.
2026-07-21 22:06:54 +01:00
Daniel Samson 92b03b8d07 usb: recognize and configure hubs, power downstream ports (B4a)
First slice of hub support (docs/usb-hub.md), handled IN the xhci-bus
because a device behind a hub is reached by the CONTROLLER via a route
string in its slot context — topology only exists inside the driver
that owns the controller. When the scan enumerates a class-9 device it
now calls setupHub: read the hub descriptor for the downstream port
count and TT arrangement, tell the controller the slot is a hub
(Configure Endpoint with the slot add-flag sets the Hub bit, Number of
Ports, and MTT/TT-Think-Time per xHCI 4.6.6), SET_HUB_DEPTH for a
SuperSpeed hub so it can compose route strings, and SET_FEATURE
PORT_POWER every downstream port.

The Device gains topology fields (route string, root port, parent hub
slot/port for the TT) and buildAddressInputContext now fills them, so
the Address Device path is ready for downstream devices — those are
enumerated in B4b. A root-port device gets route 0 and its own port as
the chain root, unchanged behavior.

Verified in QEMU with a hub on a second controller and a device behind
it (a usb-hub QEMU case, since the boot controller's auto-assigned
devices collide on the low ports): 'hub slot 1: 8 downstream ports
powered (USB2 single-TT)'. Full suite 89/89. The user's SuperSpeed
Genesys hub is the real-hardware target, flagged separately (QEMU's
USB2 hub doesn't model the compound USB3 hub).
2026-07-21 21:56:59 +01:00
Daniel Samson 983b4ed05a usb: don't 'correct' SuperSpeed EP0 max packet size — it's an exponent
Regression from the B3 MPS0 work, caught on real hardware: a SuperSpeed
hub (and the SuperSpeed boot stick) failed to enumerate. bMaxPacketSize0
(device descriptor byte 7) is a LITERAL size for USB 2.0 and below
(8/16/32/64) but an EXPONENT for SuperSpeed (9 = 2^9 = 512). The refresh
read the exponent 9 as a size and issued Evaluate Context to set EP0 to
9 bytes, corrupting the control endpoint so every following transfer
failed. SuperSpeed's EP0 is fixed at 512 and needs no correction, so
the refresh is now skipped for speed >= 4; only full/low/high speed,
where the field is a literal that can differ from the speed default,
still run it. QEMU tolerated the wrong MPS0 — real silicon does not
(the class of bug the harness can't reach, flagged real-HW-pending).

This likely also explains intermittent no-storage/no-logs on a normal
boot: the boot stick is SuperSpeed and hit the same corruption. Full
suite green (orderly-shutdown's lone failure was its known QMP-timing
flake — three clean re-runs).
2026-07-21 21:26:56 +01:00
Daniel Samson 0ac07dadc9 usb: hot-plug plumbing, all ports powered, interrupter enabled (M20)
B3 — the runtime lifecycle a hot-pluggable bus needs, plus two init
fixes that runtime device arrival depends on:

- pump() now handles PORT STATUS CHANGE events (silently dropped
  before): it queues the port, and the bus driver brings the port up
  (a device arrived) or tears it down (a device left) on its tick —
  reporting each interface ChildRemoved to the device manager, which
  prunes the node, notifies watchers, and lets the class driver's world
  end honestly, then Disable Slot frees the controller-side state.
- ALL root-hub ports are powered at init, not just those with a
  boot-time device: an unpowered port (PP=0) cannot signal a later
  connect, so a hot-plug would never be seen.
- the interrupter is enabled (IMAN.IE + USBCMD.INTE) while the ring
  stays polled — some controllers only WRITE runtime events to the
  ring when the interrupter is enabled.

Real-hardware validation is flagged for the user: QEMU's qemu-xhci does
not raise a runtime port-change event to a polling driver on device_add,
so the end-to-end hot-plug path can't be exercised in the harness (the
port-change handling itself IS proven — a late boot device's PSCE is
caught and acked). The harness gained qmp_sequence (multi-step QMP
injection with arguments) for when a drivable case exists. Full suite
88/88; the working USB path (enumeration, HID, storage) is unregressed
by the port-power and interrupter changes.
2026-07-21 21:01:41 +01:00
Daniel Samson 4091ea6912 test: boot a per-run copy of the USB image — runs must not inherit leftovers
The guest mutates its boot volume (fat tests create files, the logger
writes /var/log) and QEMU is hard-killed after a match, so booting the
build artifact in place let one run's leftovers fail the next — a stale
TESTDIR tripping the mkdir-duplicate refusal was the lone 87/88 failure
in an otherwise green suite — and dirtied the build cache's own output.
Each case now boots a fresh copy in the work directory.
2026-07-21 20:37:59 +01:00
Daniel Samson d8cf533b73 usb: correct EP0's max packet size from the device; sample speed after reset
Two real-hardware correctness fixes from the M20 list, both invisible
to QEMU's forgiving controller:

refreshMaxPacketSize0 existed only as a comment. The EP0 context kept
the SPEED-DEFAULT max packet size (full-speed: 8) even when the device
declares 16/32/64 — real controllers fault the very first full
descriptor read on the mismatch, which is the standing suspect for
full-speed mice dying on the user's PC. Enumeration now probes the
device descriptor's first 8 bytes (deliverable at any legal MPS0),
and issues Evaluate Context to correct EP0 before any longer transfer,
naming the failure and codes if the controller refuses.

And a USB2 port's PORTSC speed field is only meaningful once the port
reset ENABLES the port: the speed (and the MPS0 default derived from
it) is now sampled after the reset instead of trusting the connect-time
read.
2026-07-21 20:35:36 +01:00
Daniel Samson 1638845a4b usb/fat: transfer events matched by slot+endpoint; storage failures heal
B2 — the 1-in-3 boot-time READ CAPACITY failure, root-caused: the xHCI
library's awaitTransfer claimed ANY unclaimed transfer event as its own
completion. An interrupt-endpoint event whose TRB pointer no longer
matched the armed subscription (an error or stale completion from the
keyboard/mouse polling concurrently with storage bring-up) fell through
and was misread as the bulk transfer's completion — desynchronizing the
mass-storage bulk protocol in controller state that SURVIVED driver
restarts, so every retry failed too. Awaited transfers now match the
event's slot id and endpoint DCI; foreign events are dropped and named.
Twelve consecutive runs of the previously-flaky cases pass; the full
suite is green with none of its old intermittents.

B1 — and when storage does fail transiently, the system now heals
instead of giving up forever: a nonzero exit maps to ExitReason.aborted
(a deliberate FAILURE exit — supervisors restart those with backoff,
unlike a clean .exited), usb-storage exits nonzero when a PRESENT
device fails bring-up, and the fat service no longer blocks its harness
polling for a block device and then dies — it serves immediately
(requests fail politely), retries on a 500 ms timer, and mounts
whenever storage appears, including after a driver restart.
2026-07-21 20:27:55 +01:00
Daniel Samson 7082699f5f gitignore: /var/log — real-hardware log pulls stay out of the tree
(An earlier append landed on a line missing its newline, mangling
'.github/' and silently un-ignoring var/ — which let a stray add sweep
pulled logs into the tree. Repaired, and the logs untracked again.)
2026-07-21 20:09:29 +01:00
Daniel Samson f0611ef8ac logger: logger.log becomes the completeness receipt
Its only content was the redundant announce line — the useful shutdown
marker was written AFTER the final drain, so it reached serial and the
ring but never a file, and a truncated boot could only be inferred from
what was missing. The marker now enters the ring BEFORE the drain, so
the drain carries it into logger.log: a directory whose logger.log ends
with 'shutting down; final flush' is complete through shutdown; one
without it was cut early. The sequence-count epilogue stays serial-only,
after the drain, by design.
2026-07-21 20:05:13 +01:00
Daniel Samson eb6e8edafe apic: time-bound the TSC warp check — 47 s of AP bring-up becomes ~0.3 s
First real per-process logs off the stick (the logging track paying for
itself): kernel.log showed 16-core bring-up costing 47 s — per-core gaps
of 2-21 s — on a machine whose clocksource is the TSC, so every AP runs
the pairwise warp check. QEMU always picks HPET, so the harness never
executed this path at all.

The check was bounded by ITERATIONS: 1<<20 warp ticks, each a locked
read-modify-write on a cacheline two cores fight over — microseconds
under real contention, not the nanosecond the '~1 ms' comment assumed —
and the 1<<32-PAUSE rendezvous 'bound' is ~2 minutes on modern Intel
(PAUSE ~140 cycles). Both are now bounded by TIME measured on the TSC
itself: ~5 ms of pairwise hammering per core (Linux's check_tsc_warp
budget — ample to catch a lagging TSC) and a ~100 ms rendezvous window.
2026-07-21 19:47:53 +01:00
Daniel Samson 59ba95a315 usb: don't reset an enabled SuperSpeed port; name every setup failure
Real-PC diagnose boot (photo + OCR): the boot stick connects at
SuperSpeed on port 21 and 'device setup failed' lands in the SAME
millisecond — an instant failure, not a timeout. setupDevice reset
every port unconditionally; that is required to enable USB2 ports, but
a SuperSpeed port that trained its link is ALREADY enabled (xHCI
advances USB3 ports to Enabled, no reset — spec 4.3), and driving a hot
reset into the live link drops PED mid-reset on real silicon. QEMU
tolerates the spurious reset, which is why the harness never saw it.

An enabled speed>=4 port now skips the reset (a not-yet-enabled SS link
still gets one). Every setup step names its failure — port reset with
the PORTSC value, Enable Slot, device-slot exhaustion, Address Device
with its completion code — so the on-screen transcript of the next
failure identifies the exact xHCI command instead of one blanket line.
block.open's give-up window drops 60 s -> 30 s (the slowest observed
healthy chain completed at ~24 s); a machine whose stick failed setup
should not sit a further minute pretending otherwise.
2026-07-21 19:37:24 +01:00
Daniel Samson f587e7e05e console: page-wrap instead of scrolling — never read the framebuffer
The scroll path copied every pixel row up by one glyph height, READING
video memory — and VRAM reads are uncached-slow on real hardware.
Measured on the 16-core PC: ~90 seconds to bring the cores online,
almost entirely boot-transcript lines each paying a whole-screen scroll
copy. (QEMU never shows this: its 'VRAM' is host RAM.)

When the screen fills, the console now clears and restarts at the top —
writes only, once per screenful. The transcript reads the same as it
streams; only the scrollback illusion is gone, which a boot console
never needed.
2026-07-21 19:22:59 +01:00
Daniel Samson b541921218 build: -Ddiagnose — boot without the display so the transcript stays on screen
The display service claiming the framebuffer suppresses the on-screen
boot transcript — correctly in normal operation, but on a serial-less
machine being debugged, the timeline vanishes just when it matters. A
diagnose image (zig build -Ddiagnose=true) has init skip the display
service and demo: the timestamped transcript stays on screen
indefinitely, and the power button still runs the orderly shutdown (so
the logger's files land when storage works).

First use immediately caught a real bug IN QEMU: an intermittent (~1
in 3) usb-storage READ CAPACITY failure after a ~21 s stall — after
which usb-storage and fat both exit cleanly and nothing retries: one
transient early-boot USB failure leaves the system permanently without
storage (and therefore without logs). That no-retry policy is a prime
suspect for real hardware never mounting /var, and is Track B's first
work item.
2026-07-21 19:15:14 +01:00
Daniel Samson a91365b3d9 kernel: the boot transcript on screen, every line timestamped
Without serial and without working USB storage, a slow real-hardware
boot is undiagnosable — 'stabbing in the dark'. Two changes end that:

The log renderer stamps every line with boot-relative seconds
([  12.045] ...), so every surface — serial, debugcon, and now the
screen — is a readable timeline. And the framebuffer console registers
as an ordinary log sink at boot: kernel AND userspace lines (device
bring-up, fat mounts, logger announcements) show live on screen until
the display service claims the framebuffer, which flips the console's
suppression and silences the sink automatically — the display-owns-the-
screen design is unchanged in normal operation; the console now simply
narrates the part of boot that happens before there IS a display.

On the machine that motivated this, the next boot will show by eye
where the minutes go — including whether the USB chain ever brings
storage up, and whether screen drawing itself crawls (the latent
non-write-combining framebuffer suspect: if these very lines paint
slowly, that's the answer).
2026-07-21 19:09:34 +01:00
Daniel Samson ffa45edc8b boot: the capsule — one-file system image first, manifest and walk as fallbacks
Real-firmware finding: the per-file /system tree walk boots in seconds
under OVMF but stalls for MINUTES on real firmware — the cost is not
bytes (USB 3 moves the ~4 MB instantly) but firmware filesystem
OPERATIONS: ~30 opens, each an uncached directory-chain walk in an
unoptimized firmware FAT driver. This is why every real OS loader
(winload, GRUB) reads many files through its own filesystem code over
Block I/O rather than the firmware's file protocol.

The loader now reads boot/system.img — the bundled binaries packed into
ONE v2 initial_ramdisk (tools/pack-system-image.py, derived from the
same bundled list in the same build graph, so tree and capsule cannot
drift) — with a single open + sequential read, the one firmware file
I/O shape that is fast everywhere. The manifest (open each listed path
by name) and the tree walk remain as fallbacks, so a hand-assembled
stick without the capsule still boots. The running system is identical
in all three cases: the kernel receives the same in-RAM table.

The load phase now brackets itself with unconditional on-screen
breadcrumbs ('EFI: loading the system...' / '...starting the kernel'),
because this phase stalling behind a silent black screen — kernel
status is serial-only by design — already cost a real-hardware
debugging session.

Direction (settled with the user): this capsule becomes the BOOTSTRAP
capsule — kernel + init + the storage-bring-up set — once a
spawn-from-memory syscall lets init and the device manager load
everything else from the stick's real file tree at runtime through
danos's own storage stack: file-granular updates (rebuild one binary,
copy one file), the initramfs shape.
2026-07-21 18:57:33 +01:00
Daniel Samson d446ddd2ed boot: survive hand-written sticks — case-fold the /system walk, skip host litter
The loader ENUMERATES /system now, so it sees whatever the stick's
directory entries literally store — and a hand-copied stick differs
from our generated image: firmware returns bare 8.3 short entries
UPPERCASE (INIT, SYSTEM), and host OSes leave litter next to every file
(macOS '._' AppleDouble forks, .fseventsd). Verified in QEMU/OVMF: an
uppercase-stored volume booted to a dead kernel-only system before this
change and boots fully (display up, logger writing /var/log) after.

The walk now lowers ASCII names (the danos tree is canonically
lowercase; FAT lookups are case-insensitive by definition), skips any
dot-prefixed entry, and treats malformed or unreadable entries as
skip-this-file instead of abort-the-whole-walk. Reader.find compares
case-insensitively as belt and braces.
2026-07-21 18:01:12 +01:00
Daniel Samson 0a4388c3bc Merge branch 'claude/shm-runtime-meaning-4fce7c'
# Conflicts:
#	build.zig
#	system/drivers/virtio-gpu/virtio-gpu.zig
2026-07-21 17:27:39 +01:00
Daniel Samson 0045fd87ba naming: shm → shared_memory — 'shm' is a Unix clipping, not an acronym
Per docs/coding-standards.md (no Unix-abbreviation exception): syscalls
shared_memory_create/map/physical, kernel SharedMemoryObject + handlers,
runtime.shared_memory (library/runtime/shared-memory.zig), the
shared-memory-server/-client test services, the shared-memory QEMU case,
and docs incl. vdso.md's danos_shared_memory_*. 87/87 QEMU tests pass.
2026-07-21 17:26:15 +01:00
Daniel Samson ad0fd52cb8 Merge per-process-logging: tagged ring + logger to /var/log + kernel VFS root
Every process now logs through std.log into a kernel-stamped record
ring, and the logger service persists one file per binary path under
/var/log/<boot-stamp>/ on the flash volume — the real-hardware capture
path (no serial needed). Supporting architecture: the boot medium's
/system tree replaces the packed ramdisk (the EFI loader walks it),
task names are binary paths, FAT creates long file names with a fast
cluster allocator, and the VFS root moved into the kernel
(fs_resolve/fs_node/fs_mount/fs_unmount; resolve + redirect — the
kernel names, userspace filesystems serve). The userspace vfs server
is retired. Full QEMU suite green (88 cases).
2026-07-21 17:13:32 +01:00
Daniel Samson 65a44a5568 docs: per-process logging + the kernel VFS root
logging.md rewritten around the pipeline (std.log -> leveled debug_write
-> tagged ring -> logger service -> /var/log/<boot-stamp>/<binary-path>.log),
why the buffer is a kernel ring rather than a logging server (the
storage stack must log; early boot needs the buffer anyway), the
announce-once and counted-loss disciplines, and the accepted gaps.
vfs-protocol.md: the router is the kernel (fs_resolve names, backends
serve data); endpoint comes from resolve, not a registry lookup; mount/
unmount retired from the wire; the rewrite-prefix mount semantics.
vdso.md: klog_status + the fs naming calls join the future table; the
no-file-I/O line sharpened (kernel resolves names, never blocks on a
filesystem). syscall.md: the placeholder status blurb replaced with the
real call families.
2026-07-21 16:36:27 +01:00
Daniel Samson e186858315 vfs: the root moves into the kernel — resolve + redirect cutover
runtime.fs now routes every path through fs_resolve: kernel-served
/system nodes are read via fs_node (tokens, no open state); everything
under a userspace mount goes straight to the owning backend's endpoint
with the kernel-rewritten mount-relative path — one syscall of naming,
then the unchanged vfs-protocol rendezvous, public API untouched. mkdir/
unlink/rename resolve-then-forward (rename checks both paths land on
the SAME backend); mount is the fs_mount syscall.

The fat server mounts twice — /mnt/usb from the volume root and /var
from its /var subtree — so the logger now writes the FHS path
/var/log/<boot-stamp>/... and swapping the persistent medium later
touches only fat's two mount calls. With clients holding fat's node ids
directly, fat records each handle's owner, checks it, and sweeps a dead
client's handles via the published exit events (the old router's
pattern, now where the state actually lives).

The userspace vfs server and its router die; ServiceId.vfs=1 stays
reserved-retired; protocol.zig moves to system/vfs-protocol.zig (the
wire contract is backend-only now). vfs-test becomes the ring-3 proof
of the kernel VFS (own-binary ELF magic through /system, read-only
refusals, listing); vfs-client-death becomes the fat sweep test over
the full storage chain, with a ring-scanning check (the last-write
buffer is too racy under a chattering tree).
2026-07-21 16:27:06 +01:00
Daniel Samson 7c5645fe48 kernel: VFS root — mount table, /system from the initrd, fs syscalls
system/kernel/vfs.zig is the resolve+redirect router: fs_resolve (#46)
walks the kernel mount table; a path under the kernel-backed /system
mount (the initrd, seeded by setInitialRamdisk with a derived directory
table) yields a permanent stateless node token served by fs_node (#47)
— read/status/readdir with copy-out, initrd reads lock-free — while a
path under a userspace mount yields the backend's endpoint (installed
in the caller's table, DEDUPLICATED so 16 slots can't be exhausted by
repeated resolves) plus the rewritten mount-relative path; the caller
then speaks the unchanged vfs-protocol rendezvous directly. The kernel
never blocks on a userspace filesystem, holds no open-file state, and
refuses create-intent on the immutable /system.

fs_mount (#48) is the syscall form of the old router's op-6 cap-pass
(possession of the backend handle is the capability; an optional
rewrite prefix maps the mount into the backend's namespace — how /var
will reach the flash volume); fs_unmount (#49) removes one. A dead
backend's mount clears lazily on resolve.

Dormant this milestone: the userspace vfs still serves runtime.fs
unchanged; the kvfs QEMU case covers the kernel side (resolution, ELF
magic read-through, /system listing, read-only + unknown refusals)
until the M-G cutover exercises the syscalls end-to-end.
2026-07-21 16:15:05 +01:00
Daniel Samson 127ea2dad9 logger: per-process log files under /var/log/<boot-stamp>/
The logger service drains the tagged ring every 250 ms and demultiplexes
it into one file per process on the flash volume:

    /mnt/usb/var/log/2026-07-21T150434Z/system/services/fat.log
    [     0.214] mounted FAT (fat32, 128992 clusters, partition lba 0)

The boot stamp is the wall-clock anchor from klog_status (FAT-safe, no
colons); the kernel's records go to kernel.log; each line carries the
record's monotonic timestamp and level. Storage is best-effort and
late — the ring buffers a whole boot until the mount appears, then the
first drain writes the backlog, storage-stack records included. Lost
records surface as '-- N records lost --' from sequence gaps. Files
close (= fat's device cache flush) after a 2 s quiet period, bounding
data-at-risk without per-record flush thrash. The logger announces
itself once — a periodic status line would feed the stream it drains.

init spawns the logger last, so the reverse-order shutdown stops it
first and its final drain runs over a live storage chain; log-flush and
init's own DANOS.LOG shutdown flush retire (superseded). runtime.fs
makePath treats components at or above a mount point as router names —
create is best-effort per prefix, the final verdict is exists(path).
2026-07-21 16:05:46 +01:00
Daniel Samson 30d6ea622a fat: long-file-name creation, fast cluster allocation, makePath
createFile/createDirectory now build long-name chains: a mangled STEM~N
8.3 alias (collision-checked per directory), the standard rotate-add
checksum, and 13-UCS-2-per-entry pieces written last-logical-first into
a contiguous free-slot run (found sector-wise, growing the directory as
today). Write order is chain first, 8.3 entry last, so an interrupted
create leaves only skippable orphans. Uppercase-compliant 8.3 names
keep the bare-entry fast path; lowercase names now get a chain so their
exact case survives — matching tools/make-fat-image.py. rename stays
8.3-only (documented; nothing needs more yet).

allocateCluster drops its from-cluster-2 rescan (measured ~1 s/cluster
on a part-full volume, a 37 s shutdown flush in the lost M19 build):
a next-free hint (rewound by frees) plus a FAT-sector LBA cache turn
the scan into one device read per FAT sector.

mkdir now refuses an existing name (no duplicate entries), and
runtime.fs gains makePath (mkdir -p) for the logger's nested per-boot
directories. Engine tests cover the log-directory shape, ~N collisions,
chain unlink/reuse, the 8.3 fast path, and the checksum.
2026-07-21 15:59:06 +01:00
Daniel Samson d0c1b3e45b kernel: serialize test markers through the log lock; render one write per line
tests.zig wrote markers straight to serial, racing user-process records
rendered on other cores — visible as 16-byte UART-FIFO interleave once
the tagged renderer emitted several writes per record. Markers now go
through kernel log print (same serial sink, now under the log lock), the
renderer composes each line into one buffer and hits each sink once, and
the vfs-client-death needle drops the old self-written 'vfs: ' prefix.
2026-07-21 15:53:45 +01:00
Daniel Samson f480c5d790 runtime: std.log for every user binary — kernel-stamped attribution
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.
2026-07-21 15:39:51 +01:00
Daniel Samson 9f18d8340e kernel: tagged log ring — per-line pid/name/level records, klog_status
Replace the linear keep-earliest RAM buffer with a 512 KiB ring of
framed records (log-ring.zig, host-tested): every debug_write becomes
one record per payload line, stamped by the kernel with the sender's
pid, task name (its binary path), level, per-boot sequence number, and
monotonic timestamp. Attribution is structural — a payload cannot forge
another sender's tag, and newline injection lands inside the forger's
own next record. Oldest records are overwritten when full; sequence
gaps make the loss countable.

debug_write gains a level argument (err/warn/info/debug/raw; old
two-arg callers clamp to raw). klog_read becomes a stream-offset read
that fails once the cursor falls behind the ring's tail; the new
klog_status (#45) returns the cursors plus the boot wall-clock anchor —
what the logger service will name per-boot log directories with.

The log now guards itself with a dedicated spinlock (BKL -> log lock
order, never the reverse); panic paths use a bounded try-acquire and
fall back to sinks-only. Serial rendering keeps the historical
transcript byte-identical for kernel and legacy raw output; leveled
records get a kernel-rendered name prefix. log-flush/init's interim
drains start at the ring tail and write framed records until the logger
service replaces them.
2026-07-21 15:30:28 +01:00
Daniel Samson 0a84e52bf8 tests: match task names by basename; path-tolerant harness regexes
processRunning compared literal names against task names that are now
full binary paths; the acpi-ps2/usb-report harness regexes assumed
unprefixed driver names in device-manager spawn/restart lines.
2026-07-21 15:22:58 +01:00
Daniel Samson 1cf9985da6 boot: fix ramdisk paths lost to FAT name uppercasing; widen driver-name buffers
The EFI loader now ENUMERATES /system rather than opening files by name,
so the on-disk name must be exact, not merely case-insensitively
findable. make-fat-image.py stored 8.3-fitting lowercase names as bare
uppercase short entries ('init' -> INIT), garbling the ramdisk paths;
now any name whose case the 8.3 entry cannot reproduce gets a long-name
chain carrying the exact name.

device-manager's driver table stored names in 24 bytes — silently
truncating '/system/drivers/usb-xhci-bus' and failing the spawn; widen
to 64 (= abi.maximum_process_name). The usb-report harness regex learns
that restart lines name drivers by path.
2026-07-21 15:13:19 +01:00
Daniel Samson 2d0858caf6 boot: the /system tree is the system image — loader-built ramdisk, spawn by path
Retire the build-time ramdisk packer and the packed initial-ramdisk.img.
make-fat-image.py now lays every user binary out at its FHS path on the
boot volume (system/services, system/drivers, system/tests), and the EFI
loader walks \system at boot, packing what it finds into an in-RAM v2
initial_ramdisk whose entry names are full FHS paths. init rides the
table like every other binary: its dedicated handoff fields are gone and
the kernel spawns PID 1 via the same lookup as everyone else
(process.spawnBundled).

system_spawn resolves names by exact path first, then unique basename,
and normalizes argv[0] to the stored path — so task names (and, next,
the tagged log ring's attribution) are honest binary paths everywhere.
initrd v2 rejects the old magic so a stale image fails loudly.

Groundwork for per-process logging (/var/log/<boot-stamp>/<binary-path>.log)
and the kernel-VFS /system mount.
2026-07-21 14:47:26 +01:00
Daniel Samson ec6e888076 display: pace the frame clock by the panel's EDID refresh rate
Both EDID moments the system has are now captured and carried to the
compositor's frame clock:

- EFI: the loader derives refresh from the preferred detailed timing
  (pixel clock / total pixels) while GOP is still alive — the only moment
  it is readable — and hands it through the boot handoff into the
  display0 node's DisplayInfo (new refresh_hz field, 0 = unknown).
- GPU: the virtio-gpu driver derives the same figure from its own EDID
  read and carries it in the attach_scanout announce (request.y).

updateFrameClock() re-derives the interval from the active backend's
info at bring-up and again on every backend change — the boot
framebuffer's clock dies with the GOP floor at upgrade, replaced by the
GPU's rate. Unknown rate defaults to 60 Hz; the result is clamped to
[30, 120] Hz so a mis-parsed EDID can neither starve nor flood the
compositor. Rate only, never phase: without vblank, presents still
free-run (docs/display-v2.md, 'Fenced is not vsync').

Observed in QEMU: OVMF exposes no EDID for the VGA adapter, so the GOP
floor logs 'frame clock 62 Hz (default)' (real firmware does expose it);
the virtio-gpu EDID advertises 75 Hz and the upgrade logs 'frame clock
76 Hz (panel EDID)'. The display kernel test asserts refresh_hz rides
the seeded node; all 7 display QEMU cases pass.
2026-07-21 11:37:29 +01:00
Daniel Samson 4f02f75602 display: a ~60 Hz frame clock — presents are scheduled, not immediate
Client 'present' requests and cursor pokes no longer repaint on the spot:
they accumulate damage and arm a one-shot 16 ms timer, and the tick
composites everything pending as one frame. A fast mouse previously
turned every input event into a full present (100+/s); now any number of
draws and moves inside one interval coalesce into a single repaint.

No backend has a real vblank to pace by (docs/display-v2.md, 'Fenced is
not vsync'), so this is the software stand-in — the same strategy Linux
uses atop virtio-gpu. Bring-up paths that need pixels synchronously
(initialise, self-checks) still present directly.

onNotification now handles the message and timer badge bits
independently: one coalesced badge can carry both, and the old
either/or dispatch would have dropped a tick.

All six display QEMU cases pass.
2026-07-21 11:26:49 +01:00
Daniel Samson 16618d2cdc display: stop calling the fenced present 'vsync' — it isn't
The virtio-gpu present fence completes when the device has consumed the
frame: real completion feedback, and tear-freedom by snapshot semantics.
It is not a vblank — base virtio-gpu 2D has no display-refresh event at
all (Linux fakes one with a timer), so nothing paces presents to the
monitor. The code and docs claimed vsync anyway; now they don't.

- backend.hasVsync -> hasFencedPresent, with an honest doc comment
- marker 'display: vsync present ok' -> 'display: fenced present ok'
  (display-modeset test expectation updated, passes)
- display-v2.md gains a 'Fenced is not vsync' note; the vsync claims in
  both v2 docs are corrected
- true vsync arrives with a native driver's vblank IRQ, or approximated
  by a compositor frame clock
2026-07-21 11:24:23 +01:00
Daniel Samson 15107f54be build: run-x86-64-gpu — the interactive twin of the display-native test
Boots the usual VGA/GOP floor plus a virtio-gpu-pci adapter, so the
device-manager stack spawns the driver and the compositor upgrades to the
native fenced-present backend interactively. QEMU shows one head per
adapter: the live output is the virtio-gpu console in the View menu (the
VGA head freezes at the moment of upgrade). 512M, matching display-native.
2026-07-21 11:22:07 +01:00
Daniel Samson 23f915c593 display: damage-rect list + tile-grid trackers, vectorizable pixel loops, wide WC stores
Tearing mitigation for the GOP floor, attacking the copy window from three sides:

- Damage is no longer one bounding box. Two trackers, A/B-switchable at
  compile time (display.zig damage_mode): DamageList (free-form dirty rects,
  overlap-merged) and TileGrid (fixed 64-px tiles, exact O(1) marking, runs
  coalesced back into rects). Far-apart changes — the cursor here, an
  animating layer there — no longer unite into one huge repaint.
- fillRect/composite/blitTile now work in row spans (@memset/@memcpy), so
  the compiler vectorizes them and ReleaseSafe bounds checks drop to per-row.
- The back->front present streams 8-byte volatile stores (presentSpan);
  Backend.present takes the rect list, so each present copies only what
  changed, faster.

Host tests cover both trackers; the display QEMU cases all pass.
2026-07-21 11:22:00 +01:00
Daniel Samson e5dcc9790b started os developer guide 2026-07-20 18:43:24 +01:00
Daniel Samson f8ad4ac971 update README.md 2026-07-20 13:39:03 +01:00
Daniel Samson 914af52b94 build: release-x86-64 — the flashable hybrid ISO (Etcher/dd + optical), built in-repo 2026-07-20 13:37:10 +01:00
Daniel Samson bdd8a48476 docs: vdso + vfs-protocol — the public ABI boundary, linked from the index 2026-07-20 13:36:50 +01:00
Daniel Samson f309ce04f4 removing the need for panic and _start snippets in user space binaries 2026-07-17 16:15:30 +01:00
Daniel Samson 9989ebbec7 moving mouse cursor 2026-07-17 10:48:23 +01:00
Daniel Samson 626e3c5e9b docs: link the NVIDIA + Intel GPU feasibility studies from the index
Point the display entry (#19) at nvidia-gpus.md and intel-igpu.md as future native scanout backends.
2026-07-14 18:40:59 +01:00
Daniel Samson 2c63c76288 assets: add font families under usr/share/fonts
Lexend, Lucide, Noto Serif, and Open Sans, staged for upcoming GUI/text work on
the display stack. Asset-only — not yet referenced by any code.
2026-07-14 18:39:23 +01:00
Daniel Samson 05fc1764de docs: native GPU driver feasibility studies (NVIDIA + Intel)
Two companion research snapshots on what a minimal display-only native driver
(EDID + mode-set + framebuffer scanout, no acceleration) would take as a danos
.scanout backend, and how the two vendors compare.

- nvidia-gpus.md — RTX 3060 (Ampere GA106). Display is not GSP-gated: nouveau's
  ga102.c has a direct register path, and danos's GOP boot lets a driver attach to
  a live, already-devinit'd display. Tier-4 effort; the register-level display code
  lives in GPL nouveau while the permissive OGKM reference is the hard GSP path.
  GA10x is the last NVIDIA family that keeps a register-level display path.
- intel-igpu.md — the companion, and a materially easier, lower-tier target. Intel
  publishes register-level Display Engine PRMs, so a clean-room permissive driver
  is viable; the DMC microcontroller is optional (power states only), scanout comes
  from system RAM via the GGTT (no VRAM manager), and coreboot's libgfxinit is a
  compact native reference. The catch is hardware: an iGPU may not drive the
  monitor on a discrete-GPU desktop.

Sourced, cited snapshots — not implementations; each ends with a "first light"
milestone ladder framed as a danos .scanout service. Not yet linked from
docs/README.md.
2026-07-14 18:39:23 +01:00
Daniel Samson 65bb04d890 kernel: preserve SSE/FPU (XMM) state across the syscall/interrupt boundary
The kernel enabled SSE at boot and both kernel and userspace keep live values in
XMM (LLVM emits movdqu/movaps for >=16-byte struct copies, plus floats and SIMD),
yet the kernel never saved the SSE/FPU register file anywhere — not across
switch_context, not across the syscall boundary, not across interrupts. Any value
the compiler parked in an XMM register across a kernel entry could be silently
clobbered by kernel code, or by whatever the scheduler ran while the task blocked:
a whole-kernel, timing-dependent data-corruption bug.

It was the real root cause of the "device_enumerate corruption" Heisenbug: the
display service read garbage framebuffer geometry (height=0) because findDisplay
held the 16-byte .display field live in xmm0 across the claim/mmio_map syscalls,
and a timer preemption to the busy device-manager clobbered it. The tell that it
was register-only: memory always read correct, and the bug vanished whenever an
added syscall spilled the value to the stack.

Fix: fxsave/fxrstor the register file in the asm stubs (isr_common and
syscall_entry), right after pushing the GP trap frame — before any Zig kernel code
can touch XMM — and right before the pops. rbx bridges the exact rsp across the
call to interruptDispatch: it is callee-saved, so it survives even a blocking
dispatch that context-switches away and back, and `and $-16,%rsp; sub $512,%rsp`
gives fxsave its 16-byte-aligned scratch on the kernel stack. Context-switch-time
save/restore alone is not enough — kernel code between the interrupt and switchTo
already clobbers XMM.

The commit-58927ed Gop.init workaround (copy scalar geometry fields rather than the
whole descriptor by value) is now redundant but harmless; left in place. Deferred:
a fresh task inherits the previous task's XMM (minor info-leak / nondeterminism),
and the fxsave runs on every interrupt including ring0->ring0.
2026-07-14 18:39:23 +01:00