reorg: move mmio into library/device and spell out its API

mmio is device-driver code, so it joins the other domains under
library/device/mmio/ (module name "mmio" unchanged — a pure relocation, only
the build paths move). And its abbreviated function names are spelled out per
docs/coding-standards.md:

  read  -> readRegister          mb  -> memoryBarrier
  write -> writeRegister         rmb -> readMemoryBarrier
                                 wmb -> writeMemoryBarrier

All call sites updated (virtio-gpu, usb-xhci-library, pci.Function); the two
display-driver placeholders import mmio but use nothing, so they're untouched.
Docs (driver-model graph, README layout, drivers.md, the FHS note) follow the
new path and names.

zig build + test green; virtio-gpu, display-native, display-reattach, usb-hid,
usb-hub, usb-storage, pci-scan pass.
This commit is contained in:
Daniel Samson
2026-07-22 21:28:32 +01:00
parent 7d540c4b2f
commit 5b874fc756
9 changed files with 82 additions and 83 deletions
+2 -3
View File
@@ -245,10 +245,9 @@ system/ → /system danos's own internals (the self-representation)
fat.zig, engine.zig, on-disk.zig)
library/ → /lib libraries, one sub-directory each
runtime/ the danos-native runtime + file API (fs) — the stable application ABI
mmio/ volatile register access + memory barriers
device/ device code by domain — model/ pci/ usb/ acpi/ — each a
device/ device code by domain — mmio/ model/ pci/ usb/ acpi/ — each a
shareable data module (device-abi, pci-class, usb-abi/ids,
acpi-ids) plus a logic module (pci, usb, aml)
acpi-ids) plus a logic module (mmio, pci, usb, aml)
protocol/ driver↔service wire contracts (vfs block display scanout input
power device-manager usb-transfer), one module per directory
boot/ → /boot the loaders
+1 -1
View File
@@ -87,7 +87,7 @@ A block driver is now **writable, but not yet memory-safe.** Every storage contr
worth naming is a bus master: it is programmed by handing it the physical address of a
descriptor ring and left to read and write memory on its own. That ring is exactly what
**`dma_alloc`** now provides — physically contiguous, pinned, uncacheable, with its
physical address disclosed — and **`/lib/mmio`**'s barriers order the descriptor writes
physical address disclosed — and **`/lib/device/mmio`**'s barriers order the descriptor writes
against the doorbell, and **`msi_bind`** delivers completions. So an AHCI or NVMe driver
can be written today (the M14/M15 work in [driver-model.md](driver-model.md); the earlier
"cannot host a block driver at all" is no longer true).
+13 -13
View File
@@ -106,9 +106,9 @@ module outlived it, which is rather the point.) The pattern generalises directly
```
library/
runtime/ module "runtime" — syscalls, ipc, lifecycle, memory, threads, log, fs
mmio/ module "mmio" — volatile register access + barriers [M14]
device/ device code grouped by domain; each domain splits into a shareable
data module (enums/wire types, std-only) and a logic module (mmio/IPC)
mmio/ module "mmio" — typed volatile register access + barriers [M14]
model/ module "device-abi" — DeviceDescriptor, DeviceClass, ResourceKind
pci/ "pci-class" (data) + "pci" — config/BAR/capability walk (Function)
usb/ "usb-abi" + "usb-ids" (data) + "usb" — descriptors, control/interrupt/bulk client
@@ -160,12 +160,12 @@ class driver, the device manager, or the kernel may share them freely.
`callCap` and `replyWait(..., send_cap)`, and class drivers consume them now: the
PS/2 keyboard and mouse drivers attach to ps2-bus this way, and `runtime.usb` /
`runtime.input` open their per-device and subscription channels with `callCap`.
- **M14** — DMA memory + the memory-ordering layer. `/lib/mmio` gives drivers typed
volatile access and `mb`/`rmb`/`wmb` (per-arch); `dma_alloc`/`dma_free` grant
- **M14** — DMA memory + the memory-ordering layer. `/lib/device/mmio` gives drivers typed
volatile access and `memoryBarrier`/`readMemoryBarrier`/`writeMemoryBarrier` (per-arch); `dma_alloc`/`dma_free` grant
physically-contiguous, pinned, uncacheable, reclaim-on-teardown buffers with the
physical address exposed (`pmm.allocContiguous`, a DMA arena, `mapUserDmaInto`).
`dma_below_4g` caps the address for legacy engines; `dma_write_combining` is accepted
but falls back to coherent until PAT is programmed. The bus drivers use `/lib/mmio`,
but falls back to coherent until PAT is programmed. The bus drivers use `/lib/device/mmio`,
and `dma_alloc` has real consumers now: the xHCI driver's rings and contexts,
usb-storage's command/status wrappers, virtio-gpu's virtqueue, and the fat
service's bounce buffer.
@@ -252,7 +252,7 @@ const dev_ep = ipc.callCap(h, // ... mint a per-device endpoint,
## M14 — DMA memory and the memory-ordering contract, for HCDs ✅ done
*Implemented: `/lib/mmio` (typed volatile access + `mb`/`rmb`/`wmb`, per-arch) and
*Implemented: `/lib/device/mmio` (typed volatile access + `memoryBarrier`/`readMemoryBarrier`/`writeMemoryBarrier`, per-arch) and
`dma_alloc`/`dma_free` (contiguous, pinned, uncacheable, reclaim-on-teardown, physical
address exposed). `dma_write_combining` still falls back to coherent — real WC needs
PAT, a small follow-up. The rest of this section is the original design note.*
@@ -294,23 +294,23 @@ doorbell.* = i; // volatile store to UC MMIO
// nothing stops the compiler reordering these; the device reads a stale descriptor
```
So the rules, which belong in `library/mmio.zig` and behind `arch`:
So the rules, which belong in `library/device/mmio/mmio.zig` and behind `arch`:
| Situation | Required |
|---|---|
| MMIO register read/write | `mmio.read` / `mmio.write` (volatile) |
| Fill DMA descriptor, then ring doorbell | `wmb()` between them |
| Woken by IRQ, then read what the device wrote | `rmb()` before the read |
| MMIO write that must complete before the next read | `mb()` |
| Fill DMA descriptor, then ring doorbell | `writeMemoryBarrier()` between them |
| Woken by IRQ, then read what the device wrote | `readMemoryBarrier()` before the read |
| MMIO write that must complete before the next read | `memoryBarrier()` |
And the per-arch lowering — the reason this must be an `arch` primitive and not a
sprinkling of `asm volatile`:
| | x86_64 | aarch64 |
|---|---|---|
| `mb()` | `mfence` | `dsb sy` |
| `rmb()` | `lfence` | `dsb ld` |
| `wmb()` | `sfence` | `dsb st` |
| `memoryBarrier()` | `mfence` | `dsb sy` |
| `readMemoryBarrier()` | `lfence` | `dsb ld` |
| `writeMemoryBarrier()` | `sfence` | `dsb st` |
| DMA cache coherency | coherent; nothing to do | **not guaranteed**; needs non-cacheable buffers or cache maintenance |
x86 is forgiving here — TSO plus strong-uncacheable MMIO means you usually get away
@@ -318,7 +318,7 @@ with a compiler barrier alone. ARM is not, and [vision.md](vision.md) makes ARM
condition. Build the abstraction while there is one caller to fix.
(Zig note: `@fence` was **removed in 0.16**. Use `@atomicRmw(..., .seq_cst)` for a full
barrier, or per-arch inline asm — which is what `library/mmio.zig` should hide.)
barrier, or per-arch inline asm — which is what `library/device/mmio/mmio.zig` should hide.)
## M15 — interrupts for PCI devices ✅ done (MSI)
+2 -2
View File
@@ -290,8 +290,8 @@ Several things this list used to warn about are now available (see
[driver-model.md](driver-model.md)): **port I/O** (`io_read`/`io_write`, claim-gated by
the device's `io_port` resource — direct ring-3 `in`/`out` is still a #GP, so a PS/2 or
16550 driver goes through these), **DMA memory** (`dma_alloc`: contiguous, pinned,
uncacheable, physical address exposed), **memory barriers** (`library/mmio`'s
`mb`/`rmb`/`wmb`, imported as the `mmio` module), **fault isolation** (a ring-3 fault kills only the faulting
uncacheable, physical address exposed), **memory barriers** (`library/device/mmio`'s
`memoryBarrier`/`readMemoryBarrier`/`writeMemoryBarrier`, imported as the `mmio` module), **fault isolation** (a ring-3 fault kills only the faulting
process — `killCurrentProcess` — and the machine keeps running,
[resilience](resilience.md)), and **reclaim + restart on death** (every path out of a
process releases its claims and IRQ/MSI bindings — `releaseAllOwnedBy`,