# Paging: the kernel's page tables and VMM

Every memory access the CPU makes goes through the **page tables**: hardware walks
them to translate a virtual address into a physical one, and faults if there's no
valid mapping. danos builds its own tables (rather than staying on the firmware's,
which live in memory we'd like to reclaim and don't control), switches CR3 onto
them, and — crucially — maps with **real permissions**.

It's x86_64-specific (the 4-level table format is an Intel/AMD thing), so it lives
behind the [arch](arch.md) boundary in `system/kernel/architecture/x86_64/paging.zig`.

## The format

x86_64 uses **4 levels**: PML4 → PDPT → PD → PT, each a 512-entry table, with 9
bits of the virtual address indexing each level and the low 12 bits the offset into
the final 4 KiB page. Each entry holds a physical address plus flag bits —
present, writable, and (bit 63) **no-execute**. danos maps everything with 4 KiB
pages: precise, and the extra table memory is negligible against available RAM.

## Higher half: the address-space layout

danos is a **higher-half kernel**. The kernel is linked to run at
`0xFFFF_FFFF_8000_0000` but loaded low (the linker script's `AT()` gives each
segment a physical load address at 1 MiB up; the bootloader maps the high link
address to the low load address in its bootstrap tables and jumps in). The entire
**low canonical half is reserved for user space**; the kernel lives in the top half
alongside a **physmap** — a straight window onto all of physical memory at
`physmap_base + phys`. Wherever the kernel needs to touch a physical address (a
page-table frame, an ACPI table, a device register), it adds that constant:
`system.physToVirt(phys)`. The layout constants live in `system/boot-handoff.zig`:

| region | virtual base | PML4 slot |
|--------|--------------|-----------|
| user image + stack | `0x0000_7000_0000_0000` | 224 (low half) |
| kernel heap | `0xFFFF_8000_0000_0000` | 256 |
| physmap (all RAM + MMIO windows) | `0xFFFF_8800_0000_0000` + phys | 272 |
| kernel image | `0xFFFF_FFFF_8000_0000` | 511 |

The bootloader builds temporary **bootstrap tables** (identity + a 4 GiB physmap +
the high kernel) so it can switch CR3 and jump to the high entry; the kernel then
builds its own precise tables below and abandons them. Because both use the same
`physmap_base`, any physmap pointer minted before the switch stays valid after it.

## What gets mapped, and with what permissions

The address space is built in four passes (`init`):

1. **All RAM in the physmap, RW + NX.** Every non-MMIO region from the
   [memory map](memory-map.md) is mapped at `physToVirt(phys)`, read-write and
   *non-executable*. There is **no low/identity mapping** — the low half is user
   space. (Frames the kernel touches while still building these tables are reached
   through the loader's bootstrap physmap, which covers the low 4 GiB; both the
   frame allocator and the table builder scan low-address-up, so those frames stay
   under that limit.)
2. **The framebuffer and the Local APIC**, the device memory the kernel touches
   directly, as physmap windows (RW + NX). Other MMIO is mapped on demand by
   `mapMmio`, also into the physmap; everything else is left unmapped, so a stray
   access faults instead of silently succeeding.
3. **The kernel's own segments, overlaid with their true ELF permissions**, at
   their high link addresses mapped to their low physical load addresses. This is
   the interesting part.
4. **Every higher-half PML4 entry pre-created** (an empty PDPT where none exists
   yet). The kernel half is then a fixed set of top-level slots, so a per-process
   address space can share it by copying `PML4[256..512)` once — growth beneath
   those slots (heap, on-demand MMIO) propagates to every address space because
   they share the PDPTs. `init` asserts no new higher-half PML4 entry appears
   afterward.

### W^X from the ELF program headers

Blanket RW+NX is fine for data but wrong for the kernel's own code, which must be
executable — and its code must *not* be writable (W^X: no page is both). We get the
right permissions per region straight from the kernel ELF: the **loader already
parses the program headers**, so `efi.zig` records each `PT_LOAD` segment's
address, size and R/W/X flags into `BootInfo`. Pass 3 re-maps those ranges with
flags derived from the ELF flags:

| segment | ELF flags | mapped as |
|---------|-----------|-----------|
| `.text`   | R + X | present, **not** writable, **not** NX |
| `.rodata` | R     | present, not writable, NX |
| `.data`/`.bss` | R + W | present, writable, NX |

So code can execute but not be written, and data can be written but not executed.
(Intermediate table entries are left writable and executable so the *leaf's* bits
govern — a page is writable only if every level is, and non-executable if any level
is.) NX itself has to be switched on first via `EFER.NXE`, or the NX bit would be a
reserved bit and fault.

### The null guard

The whole low half is unmapped except for explicit user mappings, so page 0 (and
every near-null address) is unmapped by construction. A null (or near-null) pointer
dereference in the kernel takes a page fault instead of quietly reading or writing
real memory — turning a whole class of silent bugs into an immediate, located crash.

## Switching on, and the on-demand API

Loading the PML4's physical address into **CR3** switches address spaces and
flushes the TLB in one step. This works because the firmware's identity map is
still active *while we build*, so freshly allocated table frames are reachable by
physical address; afterwards they're covered by pass 1.

`init` keeps the PML4 and the frame allocator around and exposes `map(virt, phys,
writable)` / `unmap(virt)` (with `invlpg` TLB invalidation) — the primitive the
kernel heap will build on to map pages on demand.

## Verifying it

Four tests (see [testing.md](testing.md)) pin down the guarantees:

- **`vmm`** — map a fresh frame at an unused virtual address, write and read it
  back. Proves `map` works end to end.
- **`fault-pf`** — an access far above all mapped RAM faults, with the address in
  CR2. Proves we're on our own (deliberately sparse) map.
- **`fault-nx`** — calling into a data page (NX) faults on the instruction fetch.
  Proves NX is enforced.
- **`fault-null`** — writing to address 0 faults. Proves the null guard.

> Toolchain notes, both hit while writing the tests: a volatile access to a
> compile-time-*constant* address either trips the self-hosted backend's
> `mov moffs` gap or (for address 0) Zig's null-pointer safety check — so the
> null-guard test launders the address through empty asm and uses an `allowzero`
> pointer to force a real hardware access. And `invlpg`, like `lgdt`, needs its
> operand staged through a register in inline asm.

## What's next (not done here)

- **A kernel heap** — the first real user of `map`, giving the kernel dynamic
  allocation. This is the natural next milestone.
- **A higher-half kernel**: relink the kernel at a high virtual base so a future
  user address space can own the low half.
- **Per-address-space tables** once there are user processes, and shared/copy-on-
  write mappings.
- **Uncacheable MMIO**: the APIC/framebuffer pages are mapped writeback-cacheable;
  real hardware wants MMIO marked uncacheable.
