Two driver-model milestones plus a tree-wide naming pass. Suite 35/35
(QEMU) + host tests green.
M11 — IRQ-as-IPC. A ring-3 driver now sleeps until its device interrupts
it. New src/kernel/irq.zig: per-GSI endpoint bindings, comptime per-vector
trampolines, dispatch = mask GSI -> LAPIC EOI -> notifyLocked, all under one
lock region. irq_bind/irq_ack syscalls, gated by the device claim like
mmio_map. interruptDispatch no longer EOIs — each handler owns its EOI,
because a level line must be masked before it is acknowledged (irq_ack is
the unmask). Bindings are keyed on the owning task and released on exit
(a shared endpoint's siblings survive). hpetd rewritten interrupt-driven.
Tests: hpet (rewritten, reads back the I/O APIC routing) and irqfree.
M12 — bus drivers. DeviceDesc gains a parent, making the device table a
tree. dev_register (device_register) lets a process publish children below
a device it claimed; the kernel enforces resource containment (a child's
resources must nest in its parent's), so a descriptor can't fabricate a
window over kernel RAM. Descriptor copied in via copyFromUser (physmap
walk — an unmapped user pointer fails the call instead of faulting the
kernel). Per-parent child cap bounds table exhaustion. sbin/busd.zig is a
worked bus driver. Test: bus.
Naming — per docs/coding-standards.md: non-acronym abbreviations spelled
out (message, descriptor, device_service, scheduler, runtime, physical,
interpreter, ...); acronyms kept (IPC, MMIO, DMA, HCD, ...); files are
kebab-case (ipc-synchronous.zig, device-service.zig, vfs-protocol.zig, ...).
Exceptions: POSIX/C ABI names and Zig idioms (init/len/ptr) kept. Module
collisions resolved by specific naming (config -> parameters, device.zig
alias -> device_model). AML op/Op disambiguated: op = opcode, Op =
operation; per-opcode parse handlers renamed opX -> parseX.
New driver docs: drivers.md, driver-model.md (bus/class/HCD shapes + the
proposed M13–M16 ABI), coding-standards.md.
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).
init is now a real scheduled ring-3 process: it prints a heartbeat and
sleeps, forever. The kernel spawns it at boot via spawnProcess (its own
address space, preemption on) and the boot context drops to idle — the
system's steady state is "kernel idle, init alive in ring 3", beating
~1 Hz. Adds the sleep(ms) syscall. The init/process tests are reshaped
around the heartbeat (repeated syscalls, still-alive, two concurrent
processes on distinct address spaces). Pins init to LLD and folds the
.large code model's .ltext/.lrodata/.ldata into the linker script so its
code segment is R+X. Removes the now-dead borrowed-thread ELF loader
(runInitElf); spawnProcess is the one path. Docs updated. Suite 28/28.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-process address spaces (AddressSpace = a PML4 with an empty user
half and the shared kernel half copied in; create/destroy in paging.zig)
with CR3 switched on context switch and TSS.rsp0/kernel_rsp published per
switch. The GS base now points at an arch per-CPU block and every ring
transition observes the swapgs discipline, so a ring-3 `mov %ax,%gs` can
no longer poison per-CPU access. syscall/sysret is the primary user entry
(int 0x80 kept as a test path); one handler, installed once at boot,
serves both and dispatches on whether the caller is a scheduled process
or a borrowed test thread. spawnProcess loads an ELF into a fresh address
space and schedules it; exit frees the address space after switching to
the kernel tables. New `process` test: init runs twice as a real process
(create/exit/recreate) on its own page tables, coexisting with a kernel
task under preemption. Suite 28/28.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
paging.init now maps only the physmap, the framebuffer/LAPIC windows,
and the kernel's own segments; the entire low canonical half is left to
user space. Every higher-half PML4 entry is pre-created so a per-process
address space can share the kernel half by copying PML4[256..512), with
an assert against late top-half entries and a 4 GiB guard on
pre-switch table frames. The AP trampoline's low identity page is now
created transiently by arm() and unmapped by disarm(); startAp asserts
the page-table root is 32-bit addressable. Docs (paging.md) updated.
Suite 27/27; 4-core normal boot reaches /sbin/init.
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>
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.
fault-ap-df pins a #DF to an AP (its own IST must catch it); affinity checks a pinned task never migrates. onException now names the core, so an AP fault is attributed and shown contained. Both teeth-checked.
spawnOn(entry, priority, cpu) routes to a per-core pinned queue, merged with the global queue at O(1) selection. Falls back to unpinned for an offline/invalid core.
Arm the low frame (copy blob, make executable) only while a core climbs, then zero it and restore RW+NX; the frame stays reserved so cores can be re-woken. startSecondary is one re-runnable attempt; boot retries a non-responding core 3x. Validated the retry path by forcing a first-attempt failure.