Post-reorg cleanup: POSIX layer, and naming fixes
Follow-up to the monorepo re-org. Suite 35/35 plus host tests green. POSIX compatibility is now its own library, library/posix/ (unistd, stdio), layered strictly over the runtime — it calls the runtime's IPC/heap, never system calls directly. The runtime is now POSIX-free (the danos-native application ABI). The VFS wire protocol is danos-native throughout (Stat -> FileStatus, .stat -> .status, O_CREAT -> create); the POSIX layer maps the POSIX spellings at the boundary. The coding standard's ABI-name exception is scoped to one place: a file is allowed POSIX spellings only if it lives under library/posix/ — everywhere else, danos naming with no exception. Naming fixes, all mechanical: - initrd -> initial-ramdisk: the source file, the module, the tool (make-initial-ramdisk.py), the artifact (initial-ramdisk.img, including the bootloader's load path), and the identifiers. - system/kernel/device-service.zig -> devices-broker.zig: it is ring-0 kernel code (the trusted device table + claim capability), not a ring-3 service. The future user-space device *manager* (policy) will live in system/services/. - Dropped the daemon `d` suffix: hpetd -> hpet, busd -> bus. A driver lives in system/drivers/, so the folder already says what it is; encoding the role in the name too is redundant. The coding standard drops that exception. - system/devices/aml/interp.zig -> interpreter.zig (the type was already Interpreter).
This commit is contained in:
+17
-9
@@ -128,22 +128,29 @@ what you see under `system/` in the source is what a running danos represents un
|
||||
```
|
||||
system/ → /system danos's own internals (the self-representation)
|
||||
danos.zig the kernel↔user ABI contract (the `danos` module)
|
||||
parameters.zig initrd.zig shared contracts
|
||||
parameters.zig initial-ramdisk.zig shared contracts
|
||||
kernel/ IPC, memory, scheduling, the private syscall dispatch
|
||||
architecture/x86_64/ the `architecture` module (never named by generic code)
|
||||
devices/ the device model /system/devices reflects (+ aml/)
|
||||
drivers/ hpetd/ busd/ one sub-project per driver → /system/drivers
|
||||
drivers/ hpet/ bus/ one sub-project per driver → /system/drivers
|
||||
services/ init/ vfs/ system servers → /system/services (vfs/ holds
|
||||
vfs.zig, vfs-test.zig, protocol.zig)
|
||||
library/ → /lib the runtime library (the stable application ABI)
|
||||
library/ → /lib libraries, one sub-directory each
|
||||
runtime/ the danos-native runtime — the stable application ABI
|
||||
posix/ POSIX/C compatibility, layered over runtime
|
||||
boot/ → /boot the loaders
|
||||
tools/ test/ host-side build + QEMU test harness
|
||||
```
|
||||
|
||||
A sub-project exposes its **public interface as a module**: `system/services/vfs/` owns
|
||||
the VFS wire protocol (`protocol.zig`, the `vfs-protocol` module), which the runtime's
|
||||
file layer imports by name. `usb`/`block` drivers will expose their protocols the same
|
||||
way.
|
||||
the VFS wire protocol (`protocol.zig`, the `vfs-protocol` module), which the POSIX
|
||||
layer imports by name. `usb`/`block` drivers will expose their protocols the same way.
|
||||
|
||||
`library/posix/` is special: it is the **one place** POSIX/C spellings are allowed
|
||||
verbatim (`stat`, `O_CREAT`, `fopen`, `errno`). Everywhere else follows the danos
|
||||
naming rule with no exception — see [coding-standards.md](coding-standards.md). The
|
||||
POSIX layer calls the runtime, never the kernel's system calls directly, so it never
|
||||
appears in the private-ABI path.
|
||||
|
||||
## Source map
|
||||
|
||||
@@ -159,14 +166,15 @@ way.
|
||||
| IPC channels between kernel threads (message passing) | `system/kernel/ipc.zig` |
|
||||
| IPC endpoints: cross-address-space call/reply, handles, notifications | `system/kernel/ipc-synchronous.zig` |
|
||||
| User processes: ELF loading, address spaces, the syscall table | `system/kernel/process.zig` |
|
||||
| Device tree + claim capability + `device_register` containment | `system/kernel/device-service.zig` |
|
||||
| Device tree + claim capability + `device_register` containment | `system/kernel/devices-broker.zig` |
|
||||
| IRQ-as-IPC: routing a device interrupt to a driver's endpoint | `system/kernel/irq.zig` |
|
||||
| Hardware discovery (ACPI/device tree) behind one neutral device model | `system/devices/` |
|
||||
| Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` |
|
||||
| In-kernel test cases | `system/kernel/tests.zig` |
|
||||
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |
|
||||
| Runtime library (`runtime`): syscall wrappers, heap, stdio, IPC, device access — the stable application ABI | `library/runtime/` |
|
||||
| danos-native runtime (`runtime`): syscall wrappers, heap, IPC, device access — the stable application ABI | `library/runtime/` |
|
||||
| POSIX/C compatibility (`posix`): unistd, stdio — the one place POSIX names are allowed | `library/posix/` |
|
||||
| System services (init, the VFS server + its `protocol` module) | `system/services/` |
|
||||
| Device drivers, one sub-project each (`hpetd` leaf driver, `busd` bus driver) | `system/drivers/` |
|
||||
| Device drivers, one sub-project each (`hpet` leaf driver, `bus` bus driver) | `system/drivers/` |
|
||||
| Build + `run-x86-64` (QEMU/OVMF) | `build.zig` |
|
||||
| QEMU integration test harness | `test/qemu_test.py` |
|
||||
|
||||
+25
-15
@@ -6,7 +6,7 @@ Conventions for danos source. The overriding one, from which most of the rest fo
|
||||
> abbreviation is an acronym.**
|
||||
|
||||
`interruptDispatch`, not `intDisp`. `message_len`, not `message_len` (`msg` expands, `len`
|
||||
is a Zig idiom — see the exceptions). `device_service`, not `device_service`. `scheduler`, not
|
||||
is a Zig idiom — see the exceptions). `devices_broker`, not `devices_broker`. `scheduler`, not
|
||||
`sched`. The cost of a longer name is paid once, at the keyboard; the cost of a
|
||||
cryptic one is paid every time the code is read, by everyone who reads it. In a
|
||||
microkernel whose whole argument is that a human can hold each piece in their head,
|
||||
@@ -58,13 +58,22 @@ abbreviation, expand it.
|
||||
|
||||
Three, and only three.
|
||||
|
||||
1. **Foreign ABI names are spelled exactly as the ABI spells them.** A function that
|
||||
*is* the C or POSIX interface keeps its name: `fopen`, `fwrite`, `fread`, `malloc`,
|
||||
`calloc`, `realloc`, `free`, `memcpy`, `mmap`, `munmap`, `open`, `read`, `write`,
|
||||
`close`, `lseek`, `stat`, `errno`. We don't get to rename `fwrite` to
|
||||
`fileWrite` — it wouldn't be `fwrite` any more. This also covers the syscall
|
||||
*wrappers* that exist to match those names. It does **not** license inventing new
|
||||
abbreviated names in that style.
|
||||
1. **Foreign ABI names are spelled exactly as the ABI spells them — but only inside
|
||||
the layer that *is* that ABI.** A function that *is* the C or POSIX interface keeps
|
||||
its name: `fopen`, `fwrite`, `fread`, `malloc`, `calloc`, `realloc`, `free`,
|
||||
`memcpy`, `mmap`, `munmap`, `open`, `read`, `write`, `close`, `lseek`, `stat`,
|
||||
`errno`, `O_CREAT`. We don't get to rename `fwrite` to `fileWrite` — it wouldn't be
|
||||
`fwrite` any more.
|
||||
|
||||
**This exception is scoped to one place: `library/posix/`.** A file under
|
||||
`library/posix/` *is* the foreign ABI, so it keeps the ABI's spellings — that is the
|
||||
whole rule for that directory. **Everywhere else, Zig/danos naming applies with no
|
||||
POSIX exception**, so there is nothing to get wrong: if you're not in
|
||||
`library/posix/`, expand it. A concept POSIX also has gets a danos name outside that
|
||||
layer — the VFS wire protocol carries a `FileStatus`, not a `Stat`, and a `create`
|
||||
flag, not `O_CREAT`; `library/posix/` is what maps `stat`→`status` and
|
||||
`O_CREAT`→`create` at the boundary. (The `syscall` *wrappers* elsewhere are not an
|
||||
exception to this — they wrap the private danos ABI, so they use danos names.)
|
||||
|
||||
2. **Zig idioms are spelled the way Zig spells them.** Three names are the language's,
|
||||
not ours, and are left alone:
|
||||
@@ -83,11 +92,12 @@ Three, and only three.
|
||||
keep `i`; a coordinate may be `x`, `y`. The moment the scope is big enough that the
|
||||
letter's meaning isn't obvious on sight, give it a real name. When in doubt, name it.
|
||||
|
||||
4. **Established Unix filesystem and program conventions.** Top-level directories keep
|
||||
their conventional names — `src`, `lib`, `sbin`, `bin`, `docs` — as do daemon
|
||||
programs by their `d` suffix (`hpetd`, `busd`, following `sshd`/`httpd`). These are
|
||||
names a Unix reader already knows; expanding them fights the convention rather than
|
||||
serving it.
|
||||
That's all — no Unix-abbreviation exception. The source directories are full words
|
||||
(`system`, `library`, not `src`/`lib`), and there is no daemon `d` suffix: a driver
|
||||
lives in `system/drivers/` and a service in `system/services/`, so the *location*
|
||||
already says what it is. Encoding the role in the name too (`busd`, `vfsd`) is
|
||||
redundant — the program is just `bus`, `vfs`. Don't put in a name what its directory
|
||||
already tells you.
|
||||
|
||||
## A note on collisions
|
||||
|
||||
@@ -118,11 +128,11 @@ Within those spelling rules, follow Zig's own conventions:
|
||||
|
||||
- **Types** — `PascalCase`: `DeviceDescriptor`, `Endpoint`, `WaitQueue`.
|
||||
- **Functions** — `camelCase`: `mapUserDeviceInto`, `notifyFromIsr`.
|
||||
- **Variables, fields, constants** — `snake_case`: `message_length`, `device_service`,
|
||||
- **Variables, fields, constants** — `snake_case`: `message_length`, `devices_broker`,
|
||||
`notify_badge_bit`.
|
||||
|
||||
**File names are `kebab-case`.** A file named for a multi-word thing hyphenates it:
|
||||
`device-tree.zig`, `ipc-synchronous.zig`, `vfs-protocol.zig`, `device-service.zig`. A
|
||||
`device-tree.zig`, `ipc-synchronous.zig`, `vfs-protocol.zig`, `devices-broker.zig`. A
|
||||
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.)
|
||||
|
||||
@@ -8,7 +8,7 @@ Most modern Unix and Unix-like operating systems follow the FHS. DanOS has its o
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| / | Primary hierarchy root and root directory of the entire file system hierarchy. |
|
||||
| /bin | Essential command binaries that need to be available in single-user mode, including to bring up the system or repair it, for all users (e.g., cat, ls, cp). |
|
||||
| /boot | Boot loader files (e.g., EFI, initrd.img ). |
|
||||
| /boot | Boot loader files (e.g., EFI, initial-ramdisk.img ). |
|
||||
| /dev | POSIX Device files (e.g., /dev/null, /dev/disk0, /dev/tty, /dev/random). |
|
||||
| /etc | Host-specific system-wide configuration files. |
|
||||
| /home | Users' home directories, containing saved files, personal settings, etc. |
|
||||
@@ -17,7 +17,7 @@ Most modern Unix and Unix-like operating systems follow the FHS. DanOS has its o
|
||||
| /srv | Site-specific data served by this system, such as data and scripts for web servers, data offered by FTP servers, and repositories for version control systems |
|
||||
| /system | DanOS operating system files (similar idea to C:\Windows). A true representation of danos — its layout mirrors the source tree, so `/system` is what danos *is*. |
|
||||
| /system/devices | danos virtual device tree e.g. similar to /sys on linux but with danos device tree conventions (the structures in the devices module) |
|
||||
| /system/drivers | driver binaries, one sub-project each (e.g. /system/drivers/hpetd) |
|
||||
| /system/drivers | driver binaries, one sub-project each (e.g. /system/drivers/hpet) |
|
||||
| /system/services | system-service binaries — the VFS server, init, and other user-mode servers (e.g. /system/services/vfs, /system/services/init) |
|
||||
| /system/kernel | the kernel image |
|
||||
| /tmp | Directory for temporary files (see also /var/tmp). Often not preserved between system reboots and may be severely size-restricted. |
|
||||
@@ -61,7 +61,7 @@ to the driver in the order written, and a read consumes what is there. Terminals
|
||||
serial lines, keyboards and mice are all of this shape. These are the natural first
|
||||
device nodes in danos, because a character driver needs nothing the kernel doesn't
|
||||
already provide — it claims its device, maps its registers with `mmio_map`, and blocks
|
||||
on `replyWait` for either an interrupt or a client request. `system/drivers/hpetd/hpetd.zig` is already
|
||||
on `replyWait` for either an interrupt or a client request. `system/drivers/hpet/hpet.zig` is already
|
||||
that program, minus the client half.
|
||||
|
||||
The obstacle is not the file type, it is which hardware a ring-3 driver can actually
|
||||
@@ -91,7 +91,7 @@ device to a driver process, with no IOMMU programmed, is equivalent to granting
|
||||
which would forfeit the isolation that motivates user-space drivers in the first place.
|
||||
|
||||
Block devices therefore wait on DMA-capable memory, memory barriers, and VT-d/DMAR —
|
||||
the M14–M16 work in [driver-model.md](driver-model.md). A ramdisk over the initrd is
|
||||
the M14–M16 work in [driver-model.md](driver-model.md). A ramdisk over the initial ramdisk is
|
||||
the one block-shaped thing implementable now, and it needs no driver process.
|
||||
|
||||
### Pseudo-devices
|
||||
|
||||
@@ -32,19 +32,19 @@ plain bus driver with no controller — a USB hub — is also a real thing.
|
||||
|
||||
## The device table is the spine
|
||||
|
||||
danos already has the right central structure. `system/kernel/device-service.zig` holds a table of
|
||||
danos already has the right central structure. `system/kernel/devices-broker.zig` holds a table of
|
||||
`DeviceDesc`, each with a parent, a class, and a set of resources. Firmware discovery
|
||||
seeds it ([discovery.md](discovery.md)); `device_register` grows it.
|
||||
|
||||
Three invariants make it a capability system rather than a directory:
|
||||
|
||||
1. **A claim is exclusive.** `device_claim(id)` succeeds once. Everything downstream —
|
||||
`mmio_map`, `irq_bind`, `device_register` — checks `device_service.ownerOf(id) == me`.
|
||||
`mmio_map`, `irq_bind`, `device_register` — checks `devices_broker.ownerOf(id) == me`.
|
||||
2. **A descriptor is a licence to map physical memory.** Whoever claims a device may
|
||||
map its `.memory` resources and bind its `.irq` resources. This is why
|
||||
`device_register` cannot be a free-for-all.
|
||||
3. **Therefore: containment.** Every resource of a registered child must lie inside a
|
||||
resource of the same kind on its parent (`device_service.contains`). A bus driver can only
|
||||
resource of the same kind on its parent (`devices_broker.contains`). A bus driver can only
|
||||
ever *subdivide* what it already holds. Without this, `device_register` would be a
|
||||
syscall named "map any physical page you like."
|
||||
|
||||
@@ -57,7 +57,7 @@ is not an address window. Discovery is trusted; user space is not.
|
||||
|
||||
### What a bus driver looks like
|
||||
|
||||
`system/drivers/busd/busd.zig` is the smallest honest one. Its "bus" is the HPET's register block and
|
||||
`system/drivers/bus/bus.zig` is the smallest honest one. Its "bus" is the HPET's register block and
|
||||
its "devices" are the block's comparators:
|
||||
|
||||
```zig
|
||||
@@ -78,7 +78,7 @@ for (0..n) |i| { // 3. publish each child
|
||||
|
||||
Each child is left **unclaimed**, which is the handoff: a comparator driver can now
|
||||
`device_claim` one and `mmio_map` it, and will see only its own 0x20-byte window. A child
|
||||
whose window escapes the bus is refused — `busd` asserts that, and the `bus` test
|
||||
whose window escapes the bus is refused — `bus` asserts that, and the `bus` test
|
||||
asserts the kernel's table upholds it.
|
||||
|
||||
A USB device has *no* resources at all: `resource_count = 0`, because it's addressed
|
||||
@@ -247,7 +247,7 @@ barrier, or per-arch inline asm — which is what `library/mmio.zig` should hide
|
||||
|
||||
**The blocker, and it's a hard one.** No PCI device can take an interrupt today.
|
||||
[`addBars`](system/devices/acpi.zig) records `.memory` and `.io_port` BARs and never an
|
||||
`.irq`; there is no `_PRT` parsing anywhere in the tree. `hpetd` only works because the
|
||||
`.irq`; there is no `_PRT` parsing anywhere in the tree. `hpet` only works because the
|
||||
HPET advertises its own routing options in its own registers — a privilege no ordinary
|
||||
device has.
|
||||
|
||||
@@ -271,7 +271,7 @@ which means **discovery should give each `pci_device` a `.memory` resource for i
|
||||
4 KiB ECAM slot**. That's a small change to `parseMcfg` and it unblocks the whole
|
||||
capability walk (MSI, MSI-X, PCIe extended caps) without any new syscall.
|
||||
|
||||
Note QEMU's HPET reports `Tn_FSB_INT_DEL_CAP = 0` — no MSI — so `hpetd` can never
|
||||
Note QEMU's HPET reports `Tn_FSB_INT_DEL_CAP = 0` — no MSI — so `hpet` can never
|
||||
exercise this path. The first MSI driver will be the first PCI driver.
|
||||
|
||||
## M16 — the IOMMU, and the honest caveat
|
||||
@@ -291,7 +291,7 @@ gap should be named rather than implied.
|
||||
|
||||
`M13` (capability passing) is independent of `M14`/`M15` and is the cheapest. It
|
||||
unlocks class drivers, which are the shape with no hardware requirements at all — you
|
||||
could write a real one against `busd`'s comparators tomorrow.
|
||||
could write a real one against `bus`'s comparators tomorrow.
|
||||
|
||||
`M14` and `M15` together unlock the first HCD. `M14`'s barrier layer is worth landing
|
||||
on its own regardless: it's small, obviously correct, and stops every future driver
|
||||
|
||||
+7
-7
@@ -40,7 +40,7 @@ memory; if `irq_bind` took a GSI, any process could bind the keyboard's line and
|
||||
silently intercept it. Instead the kernel checks two things (`process.ownedGsi`, and
|
||||
the same check at the top of `sysMmioMap`):
|
||||
|
||||
- `device_service.ownerOf(dev_id) == me` — you claimed it, and claims are exclusive
|
||||
- `devices_broker.ownerOf(dev_id) == me` — you claimed it, and claims are exclusive
|
||||
- the resource at `res_idx` is of the right *kind* — `memory` for `mmio_map`, `irq`
|
||||
for `irq_bind`
|
||||
|
||||
@@ -138,7 +138,7 @@ Two properties worth knowing:
|
||||
|
||||
## A whole driver
|
||||
|
||||
`system/drivers/hpetd/hpetd.zig` is ~150 lines and does all of it. The shape:
|
||||
`system/drivers/hpet/hpet.zig` is ~150 lines and does all of it. The shape:
|
||||
|
||||
```zig
|
||||
const hpet = findHpet(buf) orelse return; // device_enumerate, look for
|
||||
@@ -206,7 +206,7 @@ bus driver may only ever subdivide what it already owns.
|
||||
A device with **no resources** is legal and common. A USB device is reached through its
|
||||
controller, not by MMIO, so it gets `resource_count = 0`.
|
||||
|
||||
See [`system/drivers/busd/busd.zig`](../system/drivers/busd/busd.zig) for a complete one, and
|
||||
See [`system/drivers/bus/bus.zig`](../system/drivers/bus/bus.zig) for a complete one, and
|
||||
[driver-model.md](driver-model.md) for how bus drivers, class drivers and host
|
||||
controller drivers fit together.
|
||||
|
||||
@@ -269,8 +269,8 @@ Worth knowing before you write the second driver:
|
||||
|
||||
## Verifying it
|
||||
|
||||
The `hpet` test spawns `hpetd` from the initrd and watches the serial log. The driver
|
||||
prints `hpetd: ok` only after being woken five times, and its loop's only exit is
|
||||
The `hpet` test spawns `hpet` from the initial ramdisk and watches the serial log. The driver
|
||||
prints `hpet: ok` only after being woken five times, and its loop's only exit is
|
||||
through `replyWait` returning a notification — it cannot reach that line by polling.
|
||||
|
||||
The last check doesn't trust the driver's self-report at all: the kernel reads the I/O
|
||||
@@ -285,7 +285,7 @@ $ python3 test/qemu_test.py hpet irqfree iopass
|
||||
iopass ... PASS (matched 'DANOS-TEST-RESULT: PASS')
|
||||
```
|
||||
|
||||
Two companions cover what `hpetd` can't, because it never exits:
|
||||
Two companions cover what `hpet` can't, because it never exits:
|
||||
|
||||
- **`irqfree`** — the teardown path. Binds two owners to one shared endpoint, releases
|
||||
one, and reads the I/O APIC back: the departing owner's line is masked, the sibling's
|
||||
@@ -305,7 +305,7 @@ controller drivers), and the IOMMU — have proposed signatures in
|
||||
I/O permission bitmap swapped on context switch, or `io_in`/`io_out` syscalls gated
|
||||
by the same claim. The legacy devices that need it are all low-rate, so the syscall
|
||||
is likely fast enough.
|
||||
- **Releasing a claim.** There is no `dev_release`, and `device_service` never drops a claim on
|
||||
- **Releasing a claim.** There is no `dev_release`, and `devices_broker` never drops a claim on
|
||||
exit — only IRQ bindings are released. A dead driver's device stays owned forever,
|
||||
which blocks restart.
|
||||
- **Unregistering children.** `device_register` only appends. A USB device that is
|
||||
|
||||
Reference in New Issue
Block a user