A user-space process can now touch real hardware directly, capability-gated by
the device tree — the microkernel driver model.
- src/kernel/devsvc.zig: flattens the discovered device tree into an
id-indexed snapshot + a claim table at boot (devsvc.init from main.zig).
- Syscalls 11-13: dev_enumerate (snapshot the table), dev_claim (take
exclusive ownership), mmio_map (map a claimed device's MMIO window into the
caller's AS and return the register base). The claim is the capability:
mmio_map refuses any device the caller doesn't own.
- paging.mapUserDeviceInto: maps device MMIO strong-uncacheable (PCD|PWT) and
marks each leaf with a device_grant PTE bit; freeSubtree skips pmm.free on
those leaves, so tearing down a driver never returns MMIO frames to the RAM
pool (the teardown hazard). MMIO grants live in a distinct arena, PML4[226]
(Task.dev_map_next), so device pages widen no kernel mapping.
- lib/dev.zig: user enumerate/claim/mmioMap wrappers; shared DeviceDesc/ResDesc
in danos (root.zig). sbin/hpetd.zig: finds the HPET, claims it, maps its
registers, enables the counter (an MMIO write) and reads it (0xF0) — proving
read+write passthrough to real hardware.
- Tests: `hpet` (driver reads the counter advancing from ring 3) and `iopass`
(device-granted frame survives address-space teardown). Suite 33/33.
irq_bind/irq_ack (IRQ-as-message) are stubbed (-1) pending; notifyFromIsr (M7)
is the hook they'll use.
Ship more than one user binary: the bootloader now ferries an initrd bundle
(the VFS server + future drivers) alongside sbin/init, and the kernel unpacks
and spawns each program.
- src/user/proto/initrd.zig: the container format (Header{magic,count} +
Entry{name[32],offset,len} + blobs) with a validating Reader, shared by the
kernel and the packer.
- tools/mkinitrd.py: host-side packer (Python — trivial format, and sidesteps
the reworked Zig 0.16 std fs/args API). build.zig runs it on the built user
binaries via addSystemCommand and installs initrd.img to zig-out/bin + the
ESP root.
- BootInfo gains initrd_base/initrd_len; efi.zig loadInit refactored into a
shared loadFile, and loadInitrd ferries \initrd.img into surviving
LoaderData like init.
- main.zig startInitrdBinaries(): parse the image, spawn every entry (kernel-
spawns-all for now; init takes over via sys_spawn later).
- sbin/vfs.zig: a heartbeat stub (the real VFS server is M9), packed into the
initrd to prove the pipeline.
- New `initrd` test: parse + spawn + confirm the vfs stub reaches ring 3 and
heartbeats. Harness ships initrd.img on the ESP. Suite 30/30.
The microkernel message backbone the VFS server and drivers will ride on.
- src/kernel/ipc_sync.zig: Endpoint (sender FIFO threaded via Task.next +
a recv WaitQueue for servers + a small notification ring). call() (client
sends, wakes a server, blocks) and replyWait() (server replies to the held
caller, then receives the next). Reply routing keys on Task.ipc_client —
synchronous IPC owes one reply at a time. Payloads copy frame-to-frame
through the physmap (copyAcross on arch.translate, added in M5); an unmapped
page fails the copy instead of #PF-ing. MSG_MAX 256.
- Bootstrap naming: an integer name registry (danos.ServiceId, vfs=1) with
create_endpoint / ipc_register / ipc_lookup — any process finds a server
without threading a handle through spawn.
- notifyFromIsr(): ISR-safe async wake (badge with the high bit set), the
hook M10's IRQ-as-message needs. Unused/untested until then.
- Task gains handles[16] (opaque *Endpoint, to avoid a sched<->ipc import
cycle) + ipc_client/send/reply/status fields; sched gains
blockCurrentLocked/readyLocked; arch gains setSyscallResult2 (rdx badge).
- Syscalls 6..10 wired in process.zig; exit() now drops the caller's endpoint
refs. lib/ipc.zig: user-side createEndpoint/register/lookup/call
(server-side replyWait lands with the first server in M9).
- New `ipc-call` test: two kernel tasks ping-pong 100 calls, every reply
request+1. Suite 29/29.
Start the user-space driver track (VFS + IPC + heap). This lays the
process/syscall foundation the runtime heap will grow on.
- Rename usermode.zig -> process.zig; drop the retired hello/ping blob and
its `user` test (subsumed by the real /sbin/init exerciser). Keep the
isolation-proof pf blob and the user-pf test.
- Add danos.Syscall as the single source of truth for syscall numbers, shared
by the kernel dispatcher and (later) the user runtime lib. Dispatch on the
enum. New calls: 1=yield, 4=mmap, 5=munmap. Widen debug_write's bounds
check to the whole user low half so heap buffers are writable.
- mmap grants zeroed RW+NX pages from a per-process bump arena
(Task.heap_next, PML4[224] above image+stack); munmap frees the frames.
Add paging.translateIn / unmapInto (+ arch.translate / unmapUserPageInto)
as the primitives munmap and future cross-AS copies need.
- New `usermem` test: grant three pages into a fresh AS, translate them,
release via the munmap path, tear down, and assert no frames leak.
Suite 28/28 (user -> usermem).
Add the higher-half layout constants (physToVirt/virtToPhys, physmap
and kernel bases) to the danos handoff module, and KernelSegment.phys
so the loader records where it actually placed each segment.
The loader now builds its own page tables before ExitBootServices —
identity + a physmap of low RAM (2 MiB pages) plus 4 KiB mappings for
any higher-half kernel segment — and switches CR3, then calls the entry
with boot_info in RDI, interrupts off. The kernel still links and runs
low (identity), oblivious; this proves the CR3 dance in isolation.
Suite 27/27.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ring 3 works: user GDT descriptors (sysret-ready layout), TSS.rsp0,
U/S-bit user mappings (W^X preserved), an int 0x80 syscall gate with a
mutable trap frame, and a setjmp-style enter/exit path. /sbin/init is a
real freestanding Zig binary built from sbin/, shipped on the ESP,
loaded by the bootloader (BootInfo.init_base/len), validated and mapped
by an in-kernel user-ELF loader, and run at CPL 3 — syscalls: exit,
ping, write. Tests: user, user-pf (U/S isolation proof, error code
0x5), init. Suite 27/27.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
max_cpus, max_tasks, kernel/IST stack sizes, and timer_hz move from scattered constants into one config module. root.zig goes back to being just the boot contract. Values unchanged; any can become a -D build option later.
Hoist max_cpus into danos (root.zig), raise it to 128, and stop reserving a static [max_cpus][16 KiB] IST array. Only the BSP's IST stack is static (needed before the allocator); each AP's is heap-allocated when it comes online. Shrinks the kernel from ~1.1 MB to ~139 KiB in ReleaseSmall.