Commit Graph

23 Commits

Author SHA1 Message Date
Daniel Samson 69b018cc32
display: the compositor service — claim, double-buffer, present (D2)
Stand up /system/services/display: a ring-3 process that claims the framebuffer
D1 seeded, maps it write-combining as the front buffer, allocates a cacheable
back buffer, and presents composed frames. The GUI track's compositor, reached
by name over ServiceId.display (= 9).

- protocol.zig: the display wire protocol (info/create_layer/configure_layer/
  destroy_layer/fill_rect/blit_tile/damage/present). `info` and a whole-screen
  `present` are live; the layer ops fail-stub until D3.
- display.zig: enumerate -> claim -> mmio_map(WC) the LFB, mmap a cacheable back
  buffer, clear it and present it (proving the double-buffer path), then serve.
- runtime.display + barrel exports (display, display_protocol): a cached
  `.display` client with info()/present(), the runtime.block shape.
- init spawns "display" in boot_services; build.zig wires the protocol onto the
  runtime, builds the exe, packs it into the initial-ramdisk, installs it.

mmap fix the back buffer forced: systemMmap was capped at 256 pages (1 MiB) by a
fixed kernel-stack scratch array. Rewrote it to map page-by-page with rollback
(no array) and raised the cap to 8192 pages (32 MiB) — enough for a 4K back
buffer. A real limitation met.

Gate: `python3 test/qemu_test.py display-service` matches the service's own
serial heartbeats (display: online WxH / presented frame 0), printed only after
the full claim -> WC-map -> back-buffer -> present chain. Regression-checked
usermem, heap, init, and D1's display.
2026-07-14 01:26:25 +01:00
Daniel Samson 8a38540312
Phase 2d (i): a kernel wall-clock from the CMOS RTC
Adds real (calendar) time, the foundation for filesystem timestamps. Monotonic
time (`clock`) says how long since boot; this says what time it actually is.

- cpu.zig (x86_64): readRtcUnixSeconds() reads the CMOS real-time clock (ports
  0x70/0x71) — waits out an update-in-progress, reads twice until stable, handles
  BCD-vs-binary and 12-vs-24-hour per status register B — and converts to Unix
  epoch seconds (UTC).
- kernel/wall-clock.zig: reads the RTC once at boot and anchors it to the monotonic
  clock, so a query is a cheap arithmetic offset — no per-call CMOS poll, no lock,
  no SMP hazard on the shared ports. kmain calls init() once the monotonic clock is
  final and logs the epoch.
