Make zig-out a FHS image, and the boot volume

`zig build` now installs into a FHS-shaped zig-out that *is* the danos filesystem
and the boot volume — no more zig-out/bin or a separate esp/:

  zig-out/EFI/BOOT/BOOTX64.efi        (firmware entry; UEFI fixes this path)
  zig-out/boot/initial-ramdisk.img
  zig-out/system/kernel               (the kernel binary)
  zig-out/system/services/init  vfs
  zig-out/system/drivers/hpet   bus

Binaries land at their addressed, leaf-collapsed paths per the sub-project
resolution rule (system/services/init/init.zig -> system/services/init); vfs, hpet,
and bus are installed to their FHS homes too, so the image is complete even though
at boot they arrive inside the initial-ramdisk.

The bootloader (boot/efi.zig) now loads each artifact from its FHS path
(system\kernel, system\services\init, boot\initial-ramdisk.img); run-x86-64 mounts
zig-out directly; the QEMU test harness assembles its ESP from the FHS zig-out.

Also renames system/kernel/main.zig -> kernel.zig so the kernel follows the
name/name.zig convention (kernel/ = ring-0 code, services/ = ring-3 OS services).
Documents the resolution rule in the repository-layout section (README + coding
standard). Suite 35/35 plus host tests green.
This commit is contained in:
Daniel Samson
2026-07-10 13:57:02 +01:00
parent ceacc6b514
commit 3d1de37d0e
10 changed files with 103 additions and 62 deletions
+20 -1
View File
@@ -125,6 +125,25 @@ reach it *by module name*, never by a path into its files. The source tree delib
what you see under `system/` in the source is what a running danos represents under
`/system`.
**A sub-project is addressed by its directory; its entry point repeats the directory's
name.** `system/services/init/` contains `init.zig` (its root), and produces a binary
addressed as **`system/services/init`** — the repeated leaf resolves away:
| Source (root file) | Addressed as (module / binary / FHS path) |
|----------------------------------------|--------------------------------------------|
| `system/services/init/init.zig` | `system/services/init` → `/system/services/init` |
| `system/drivers/hpet/hpet.zig` | `system/drivers/hpet` → `/system/drivers/hpet` |
| `library/runtime/runtime.zig` | `library/runtime` (the `runtime` module) |
In **source**, a sub-project is a directory so it can hold many files — the entry is
`init/init.zig`, beside it `vfs/vfs-test.zig`, `vfs/protocol.zig`, and so on. When
**addressed or installed**, that collapses to the single canonical path: the `init`
binary installs to `/system/services/init` (a file at that path), not
`/system/services/init/init`. The repeated leaf exists only in source; the directory is
the identity, the entry file is its implementation. (Same idea as a Go package being its
directory, or a macOS `.app` bundle addressed by the bundle, not the executable within.)
A sub-project's extra files are reached through the module, never as separate paths.
```
system/ → /system danos's own internals (the self-representation)
danos.zig the kernel↔user ABI contract (the `danos` module)
@@ -157,7 +176,7 @@ appears in the private-ABI path.
| Area | Code |
|------|------|
| Boot methods (one per way of booting the kernel) | `boot/` — `efi.zig` (UEFI) → `BOOTX64.efi` |
| Kernel entry, panic, bring-up | `system/kernel/main.zig` |
| Kernel entry, panic, bring-up | `system/kernel/kernel.zig` |
| Shared loader↔kernel contract (`BootInfo`, `Framebuffer`, `MemoryMap`, `Syscall`, ABI) | `system/danos.zig` |
| Physical frame allocator | `system/kernel/pmm.zig` |
| Kernel heap (`std.mem.Allocator`) | `system/kernel/heap.zig` |
+5
View File
@@ -137,6 +137,11 @@ single word or acronym needs no hyphen: `scheduler.zig`, `paging.zig`, `apic.zig
`idt.zig`. (The module *alias* a file is imported under still follows the code
conventions above — `snake_case` — because it's an identifier, not a filename.)
**A sub-project's entry point repeats its directory's name** — `init/init.zig`,
`runtime/runtime.zig`, `hpet/hpet.zig` — and the sub-project is addressed by the
*directory* (`system/services/init`, `library/runtime`), with the repeated leaf
resolving away. See the repository-layout section of [README.md](README.md).
## Why acronyms are the line
Because an acronym has no letters to restore. `MMIO` doesn't become "memory mapped
+12 -9
View File
@@ -20,14 +20,17 @@ UEFI boots by looking for a FAT-formatted partition called the **EFI System
Partition (ESP)** and running a file at a well-known fallback path:
```
esp/EFI/BOOT/BOOTX64.efi <- the "removable media" default for x86-64
EFI/BOOT/BOOTX64.efi <- the "removable media" default for x86-64
```
That's exactly the layout `build.zig` assembles. It builds `boot/efi.zig` for the
`uefi` target, installs it to `esp/EFI/BOOT/BOOTX64.efi`, and drops the kernel ELF
at `esp/kernel`. The `run-x86-64` step then points QEMU at OVMF (UEFI firmware for
virtual machines) and presents that `esp/` directory to the guest as a FAT drive.
The firmware finds `BOOTX64.efi` and runs it — that's our `main()`.
The boot volume is the **FHS-shaped `zig-out`** itself (see the repository-layout note
in [README.md](README.md)): `build.zig` installs `boot/efi.zig` (built for the `uefi`
target) to `zig-out/EFI/BOOT/BOOTX64.efi` — the one path UEFI firmware fixes — and lays
the rest out by FHS path: the kernel at `zig-out/system/kernel`, init at
`zig-out/system/services/init`, the initial-ramdisk at `zig-out/boot/`. The
`run-x86-64` step points QEMU at OVMF (UEFI firmware for virtual machines) and presents
`zig-out` to the guest as a FAT drive. The firmware finds `BOOTX64.efi` and runs it —
that's our `main()`, which then loads the kernel and init from their FHS paths.
## Boot services: the firmware's API
@@ -164,13 +167,13 @@ the loader writes are the bytes the kernel reads.
```
power on
-> UEFI firmware initialises hardware
-> finds esp/EFI/BOOT/BOOTX64.efi, runs it (our efi.zig main)
-> finds EFI/BOOT/BOOTX64.efi on the FHS volume, runs it (our efi.zig main)
-> grab boot services
-> queryFramebuffer (via GOP: EDID native res, setMode, describe fb)
-> loadKernel (read danos ELF, load PT_LOAD segments to 0x100000)
-> loadKernel (read system/kernel ELF, load PT_LOAD segments to 0x100000)
-> exitBootServices (retry until the memory-map key holds)
-> jump to e_entry, boot_info pointer in RDI
-> kernel _start (system/kernel/main.zig: framebuffer console, then halt)
-> kernel _start (system/kernel/kernel.zig: framebuffer console, then halt)
```
Bottom line: **UEFI's job is to give us a CPU, memory, and a framebuffer, then
+1 -1
View File
@@ -83,7 +83,7 @@ treats the call:
signature for a kernel entry point — the bootloader jumps in and nothing ever
jumps back out.
You can see the chain in `system/kernel/main.zig`: `_start` is `noreturn`, it calls
You can see the chain in `system/kernel/kernel.zig`: `_start` is `noreturn`, it calls
`kmain` which is `noreturn`, which ends by calling `arch.halt()` which is
`noreturn`. The "never returns" property is threaded all the way down.