- wall_clock() syscall (33) -> Unix epoch seconds, wrapped by runtime.system
  .wallClock(). Wall-clock *seconds* are mechanism the kernel owns like the
  monotonic clock; calendars/timezones are user-space policy (the stale comment on
  systemClock that called wall-clock a "user-space service" is updated in spirit by
  the new handler's doc).
- A `wall-clock` kernel test asserts the boot RTC read is a plausible current epoch.

Verified against the host: the guest read epoch 1783971244 while `date -u +%s` gave
1783971245 (one second of boot lag) — the CMOS read + epoch conversion are correct to
the second. zig build, zig build test, and smoke/clock/init are green.
2026-07-13 20:35:03 +01:00
Daniel Samson f52c591f5e
Persist the kernel boot log to the USB FAT volume
On a headless or real board nothing captures serial, so the boot log — the whole
diagnostic stream — is lost at power-off. This retains it in the kernel and copies
it to the boot USB volume as /mnt/usb/DANOS.LOG, the on-disk equivalent of QEMU's
`-serial file:`. Pull the stick, read DANOS.LOG on another machine.

How it fits together:
- Kernel RAM sink (log.zig): a fixed 256 KiB in-image buffer registered as a log
  sink in kmain, right after serial. Because userspace debug_write funnels through
  log.write, it captures the entire stream — kernel lines and every service's
  output — from the first line. Fills linearly and stops when full (earliest boot
  output, the most valuable, is kept); no allocation, so it is panic-safe.
- klog_read syscall (32): copies that buffer out to a user buffer, the mirror of
  debug_write — same overflow-safe user-half bounds check, kernel -> user copy,
  under the kernel lock so the snapshot can't grow mid-copy. Wrapped by
  runtime.system.klogRead.
- log-flush (new one-shot, in the initial-ramdisk): waits for the fat server to
  mount /mnt/usb, then copies the whole log to /mnt/usb/DANOS.LOG. init spawns it
  once the boot services are up (fire-and-forget; it polls the mount itself). If
  no volume is mounted — no stick, or the no-VFS ramdisk sweep — it exits silently.
- init shutdown flush: init repeats the copy inline at the top of shutDown(),
  BEFORE it tears down the storage services (the fat server is stopped first), so
  a clean poweroff captures the fullest log while /mnt/usb is still writable.
- unistd.writeAll: loops write() past the 224-byte VFS payload cap; both flush
  paths use it.

The filename is 8.3 (DANOS.LOG) at the mount root — the FAT short-name rule, and
there is no mkdir on the FAT path yet. Extend-only writes mean the two same-session
flushes never leave stale bytes (the shutdown log is a superset of the boot log);
a shorter log on a later boot of the same stick can leave a stale tail — a noted,
cosmetic limitation, not worth pulling O_TRUNC into the FAT write path for now.

Verified end to end under QEMU: a new `log-flush` case (reusing the orderly-shutdown
build) asserts both markers then S5, and DANOS.LOG is read back out of the image
afterwards (11804 bytes, containing the kernel init line, the FAT mount line, and
the boot flush marker). Regression stays green: zig build, zig build test, zig
build check-fat-image, and a sequential QEMU sweep — smoke, init, initial-ramdisk
(log-flush silent in the bare sweep), orderly-shutdown, fat-mount, usb-storage,
usb-hid, vfs, input, device-manager, process, signals, dma, fault-pf.
2026-07-13 18:18:34 +01:00
Daniel Samson a64a01a6a9
M6: FAT read/write filesystem server, mounted into the VFS
Add a FAT12/16/32 filesystem the VFS mounts at /mnt/usb, reading and writing a
USB stick through the block device. Verified end to end under QEMU: the fat
server mounts the volume, the VFS routes /mnt/usb to it, and a client lists the
root and reads a file (the ELF magic of /mnt/usb/system/kernel).

- engine.zig: the FAT engine over a BlockDevice interface — mount (a bare FAT or,
  as QEMU's VVFAT and most real sticks present it, an MBR-partitioned disk), FAT
  chain walk (12/16/32), cluster allocation, directory traversal with long-name
  read, and file read / write / create. Host-tested against a RAM-backed FAT16
  image (create, cluster-spanning write, mid-file overwrite, read-back, list).
- on-disk.zig: the align(1) boot-sector / directory / long-name / FSInfo structs
  and the cluster-count FAT-type detection.
- fat.zig: the server — wraps the .block device (a DMA bounce buffer) in a
  BlockDevice, mounts the FAT, serves the vfs-protocol as a backend, and mounts
  itself into the VFS at /mnt/usb. Spawned by init as a boot service.
- runtime.block: the block-device client (geometry / read / write by physical
  address, so whole sectors never cross IPC).
- Raise the kernel service-name registry (maximum_services) 8 -> 16: it is
  indexed directly by ServiceId, and fat = 8 was being rejected, so the fat
  server exited before registering.
- VFS: an absolute path with no matching mount is now not-found rather than
  silently created in the flat ramfs — so /mnt/usb fails cleanly until mounted.

Tests: fat-mount (the full stack: block -> FAT -> VFS mount -> list + file read)
passes; host units cover the engine and on-disk structs; the vfs, shutdown, and
USB regression suite stays green (10/10).
2026-07-13 15:05:53 +01:00
Daniel Samson 3fb9d5936a
USB driver stack: xHCI transfers, HID keyboard/mouse, mass storage
Flesh out the xHCI host-controller driver into a full transfer engine and build
the three USB class drivers on top, all verified end to end under QEMU.

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

Tests: usb-report, usb-hid, usb-storage pass under python3 test/qemu_test.py;
host units (usb-abi, usb-ids, hid-report, bulk-only-transport, scsi) green.
2026-07-13 14:11:00 +01:00
Daniel Samson 452080e997
Add runtime.time, drop demo drivers, harden TSC timekeeping
Time is a kernel concern in danos: the kernel owns the scheduling timer and
already exposes monotonic time via the clock/sleep/timer_bind syscalls, so a
userspace time service would be a redundant, slower path. This adds the generic
runtime.time module over those syscalls, retires the two demonstration drivers,
reorganizes the milestone docs, and makes the monotonic clock correct on Intel,
AMD, and inside any VM.

runtime.time (library/runtime/time.zig)
- Instant/Duration interface: now, sleep, spin, after, monotonicNanos, available
- a thin layer over system.clock/sleep/timerOnce; unit-tested arithmetic

Remove the demo drivers hpet and bus (a teaching example belongs in the docs,
not shipped in the tree)
- system/drivers/ now holds only real drivers: pci-bus, ps2-bus, usb-xhci-bus
- device-manager end-to-end test repointed to pci-bus (asserts on kernel state:
  the process table and the device tree, not a racy serial marker)
- device_register containment moved to a new in-kernel `containment` test
- the driver-model worked example moved inline into docs/drivers.md

Reorganize milestone docs into topic docs
- m17-m18 / m19-m20 / m21 plans dissolved into process-lifecycle, device-manager,
  discovery, and acpi docs; new docs/power.md and docs/timers.md; ~20 citations
  repointed; plan docs deleted

TSC reliability (apic.zig, smp.zig, cpu.zig, kernel.zig)
- check the invariant-TSC bit (CPUID 0x80000007 EDX[8]) on Intel and AMD
- cross-core "warp" check at SMP bring-up, pairwise BSP<->AP as each core comes up
- fall back to the HPET clocksource when the TSC is not invariant (a bare VM) or
  not synchronized (a warp), switched continuously so time never jumps
- boot log reports the outcome; new tsc-sync test exercises the TSC + warp path

Verified: zig build; zig build test; 60/60 QEMU cases (incl. new containment and
tsc-sync).
2026-07-13 11:56:43 +01:00
Daniel Samson 1f2c60b3ec
The power button, in ring 3: SCI bound, fixed event published (M21.1)
The kernel publishes the FADT as one more acpi-tables memory resource
(tagged by its intact FACP header — the AML blobs are header-stripped);
the acpi service reads the PM1 event/control and GPE register ports from
that copy, so the kernel's own FADT parse is untouched. A power-protocol
module (ServiceId.power = 5, domain-named so an ARM PSCI service can serve
the same id) carries subscribe / shutdown / events.

The acpi service converts to runtime.service.run — device discovery, the
.power protocol, and the SCI notification all fold into one loop. At
startup it enables ACPI mode if SCI_EN is clear (the SMI dance), binds the
SCI (found as the node's len-1 irq resource, distinct from the broad
window), and sets PWRBTN_EN. On the SCI it reads PM1_STS, clears
PWRBTN_STS write-1, logs the press, publishes power_button to
subscribers, and always acks. The power-button scenario proves it with a
real QMP system_powerdown injected mid-run through the M21.0 channel.
2026-07-13 05:38:03 +01:00
Daniel Samson 3cc1d38dd0
The device manager supervises: hello, backoff, and the crash-loop cap (M18.1)
The manager is now a harness service on the well-known .device_manager
endpoint. Every driver spawns supervised; drivers with an assignment must
hello (device-manager-protocol, versioned) within a deadline enforced by
a timer sweep. Exit reasons drive the restart decision: clean exits stay
down, faults restart with 300/600/1200ms backoff, and three fast deaths
mark a driver failed instead of respawning forever. usb-xhci-bus is the
first conforming driver; the crash-test fixture claims a device, hellos,
and faults on purpose — each respawn re-proving claim release on death
through the manager's own path. maximum_tasks grows 16 -> 32: the
initial-ramdisk sweep (15 binaries at once) was intermittently
overflowing the static pool.
2026-07-13 00:19:30 +01:00
Daniel Samson 650a1b1595
Signals over IPC, one-shot timers, and the service harness (M17.4)
Signals are statements delivered as coalescing notifications to the
endpoint a process nominates with signal_bind — never a hijacked stack,
never a question (liveness is the zero-length ping the harness answers).
process_signal is supervisor-or-self gated, like kill; unbound targets
accumulate a pending mask delivered on bind. timer_bind is the missing
timed wait: a one-shot deadline landing in the same replyWait as
everything else — what stop(), hello deadlines, and restart backoff are
built from. runtime.service.run folds requests, signals, and
notifications into callbacks; the VFS conversion deletes its hand-rolled
loop and gains the whole lifecycle contract. The signals scenario drives
ping, reload, terminate->exited, the timer, and the deaf-child
deadline->killed path from ring 3. docs/process-lifecycle.md increments
1-4 are now as-built.
2026-07-12 23:53:38 +01:00
Daniel Samson d8c55c6f2f
Publish exit events to subscribers; the VFS releases dead clients' handles (M17.3)
process_subscribe adds an endpoint to a bounded, ref-counted subscriber
table; every death posts the same badge encoding a supervisor's exit
notification uses, equally late, so subscribers observe a fully-released
child. A dying subscriber's own subscriptions are removed first — it never
hears about itself. The VFS is the first subscriber: open handles now
record their owner and are swept when the owner dies, because a service
must never depend on clients cleaning up after themselves
(docs/process-lifecycle.md). Proven by the vfs-client-death scenario.
2026-07-12 23:41:44 +01:00
Daniel Samson 2ebfb0c3b0
Record and expose how every process ends (M17.2)
The kernel records an ExitReason at all three death sites — clean exit,
fault (classified by vector), and process_kill — into a bounded ring
before the exit notification posts, so a supervisor's query never races
the notice. process_exit_reason is gated by the same supervisor check as
kill; runtime.process.exitReason is the stable interface. This is the
input restart policy reads (docs/process-lifecycle.md iron rule 2).
2026-07-12 23:34:09 +01:00
Daniel Samson 5bba5d3363
Wire real PS/2 scancodes through to input events and characters
Replace the keyboard driver's synthetic stream with the real path:

- ps2-bus binds IRQ1 (interrupt bits set only after the bind), drains
  port 0x60 on each interrupt, and forwards every byte to the attached
  child driver over async ipc_send, routed by the status register's
  auxiliary-output bit. Children attach via the new well-known ps2_bus
  service, handing over their endpoint as a capability.
- scancode.zig (new, pure, host-tested): scancode set 2 -> USB HID
  usage decoding (F0/E0/E1 prefix state machine) plus keyboard state —
  pressed-key bitmap, typematic-repeat classification, modifier and
  caps-lock tracking.
- keyboard.zig decodes the forwarded stream and publishes real
  key_down/key_press/key_up events, filling key_press characters via
  xkeyboard-config (layout from argv[2], default us) and synthesizing
  ASCII control characters for Enter/Tab/Backspace/Escape.
- protocol.zig names the full HID usage set in Keycode; build.zig
  threads the xkeyboard-config module into user binaries.

Verified end to end in QEMU via monitor sendkey: shift, caps lock,
and control-character synthesis all decode correctly. The mouse
driver still publishes its synthetic stream; attaching it to the
bus the same way is the follow-up.
2026-07-11 22:48:59 +01:00
Daniel Samson 65244e3103
Add input module: broadcast keyboard events over IPC
Programs can now subscribe to keyboard events (key_down/key_up/key_press)
and drivers can broadcast them, through a new user-space input service.

The delivery model is forced by danos IPC: a synchronous rendezvous holds
one pending reply, so a server cannot park N subscribers blocked in a
"wait for next event" call — delivery must be push. But a synchronous push
has no timeout and the kernel never wakes a sender parked on a dead peer's
endpoint, so one dying subscriber would hang all input. So this lands the
roadmap's planned asynchronous buffered send and builds the service on it:

- ipc_send (syscall 26): non-blocking post to an endpoint's bounded payload
  ring, delivered through reply_wait as a buffered message (notify_message_bit).
  A full ring drops the oldest. It can never hang on a dead/slow peer.
- input-protocol + runtime.input helpers (subscribe/next, connectSource/
  publish) — the first real consumer of M13 capability passing: a subscriber
  hands the service its own endpoint as a capability.
- input service (fan-out via ipc_send, dead-subscriber pruning), a synthetic
  input-source, and input-test; the ps2-bus keyboard driver publishes to it.
  Real IRQ1 scancode decoding (which must live in the bus, the PNP0303 owner)
  is a documented follow-up; the source is synthetic for now.
- build/init wiring, an `input` QEMU case, and docs/input.md.

Full QEMU suite 48/48, including the new input case and every IPC/endpoint
regression (ipc, ipc-call, ipc-cap, vfs, hpet, bus, irqfree).
2026-07-11 15:03:24 +01:00
Daniel Samson d218d93f79
Add process management: enumerate, supervisor-gated kill, exit notifications
process_enumerate snapshots the task table (the device_enumerate shape, so
ps is a user program); system_spawn returns the child id, records the caller
as supervisor, and takes an exit endpoint; process_kill is allowed only for
the supervisor. Every death — exit, fault, or kill — posts a child-exit badge
to that endpoint (the IRQ-as-IPC pattern as SIGCHLD). A target caught off-CPU
is reaped in place; a running one is condemned and finished at its next
system call or tick, guarded so teardown never lands mid-kernel-operation.
Tested by process-list, process-kill, and supervision (a ring-3 supervisor
exercising the whole surface); design notes in docs/process-management.md.
2026-07-11 09:32:25 +01:00
Daniel Samson 59104dd988
refactor 2026-07-11 04:32:17 +01:00
Daniel Samson e499f500c3
adding clock system call 2026-07-11 03:43:33 +01:00
Daniel Samson 3ec2d1828a
docs: abi is the kernel↔runtime contract, not spoken by applications
Correct the abi.zig header (and the matching build.zig / README lines): the syscall ABI
is the private contract between the kernel and the runtime library, not something every
user program speaks. danos applications call the `runtime` (the stable, danos-native
ABI); the runtime is the only thing that issues system calls, and POSIX layers over the
runtime — the same split as libSystem on macOS or win32 over the NT syscalls. The call
numbers here are an implementation detail the runtime hides and may renumber, not a
public interface.
2026-07-10 20:38:41 +01:00
Daniel Samson 21657943a8
Port I/O grants (io_read / io_write) + refresh stale driver docs
Ring 3 still has no direct in/out (no TSS I/O bitmap, IOPL never raised — a #GP), but a
driver no longer needs it: io_read(device_id, resource_index, offset, width) and
io_write(..., value) grant port access the same way mmio_map grants memory. The claim
plus the device's discovered io_port resource are the capability — resolveIoPort checks
the device is claimed by the caller, the resource is io_port, and [offset, offset+width)
stays inside it, then issues the in/out via architecture.pioRead/pioWrite. So a PS/2 or
16550 driver is now writable; the low-rate legacy hardware that needs port I/O is fine
with a syscall per access. io_port resources were recorded by discovery and ignored —
now they're used. Runtime: device.ioRead/ioWrite.

New `ioport` test claims QEMU's PS/2 controller (io_port 0x64, discovered via ACPI) and
checks the gate admits an in-range access, refuses over-wide / out-of-range / unclaimed,
and that the kernel actually reads the status port (0x1c). Suite 41/41 plus host tests.

Docs refreshed for the whole M13-M16 + port-I/O reality: drivers.md "Limits" no longer
lists port I/O, DMA memory, or barriers as missing (they exist) and its "what's next"
reflects that; the FSH doc's character-device and block-device sections are corrected
("cannot host a block driver at all" is no longer true — writable now, not yet memory-
safe pending IOMMU enforcement); device-interrupts.md unblocks the keyboard and narrows
the interrupt gap to MSI-X.
2026-07-10 20:36:14 +01:00
Daniel Samson 4ef21fa083
M15: interrupts for PCI devices (ECAM config space + MSI)
Two parts, both blockers for real PCI drivers.

ECAM config space per function: enumeratePci now gives every pci_device its own 4 KiB
configuration window as resource 0. A claimed PCI driver mmio_maps that to reach its
command register, BARs, and — the point — its capability list (MSI/MSI-X, PCIe extended
caps), with no new syscall. Verified in the discovery test (QEMU q35's functions each
carry it).

MSI: msi_bind(device_id, endpoint) -> address (rax), data (rdx) allocates a per-device
edge-triggered vector, binds it to the endpoint, and returns the (address, data) the
driver programs into its own MSI capability. Unlike irq_bind there's no GSI, no I/O APIC
entry, no sharing, and no ack cycle — dispatch recognises an MSI vector (vector_gsi ==
none, msi_bound set), EOIs, and notifies. Owner-keyed release drops the binding on exit.
Legacy INTx (_PRT parsing + shared lines) is deliberately skipped; MSI is the real
answer.

QEMU's HPET has no MSI, so the new `msi` test proves the vector-routing path with a
self-IPI (new apic.selfIpi) standing in for the device's MSI write: bind a vector,
fire it, the bound endpoint is notified. The msi_bind syscall wraps irq.msiBind with
the claim check and lands its first real use with the first PCI driver. Suite 39/39
plus host tests.
2026-07-10 19:59:09 +01:00
Daniel Samson 125a3b4993
M14b: DMA memory (dma_alloc / dma_free)
An HCD programs a bus-master engine: it needs a descriptor ring that is physically
contiguous, at a physical address it knows, uncacheable, and pinned. mmap gives none
of those. Add dma_alloc(len, flags) -> vaddr (rax), paddr (rdx) and dma_free(vaddr,
len): grant contiguous, zeroed, pinned, strong-uncacheable memory in a per-process DMA
arena (PML4[228]) and hand back both addresses.

Pieces: pmm.allocContiguous(count, max_phys) finds a run of contiguous free frames
below a cap (dma_below_4g for 32-bit engines); mapUserDmaInto maps them uncacheable
(PCD|PWT) but WITHOUT device_grant, so unlike an MMIO grant these frames are real RAM
and freeSubtree returns them on teardown — a driver that dies leaks nothing. dma_free
is bounded to the DMA arena so it can never unmap the caller's stack/heap/MMIO.
dma_write_combining is accepted but falls back to coherent (WC needs PAT programming).

Runtime: runtime.dma.alloc/free (a two-return-value stub, like replyWait). New `dma`
kernel test drives the mechanism directly — contiguity, the below-4G cap, coherent
mapping, and reclaim-on-teardown (no leak). The thin syscall wrappers follow the tested
mmap/mmio_map shape and land their first real use with the first DMA driver. Suite
38/38 plus host tests.
2026-07-10 19:43:59 +01:00
Daniel Samson a581712b09
M13: IPC capability passing
ipc_call and ipc_reply_wait grow a `send_cap` argument (r9) and a `received_cap`
return (r8): an endpoint travels alongside a message, installed into the receiver's
handle table. The transfer is a share, not a move — the endpoint's refcount is bumped
and the sender keeps its handle. If the receiver's table is full the call fails
-ENOSPC and the message is NOT delivered (a half-delivered capability is worse than a
failed send); a bad handle fails -EBADF. Both directions carry a cap: a client's call
hands one to the server (seen in the server's replyWait), and the server's reply hands
one back (seen in the client's call return).

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

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

New ipc-cap test (two kernel tasks exercise both directions, each verifying the
endpoint it received is the same object shared, refcount bumped to 2). No class driver
consumes callCap yet — it lands with the first one. Suite 37/37 plus host tests.
2026-07-10 19:23:19 +01:00
Daniel Samson afbf10f7fc
Device manager (increment 2): system_spawn — actually start the driver
Add a `system_spawn(name)` system call: the kernel loads a binary bundled in the
initial-ramdisk, by name, as a fresh ring-3 process. It's the mechanism a user-space
supervisor needs — discovery and policy stay in user space, the kernel only spawns.
The kernel already holds the initial-ramdisk image from the boot handoff; it now
stashes it (process.setInitialRamdisk) so the handler can resolve names, bounds-checks
the name into the user half like debug_write, and returns -1 for an unknown name or a
load failure. Ungated for now (any process may spawn any bundled binary); a spawn
capability belongs here once the model grows one.

The device manager stops logging "would spawn it" and calls runtime.system.spawn on
its matched driver. On QEMU it discovers the HPET, matches `hpet`, and spawns it — and
the driver comes all the way up (claims the timer, maps its MMIO, binds and services
its IRQ, prints "hpet: ok"). The device-manager test now keys on that final marker:
since only the manager is spawned, `hpet: ok` appearing proves the whole
discover -> match -> system_spawn -> driver-up chain end to end.

Transitional: the kernel still auto-spawns the whole initial-ramdisk at boot, so a
real boot briefly double-spawns hpet (the second claim fails harmlessly). Increment 3
removes that redundancy so the manager is the sole owner of driver spawning. Suite
36/36 plus host tests.
2026-07-10 18:24:46 +01:00
Daniel Samson be81394be3
Split the `system` contract into boot-handoff / abi / device-abi
The `system` module (formerly `danos`) had become a grab-bag: it held the
loader<->kernel handoff *and* the kernel<->user ABI *and* the device wire types, in
one module three different audiences imported. Usage proved the seam — the
bootloader never touched the syscall/device ABI, and user space never touched the
boot handoff — so split it by audience, one module per contract:

  system/boot-handoff.zig       loader <-> kernel: BootInformation, Framebuffer,
                                MemoryMap, the VM layout + physicalToVirtual, kernel_abi
  system/abi.zig                kernel <-> user, core: SystemCall, mmap prot flags,
                                page_size, notify_badge_bit, ServiceId
  system/devices/device-abi.zig kernel <-> user, devices: DeviceDescriptor,
                                DeviceClass, ResourceDescriptor, ResourceKind, ...

device-abi is the devices sub-project's public interface, exposed as its own module
the way vfs exposes vfs-protocol — importable by user space, unlike the
kernel-internal device model it also feeds. That collapses a real duplication:
DeviceClass and ResourceKind were defined twice (device-model.zig and the contract,
kept "in sync by hand"); device-model now re-exports them from device-abi, so the
enum a driver matches on and the one the kernel classifies with are one type.

Each import now declares which contract it speaks: the bootloader imports only
boot-handoff; a driver only abi + device-abi (via the runtime); the kernel all
three. This also retires the `system` / `runtime.system` name overlap. page_size
lands in abi (it's part of the mmap contract user space aligns to); the bootloader
keeps its own local 4 KiB constant so it depends on nothing but the handoff.

All 21 importers rewired, docs updated to keep /system mapping to source. Build,
host tests, and the QEMU suite (36/36) all green.
2026-07-10 18:08:51 +01:00