diff --git a/boot/efi.zig b/boot/efi.zig index ee16464..5178336 100644 --- a/boot/efi.zig +++ b/boot/efi.zig @@ -15,8 +15,8 @@ const kernel_file_name = std.unicode.utf8ToUtf16LeStringLiteral("kernel"); /// the FAT driver walks the components itself, so no directory dance needed). const init_file_name = std.unicode.utf8ToUtf16LeStringLiteral("sbin\\init"); -/// Path of the initrd image on the boot volume (the VFS server + drivers). -const initrd_file_name = std.unicode.utf8ToUtf16LeStringLiteral("initrd.img"); +/// Path of the initial_ramdisk image on the boot volume (the VFS server + drivers). +const initial_ramdisk_file_name = std.unicode.utf8ToUtf16LeStringLiteral("initial-ramdisk.img"); /// Physical page size, and the sentinel UEFI uses to seek to end-of-file. const page_size = 4096; @@ -68,9 +68,9 @@ fn boot() !noreturn { log(") - booting without user space\r\n"); }; - // Best effort: the initrd (VFS server + drivers) is optional too. - loadInitrd(bs, &boot_information) catch |err| { - log("danos: no initrd ("); + // Best effort: the initial_ramdisk (VFS server + drivers) is optional too. + loadInitialRamdisk(bs, &boot_information) catch |err| { + log("danos: no initial_ramdisk ("); logBytes(@errorName(err)); log(")\r\n"); }; @@ -396,12 +396,12 @@ fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) ! log("danos: sbin/init loaded\r\n"); } -/// Ferry the initrd (the VFS server + drivers) to the kernel, same as init. -fn loadInitrd(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void { - const image = try loadFile(bs, initrd_file_name); - boot_information.initrd_base = @intFromPtr(image.ptr); - boot_information.initrd_len = image.len; - log("danos: initrd loaded\r\n"); +/// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init. +fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void { + const image = try loadFile(bs, initial_ramdisk_file_name); + boot_information.initial_ramdisk_base = @intFromPtr(image.ptr); + boot_information.initial_ramdisk_len = image.len; + log("danos: initial_ramdisk loaded\r\n"); } /// Validate the ELF, copy every PT_LOAD segment to its physical address, and diff --git a/build.zig b/build.zig index 693c5f3..a908462 100644 --- a/build.zig +++ b/build.zig @@ -58,6 +58,7 @@ fn addUserBinary( b: *std.Build, target: std.Build.ResolvedTarget, runtime_module: *std.Build.Module, + posix_module: *std.Build.Module, name: []const u8, root: []const u8, ) *std.Build.Step.Compile { @@ -74,6 +75,9 @@ fn addUserBinary( .stack_protector = false, .imports = &.{ .{ .name = "runtime", .module = runtime_module }, + // POSIX/C compatibility layer, available to any program that wants it + // (danos-native code uses `runtime` directly). See library/posix/. + .{ .name = "posix", .module = posix_module }, }, }), }); @@ -144,11 +148,13 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("system/services/vfs/protocol.zig"), }); - // The user-space runtime library (a nascent libc): system_call wrappers, the - // C-convention heap, IPC helpers, the process start shim. Compiled into every - // user binary (see addUserBinary), so it inherits each exe's `.large` code - // model — do NOT set a target/code_model here. It imports `danos` for the - // shared SystemCall numbers and `vfs-protocol` for the file API. + // The danos-native user-space runtime: system_call wrappers, the C-convention + // heap, IPC helpers, the process start shim, device access. This is the stable + // application ABI; POSIX compatibility is a separate library on top (see below). + // Compiled into every user binary (see addUserBinary), so it inherits each exe's + // `.large` code model — do NOT set a target/code_model here. It imports `danos` + // for the shared SystemCall numbers and re-exports `vfs-protocol` for the VFS + // server. const runtime_module = b.addModule("runtime", .{ .root_source_file = b.path("library/runtime/runtime.zig"), .imports = &.{ @@ -157,10 +163,23 @@ pub fn build(b: *std.Build) void { }, }); - // The initrd container format, shared by the kernel (unpacks it) and the - // build-time packer tools/mkinitrd.zig (produces it). No dependencies. - const initrd_module = b.addModule("initrd", .{ - .root_source_file = b.path("system/initrd.zig"), + // The POSIX / C compatibility layer, a separate library layered strictly over the + // runtime (it calls the runtime's IPC/heap, never system calls directly). This is + // the one place POSIX/C spellings are allowed verbatim — see docs/coding-standards.md + // and library/posix/posix.zig. + const posix_module = b.addModule("posix", .{ + .root_source_file = b.path("library/posix/posix.zig"), + .imports = &.{ + .{ .name = "runtime", .module = runtime_module }, + .{ .name = "vfs-protocol", .module = vfs_protocol_module }, + .{ .name = "danos", .module = danos_module }, + }, + }); + + // The initial_ramdisk container format, shared by the kernel (unpacks it) and the + // build-time packer tools/make-initial-ramdisk.py (produces it). No dependencies. + const initial_ramdisk_module = b.addModule("initial-ramdisk", .{ + .root_source_file = b.path("system/initial-ramdisk.zig"), }); // Compile-time configuration the kernel reads as `@import("build_options")`. The @@ -198,7 +217,7 @@ pub fn build(b: *std.Build) void { .{ .name = "platform", .module = platform_module }, .{ .name = "parameters", .module = parameters_module }, .{ .name = "build_options", .module = build_options_module }, - .{ .name = "initrd", .module = initrd_module }, + .{ .name = "initial-ramdisk", .module = initial_ramdisk_module }, }, }), }); @@ -220,37 +239,37 @@ pub fn build(b: *std.Build) void { // Built by the shared user-binary recipe (see addUserBinary): freestanding, // linked into the kernel's user region against the `runtime` runtime library, and // started in ring 3 by the kernel's user-ELF loader. - const init_exe = addUserBinary(b, kernel_target, runtime_module, "init", "system/services/init/init.zig"); + const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "init", "system/services/init/init.zig"); b.installArtifact(init_exe); - // --- initrd: a bundle of extra user binaries (VFS server + drivers) --- + // --- initial_ramdisk: a bundle of extra user binaries (VFS server + drivers) --- // Each is built by the same user-binary recipe, then packed into one image by - // the host-side mkinitrd tool. The bootloader ferries the image to the kernel, - // which unpacks it and spawns each program (system/initrd.zig). - const vfs_exe = addUserBinary(b, kernel_target, runtime_module, "vfs", "system/services/vfs/vfs.zig"); - const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, "vfs-test", "system/services/vfs/vfs-test.zig"); - const hpetd_exe = addUserBinary(b, kernel_target, runtime_module, "hpetd", "system/drivers/hpetd/hpetd.zig"); - const busd_exe = addUserBinary(b, kernel_target, runtime_module, "busd", "system/drivers/busd/busd.zig"); + // the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel, + // which unpacks it and spawns each program (system/initial-ramdisk.zig). + const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "vfs", "system/services/vfs/vfs.zig"); + const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "vfs-test", "system/services/vfs/vfs-test.zig"); + const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "hpet", "system/drivers/hpet/hpet.zig"); + const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "bus", "system/drivers/bus/bus.zig"); - // Pack the user binaries into the initrd image with the host-side Python tool + // Pack the user binaries into the initial_ramdisk image with the host-side Python tool // (the container format is trivial, and Python sidesteps std API churn). Args: - // mkinitrd.py [ ]... — one name/file pair per binary. + // make-initial-ramdisk.py [ ]... — one name/file pair per binary. const mk_run = b.addSystemCommand(&.{"python3"}); - mk_run.addFileArg(b.path("tools/mkinitrd.py")); - const initrd_img = mk_run.addOutputFileArg("initrd.img"); + mk_run.addFileArg(b.path("tools/make-initial-ramdisk.py")); + const initial_ramdisk_img = mk_run.addOutputFileArg("initial-ramdisk.img"); mk_run.addArg("vfs"); mk_run.addFileArg(vfs_exe.getEmittedBin()); mk_run.addArg("vfs-test"); mk_run.addFileArg(vfstest_exe.getEmittedBin()); - mk_run.addArg("hpetd"); - mk_run.addFileArg(hpetd_exe.getEmittedBin()); - mk_run.addArg("busd"); - mk_run.addFileArg(busd_exe.getEmittedBin()); + mk_run.addArg("hpet"); + mk_run.addFileArg(hpet_exe.getEmittedBin()); + mk_run.addArg("bus"); + mk_run.addFileArg(bus_exe.getEmittedBin()); // Install the image to zig-out/bin (so the QEMU test harness picks it up like // the other binaries). The run-x86-64 ESP install is added below. - const initrd_install = b.addInstallFile(initrd_img, "bin/initrd.img"); - b.getInstallStep().dependOn(&initrd_install.step); + const initial_ramdisk_install = b.addInstallFile(initial_ramdisk_img, "bin/initial-ramdisk.img"); + b.getInstallStep().dependOn(&initial_ramdisk_install.step); // Boot methods live in boot/, one per way of getting the kernel running. // Each is its own binary/entry (a loader is built for its own target); today @@ -314,8 +333,8 @@ pub fn build(b: *std.Build) void { const init_install = b.addInstallArtifact(init_exe, .{ .dest_dir = .{ .override = .{ .custom = "esp/sbin" } }, }); - // ...and the initrd (VFS server + drivers) from the volume root. - const initrd_esp_install = b.addInstallFile(initrd_img, "esp/initrd.img"); + // ...and the initial_ramdisk (VFS server + drivers) from the volume root. + const initial_ramdisk_esp_install = b.addInstallFile(initial_ramdisk_img, "esp/initial-ramdisk.img"); // The firmware needs to write NVRAM, so give it a writable copy of the vars. const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars }); @@ -353,7 +372,7 @@ pub fn build(b: *std.Build) void { run_efi.step.dependOn(&efi_install.step); run_efi.step.dependOn(&kernel_install.step); run_efi.step.dependOn(&init_install.step); - run_efi.step.dependOn(&initrd_esp_install.step); + run_efi.step.dependOn(&initial_ramdisk_esp_install.step); const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/run-x86-64-serial0-.log"); run_efi_step.dependOn(&run_efi.step); diff --git a/docs/README.md b/docs/README.md index 9122055..1761ea0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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` | diff --git a/docs/coding-standards.md b/docs/coding-standards.md index 3edbfe5..2ac6dfa 100644 --- a/docs/coding-standards.md +++ b/docs/coding-standards.md @@ -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.) diff --git a/docs/danos-file-system-hierarchy-FSH.md b/docs/danos-file-system-hierarchy-FSH.md index e2024a7..2e96831 100644 --- a/docs/danos-file-system-hierarchy-FSH.md +++ b/docs/danos-file-system-hierarchy-FSH.md @@ -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 diff --git a/docs/driver-model.md b/docs/driver-model.md index ba8eb5c..565fdef 100644 --- a/docs/driver-model.md +++ b/docs/driver-model.md @@ -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 diff --git a/docs/drivers.md b/docs/drivers.md index 509fd62..ca56dc9 100644 --- a/docs/drivers.md +++ b/docs/drivers.md @@ -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 diff --git a/library/posix/posix.zig b/library/posix/posix.zig new file mode 100644 index 0000000..a6af8e7 --- /dev/null +++ b/library/posix/posix.zig @@ -0,0 +1,13 @@ +//! DanOS's POSIX / C compatibility layer — `unistd`, `stdio`, and (later) the C +//! `errno` / `struct stat` / `extern "C"` surface. This is the *one* place POSIX and +//! C spellings are allowed to appear verbatim (see docs/coding-standards.md): a file +//! under library/posix/ *is* the foreign ABI, so it keeps the ABI's names. Everything +//! it touches on the danos side (the VFS protocol, the runtime) uses danos names, +//! which this layer translates to at the boundary. +//! +//! It is layered strictly *over* the runtime: it calls the runtime's IPC and heap, +//! never the kernel's system calls directly. danos-native applications use the +//! runtime; this exists so *POSIX* software can too. + +pub const unistd = @import("unistd.zig"); +pub const stdio = @import("stdio.zig"); diff --git a/library/runtime/stdio.zig b/library/posix/stdio.zig similarity index 98% rename from library/runtime/stdio.zig rename to library/posix/stdio.zig index ddc32ac..4312669 100644 --- a/library/runtime/stdio.zig +++ b/library/posix/stdio.zig @@ -5,7 +5,7 @@ const std = @import("std"); const unistd = @import("unistd.zig"); -const heap = @import("heap.zig"); +const heap = @import("runtime").heap; pub const SEEK_SET = unistd.SEEK_SET; pub const SEEK_CURRENT = unistd.SEEK_CURRENT; diff --git a/library/runtime/unistd.zig b/library/posix/unistd.zig similarity index 88% rename from library/runtime/unistd.zig rename to library/posix/unistd.zig index 8e9f736..4ec9bb5 100644 --- a/library/runtime/unistd.zig +++ b/library/posix/unistd.zig @@ -5,10 +5,10 @@ const std = @import("std"); const protocol = @import("vfs-protocol"); -const ipc = @import("ipc.zig"); +const ipc = @import("runtime").ipc; const danos = @import("danos"); -pub const O_CREAT = protocol.O_CREAT; +pub const O_CREAT = protocol.create; pub const SEEK_SET: u32 = 0; pub const SEEK_CURRENT: u32 = 1; pub const SEEK_END: u32 = 2; @@ -110,11 +110,11 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 { SEEK_SET => 0, SEEK_CURRENT => @intCast(f.offset), SEEK_END => blk: { - const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; - var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined; + const request = protocol.Request{ .operation = .status, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; + var sbuf: [@sizeOf(protocol.FileStatus)]u8 = undefined; const r = transact(request, &.{}, &sbuf) orelse return -1; - if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1; - const st = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]); + if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.FileStatus)) return -1; + const st = std.mem.bytesToValue(protocol.FileStatus, sbuf[0..@sizeOf(protocol.FileStatus)]); break :blk @intCast(st.size); }, else => return -1, @@ -126,17 +126,17 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 { } /// Stat `path`. Returns 0 or -1. -pub fn stat(path: []const u8, out: *protocol.Stat) i32 { +pub fn stat(path: []const u8, out: *protocol.FileStatus) i32 { // Open, stat by node, close — simple and enough for now. const fd = open(path, 0); if (fd < 0) return -1; defer close(fd); const f = fdPtr(fd).?; - const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; - var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined; + const request = protocol.Request{ .operation = .status, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; + var sbuf: [@sizeOf(protocol.FileStatus)]u8 = undefined; const r = transact(request, &.{}, &sbuf) orelse return -1; - if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1; - out.* = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]); + if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.FileStatus)) return -1; + out.* = std.mem.bytesToValue(protocol.FileStatus, sbuf[0..@sizeOf(protocol.FileStatus)]); return 0; } diff --git a/library/runtime/runtime.zig b/library/runtime/runtime.zig index 27be2e8..bf48ba2 100644 --- a/library/runtime/runtime.zig +++ b/library/runtime/runtime.zig @@ -17,9 +17,7 @@ pub const start = @import("start.zig"); /// The VFS wire protocol (shared with the VFS server). pub const vfs_protocol = @import("vfs-protocol"); /// POSIX-style file API: open/read/write/lseek/stat/close. -pub const unistd = @import("unistd.zig"); /// C stdio: fopen/fread/fwrite/fseek/ftell/fclose over unistd. -pub const stdio = @import("stdio.zig"); /// Device access for drivers: enumerate/claim/mmioMap. pub const device = @import("device.zig"); diff --git a/system/danos.zig b/system/danos.zig index 1a9114c..cbaaad4 100644 --- a/system/danos.zig +++ b/system/danos.zig @@ -245,12 +245,12 @@ pub const BootInformation = extern struct { /// The raw `/sbin/init` ELF image, read off the boot volume by the loader /// into memory that survives the handoff (classified reserved, so the kernel /// identity-maps it and never allocates over it). 0/0 = no init found — the - /// kernel boots without user space. Grows into a full initrd handoff later. + /// kernel boots without user space. Grows into a full initial_ramdisk handoff later. init_base: u64 = 0, init_len: u64 = 0, - /// The initrd image (a bundle of extra user binaries — the VFS server and + /// The initial_ramdisk image (a bundle of extra user binaries — the VFS server and /// device drivers), read off the boot volume into memory that survives the - /// handoff, same as `init` above. 0/0 = no initrd. See system/initrd.zig. - initrd_base: u64 = 0, - initrd_len: u64 = 0, + /// handoff, same as `init` above. 0/0 = no initial_ramdisk. See system/initial-ramdisk.zig. + initial_ramdisk_base: u64 = 0, + initial_ramdisk_len: u64 = 0, }; diff --git a/system/devices/aml/aml.zig b/system/devices/aml/aml.zig index 79c9464..c7386ba 100644 --- a/system/devices/aml/aml.zig +++ b/system/devices/aml/aml.zig @@ -3,7 +3,7 @@ //! //! This module has two stages. `parser.zig` walks the entire byte stream and //! records every named object into a namespace tree (`namespace.zig`), capturing -//! method bodies and field/region layout. `interp.zig` then *evaluates* control +//! method bodies and field/region layout. `interpreter.zig` then *evaluates* control //! methods on demand — running operators, control flow, and OperationRegion field //! access — so callers can resolve device status (`_STA`), current resource //! settings (`_CRS`), sleep states (`_Sx`), and the like against the live namespace. @@ -17,10 +17,10 @@ pub const Node = @import("namespace.zig").Node; pub const NodeKind = @import("namespace.zig").NodeKind; /// The AML evaluator: interprets control methods (and reads Names/Fields) far -/// enough for device discovery. See `interp.zig`. -pub const Interpreter = @import("interp.zig").Interpreter; -pub const Object = @import("interp.zig").Object; -pub const EvaluateHal = @import("interp.zig").Hal; +/// enough for device discovery. See `interpreter.zig`. +pub const Interpreter = @import("interpreter.zig").Interpreter; +pub const Object = @import("interpreter.zig").Object; +pub const EvaluateHal = @import("interpreter.zig").Hal; /// The SLP_TYP values written to PM1a/PM1b control to enter a sleep state. pub const SleepType = struct { diff --git a/system/devices/aml/interp.zig b/system/devices/aml/interpreter.zig similarity index 100% rename from system/devices/aml/interp.zig rename to system/devices/aml/interpreter.zig diff --git a/system/drivers/busd/busd.zig b/system/drivers/bus/bus.zig similarity index 82% rename from system/drivers/busd/busd.zig rename to system/drivers/bus/bus.zig index 6274b68..e3decf8 100644 --- a/system/drivers/busd/busd.zig +++ b/system/drivers/bus/bus.zig @@ -1,4 +1,4 @@ -//! /sbin/busd — a user-space **bus driver**, and the smallest honest example of one. +//! /sbin/bus — a user-space **bus driver**, and the smallest honest example of one. //! //! A bus driver owns a device that *contains other devices*, enumerates them by some //! bus-specific protocol, and publishes each one into the kernel's device table so a @@ -6,10 +6,10 @@ //! the "bus" is the HPET's register block and the "devices" are its comparators, each //! a 0x20-byte window at 0x100 + 0x20*n that can be driven independently. //! -//! It's a toy bus, but nothing about the mechanism is: `busd` reads how many children +//! It's a toy bus, but nothing about the mechanism is: `bus` reads how many children //! exist from the hardware (GENERAL_CAP bits [12:8]), publishes one `DeviceDescriptor` per //! child with a sub-window of its own MMIO plus the shared IRQ, and the kernel checks -//! every one of those resources is contained in what `busd` was granted. A comparator +//! every one of those resources is contained in what `bus` was granted. A comparator //! driver then claims a child and maps only *its* registers — not the whole block. //! //! It also proves the negative: registering a child whose window escapes the parent's @@ -65,30 +65,30 @@ fn firstChildOf(buffer: []device.DeviceDescriptor, total: usize, parent_id: u64) pub fn main() void { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { - _ = runtime.system.write("busd: out of memory\n"); + _ = runtime.system.write("bus: out of memory\n"); return; }; const parent = findHpet(buffer) orelse { - _ = runtime.system.write("busd: no HPET\n"); + _ = runtime.system.write("bus: no HPET\n"); return; }; const resource = resourcesOf(parent); // Claim the bus. Everything below is subdivision of what this claim granted. // - // Claims are exclusive, and at a normal boot the kernel spawns every initrd - // binary — so hpetd may own the HPET already. That's not an error, it's the + // Claims are exclusive, and at a normal boot the kernel spawns every initial_ramdisk + // binary — so hpet may own the HPET already. That's not an error, it's the // capability model working: exit quietly and leave the device to its owner. The - // `bus` test spawns busd alone, so there it wins the claim. + // `bus` test spawns bus alone, so there it wins the claim. if (!device.claim(parent.id)) { - _ = runtime.system.write("busd: HPET already claimed by another driver, nothing to do\n"); + _ = runtime.system.write("bus: HPET already claimed by another driver, nothing to do\n"); return; } // Enumerate the bus: ask the hardware how many children it has. const base = device.mmioMap(parent.id, 0) orelse { - _ = runtime.system.write("busd: mmio_map failed\n"); + _ = runtime.system.write("bus: mmio_map failed\n"); return; }; const cap: *volatile u64 = @ptrFromInt(base + register_general_cap); @@ -112,7 +112,7 @@ pub fn main() void { } if (device.register(parent.id, &child) == null) { - _ = runtime.system.write("busd: register failed\n"); + _ = runtime.system.write("bus: register failed\n"); return; } published += 1; @@ -133,11 +133,11 @@ pub fn main() void { .len = 0x1000, }; if (device.register(parent.id, &rogue) != null) { - _ = runtime.system.write("busd: FAIL out-of-window child was accepted\n"); + _ = runtime.system.write("bus: FAIL out-of-window child was accepted\n"); return; } if (device.enumerate(buffer) != before) { - _ = runtime.system.write("busd: FAIL rogue child leaked into the table\n"); + _ = runtime.system.write("bus: FAIL rogue child leaked into the table\n"); return; } @@ -149,18 +149,18 @@ pub fn main() void { if (d.parent != parent.id) continue; const w = d.resources[0]; if (w.start < resource.mmio.start or w.len >= resource.mmio.len) { - _ = runtime.system.write("busd: FAIL child window is not inside the bus\n"); + _ = runtime.system.write("bus: FAIL child window is not inside the bus\n"); return; } seen += 1; } if (seen != published) { - _ = runtime.system.write("busd: FAIL child count mismatch\n"); + _ = runtime.system.write("bus: FAIL child count mismatch\n"); return; } // Delegation, end to end: claim a child and map *it*. A real class driver would be - // a different process; here busd plays both parts, which exercises the same path. + // a different process; here bus plays both parts, which exercises the same path. // The child's window is 0x20 bytes at parent+0x100, so the register it sees at // offset 0 must be the same timer-0 configuration register the bus sees at 0x100. // @@ -168,21 +168,21 @@ pub fn main() void { // 4 KiB the HPET lives in — the granularity limit documented in docs/drivers.md. // The *resource* is narrow even though the page isn't.) const child_id = firstChildOf(buffer, device.enumerate(buffer), parent.id) orelse { - _ = runtime.system.write("busd: FAIL no child to claim\n"); + _ = runtime.system.write("bus: FAIL no child to claim\n"); return; }; if (!device.claim(child_id)) { - _ = runtime.system.write("busd: FAIL could not claim own child\n"); + _ = runtime.system.write("bus: FAIL could not claim own child\n"); return; } const child_base = device.mmioMap(child_id, 0) orelse { - _ = runtime.system.write("busd: FAIL child mmio_map refused\n"); + _ = runtime.system.write("bus: FAIL child mmio_map refused\n"); return; }; const via_child: *volatile u64 = @ptrFromInt(child_base); const via_bus: *volatile u64 = @ptrFromInt(base + 0x100); if (via_child.* != via_bus.*) { - _ = runtime.system.write("busd: FAIL child window does not alias the bus register\n"); + _ = runtime.system.write("bus: FAIL child window does not alias the bus register\n"); return; } @@ -195,12 +195,12 @@ pub fn main() void { _ = runtime.system.munmap(scratch, 0x1000); const descriptor: *const device.DeviceDescriptor = @ptrFromInt(scratch); if (device.register(parent.id, descriptor) != null) { - _ = runtime.system.write("busd: FAIL register accepted an unmapped descriptor\n"); + _ = runtime.system.write("bus: FAIL register accepted an unmapped descriptor\n"); return; } } - _ = runtime.system.write("busd: ok\n"); + _ = runtime.system.write("bus: ok\n"); while (true) runtime.system.sleep(1000); } diff --git a/system/drivers/hpetd/hpetd.zig b/system/drivers/hpet/hpet.zig similarity index 87% rename from system/drivers/hpetd/hpetd.zig rename to system/drivers/hpet/hpet.zig index affd26f..1ee70b2 100644 --- a/system/drivers/hpetd/hpetd.zig +++ b/system/drivers/hpet/hpet.zig @@ -1,4 +1,4 @@ -//! /sbin/hpetd — a user-space HPET driver. It proves the whole driver model end to +//! /sbin/hpet — a user-space HPET driver. It proves the whole driver model end to //! end: enumerate the device table, find the HPET, claim it, map its registers into //! this ring-3 address space (strong-uncacheable), **bind its interrupt to an IPC //! endpoint**, then sit blocked in `replyWait` until the hardware wakes it. @@ -13,8 +13,8 @@ //! the full cycle to be correct: //! //! kernel ISR mask the GSI -> EOI -> notify this endpoint -//! hpetd wake, clear GENERAL_INT_STATUS (deasserts the line), re-arm -//! hpetd irq_ack -> kernel unmasks the GSI +//! hpet wake, clear GENERAL_INT_STATUS (deasserts the line), re-arm +//! hpet irq_ack -> kernel unmasks the GSI //! //! Clear the status bit *before* acking, or the line is still asserted when the //! kernel unmasks and the I/O APIC redelivers forever. @@ -64,7 +64,7 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found { for (buffer[0..n]) |d| { if (d.class != @intFromEnum(device.DeviceClass.timer)) continue; // Skip comparator children a bus driver may have published below the block - // (see system/drivers/busd/busd.zig) — we want the register block itself. + // (see system/drivers/bus/bus.zig) — we want the register block itself. if (d.parent != device.no_parent) continue; var mmio: ?u64 = null; var irq: ?u64 = null; @@ -85,21 +85,21 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found { pub fn main() void { // Enumerate into a heap buffer (too big for the one-page user stack). const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch { - _ = runtime.system.write("hpetd: out of memory\n"); + _ = runtime.system.write("hpet: out of memory\n"); return; }; const hpet = findHpet(buffer) orelse { - _ = runtime.system.write("hpetd: no HPET with an IRQ\n"); + _ = runtime.system.write("hpet: no HPET with an IRQ\n"); return; }; if (!device.claim(hpet.device_id)) { - _ = runtime.system.write("hpetd: claim failed\n"); + _ = runtime.system.write("hpet: claim failed\n"); return; } const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse { - _ = runtime.system.write("hpetd: mmio_map failed\n"); + _ = runtime.system.write("hpet: mmio_map failed\n"); return; }; @@ -108,7 +108,7 @@ pub fn main() void { const gsi = hpet.gsi; const endpoint = ipc.createEndpoint() orelse { - _ = runtime.system.write("hpetd: create_endpoint failed\n"); + _ = runtime.system.write("hpet: create_endpoint failed\n"); return; }; @@ -116,7 +116,7 @@ pub fn main() void { // Counter period, so we can arm the comparator a fixed wall-clock distance out. const femtos_per_tick = register(base, register_general_cap).* >> 32; if (femtos_per_tick == 0) { - _ = runtime.system.write("hpetd: bad HPET period\n"); + _ = runtime.system.write("hpet: bad HPET period\n"); return; } const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick; @@ -139,10 +139,10 @@ pub fn main() void { register(base, register_general_configuration).* |= configuration_enable; if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) { - _ = runtime.system.write("hpetd: irq_bind failed\n"); + _ = runtime.system.write("hpet: irq_bind failed\n"); return; } - _ = runtime.system.write("hpetd: bound, sleeping until the hardware speaks\n"); + _ = runtime.system.write("hpet: bound, sleeping until the hardware speaks\n"); // --- the driver loop ----------------------------------------------------- // Blocked in replyWait. No polling, no spinning: the next line of this function @@ -170,14 +170,14 @@ pub fn main() void { register(base, register_timer0_configuration).* &= ~tn_int_enb; } - _ = runtime.system.write("hpetd: irq\n"); + _ = runtime.system.write("hpet: irq\n"); if (!device.irqAck(hpet.device_id, hpet.irq)) { - _ = runtime.system.write("hpetd: irq_ack failed\n"); + _ = runtime.system.write("hpet: irq_ack failed\n"); return; } } - _ = runtime.system.write("hpetd: ok\n"); + _ = runtime.system.write("hpet: ok\n"); while (true) runtime.system.sleep(1000); } diff --git a/system/initrd.zig b/system/initial-ramdisk.zig similarity index 86% rename from system/initrd.zig rename to system/initial-ramdisk.zig index 04164e9..487c280 100644 --- a/system/initrd.zig +++ b/system/initial-ramdisk.zig @@ -1,5 +1,5 @@ -//! The initrd (initial ramdisk) container format — shared by the build-time -//! packer (tools/mkinitrd.zig) and the kernel that unpacks it. Deliberately +//! The initial_ramdisk (initial ramdisk) container format — shared by the build-time +//! packer (tools/make-initial-ramdisk.py) and the kernel that unpacks it. Deliberately //! trivial: a header, a table of fixed-size entries, then the concatenated file //! blobs. We own both producer and consumer, so it need be no fancier. //! @@ -10,7 +10,7 @@ const std = @import("std"); -/// "DNRD" — identifies a danos initrd image. +/// "DNRD" — identifies a danos initial_ramdisk image. pub const magic: u32 = 0x444E5244; pub const Header = extern struct { @@ -24,7 +24,7 @@ pub const Entry = extern struct { len: u64, // blob length in bytes }; -/// A validated view over an initrd image. `init` checks the magic and that the +/// A validated view over an initial_ramdisk image. `init` checks the magic and that the /// entry table fits; `entry` bounds-checks each blob against the image. pub const Reader = struct { image: []const u8, diff --git a/system/kernel/device-service.zig b/system/kernel/devices-broker.zig similarity index 100% rename from system/kernel/device-service.zig rename to system/kernel/devices-broker.zig diff --git a/system/kernel/irq.zig b/system/kernel/irq.zig index bdbc879..f1ec2db 100644 --- a/system/kernel/irq.zig +++ b/system/kernel/irq.zig @@ -22,7 +22,7 @@ //! //! Binding is capability-gated exactly like `mmio_map`: the caller must have //! `device_claim`ed the device, and the GSI must come from one of that device's `irq` -//! resources in the discovered device table (system/kernel/device-service.zig). A driver can +//! resources in the discovered device table (system/kernel/devices-broker.zig). A driver can //! therefore never bind an interrupt it doesn't own — a raw-GSI system_call would let //! any process steal the keyboard's line. //! @@ -152,7 +152,7 @@ pub fn bind(gsi: u32, endpoint: *ipc_sync.Endpoint, owner: u32) BindError!void { bound_owner[gsi] = owner; // Level-triggered, active-high. Level is the general case a driver must survive - // (and what hpetd configures its comparator for); an edge source simply never + // (and what hpet configures its comparator for); an edge source simply never // leaves the line asserted, so the mask/ack cycle is harmless there. // // Hardcoded for now: a device whose MADT interrupt-source override declares the diff --git a/system/kernel/main.zig b/system/kernel/main.zig index a07cb4a..6ab15a7 100644 --- a/system/kernel/main.zig +++ b/system/kernel/main.zig @@ -8,9 +8,9 @@ const pmm = @import("pmm.zig"); const heap = @import("heap.zig"); const scheduler = @import("scheduler.zig"); const process = @import("process.zig"); -const device_service = @import("device-service.zig"); +const devices_broker = @import("devices-broker.zig"); const irq = @import("irq.zig"); -const initrd = @import("initrd"); +const initial_ramdisk = @import("initial-ramdisk"); const platform = @import("platform"); const tests = @import("tests.zig"); const build_options = @import("build_options"); @@ -161,10 +161,10 @@ fn kmain(boot_information: *const BootInformation) noreturn { // Snapshot the device tree for user-space drivers (device_enumerate/claim/ // mmio_map operate on this flat, id-indexed table + claim map). - device_service.init(&device_tree); - if (device_service.dropped > 0) { + devices_broker.init(&device_tree); + if (devices_broker.dropped > 0) { // Otherwise entirely silent: drivers would just never see that hardware. - log.print("danos: WARNING {d} device(s) dropped — table full\n", .{device_service.dropped}); + log.print("danos: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped}); } // Install the device-IRQ trampolines, so a driver's irq_bind has vectors to @@ -285,10 +285,10 @@ fn kmain(boot_information: *const BootInformation) noreturn { status("no /sbin/init on the boot volume.\n"); } - // Spawn the extra user binaries the loader ferried in the initrd (the VFS + // Spawn the extra user binaries the loader ferried in the initial_ramdisk (the VFS // server, and later device drivers). For now the kernel launches them all; // once init is a real service supervisor it will spawn them itself (system_spawn). - startInitrdBinaries(boot_information); + startInitialRamdiskBinaries(boot_information); // Become the idle task: drop below every real task and halt until an // interrupt. The timer keeps preempting into init and any other work. @@ -297,22 +297,22 @@ fn kmain(boot_information: *const BootInformation) noreturn { architecture.halt(); } -/// Spawn every program bundled in the initrd as its own ring-3 process. A bad +/// Spawn every program bundled in the initial_ramdisk as its own ring-3 process. A bad /// image or a program that fails to load is logged and skipped — the rest of the /// system still runs. -fn startInitrdBinaries(boot_information: *const danos.BootInformation) void { - if (boot_information.initrd_len == 0) return; - const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; - const rd = initrd.Reader.init(image) orelse { - status("initrd: bad image, skipping\n"); +fn startInitialRamdiskBinaries(boot_information: *const danos.BootInformation) void { + if (boot_information.initial_ramdisk_len == 0) return; + const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; + const rd = initial_ramdisk.Reader.init(image) orelse { + status("initial_ramdisk: bad image, skipping\n"); return; }; var i: u32 = 0; while (i < rd.count) : (i += 1) { const item = rd.entry(i) orelse continue; - statusPrint("starting /sbin/{s} (from initrd)...\n", .{item.name}); + statusPrint("starting /sbin/{s} (from initial_ramdisk)...\n", .{item.name}); process.spawnProcess(item.blob, 4) catch |err| { - statusPrint("initrd: {s} failed to load: {s}\n", .{ item.name, @errorName(err) }); + statusPrint("initial_ramdisk: {s} failed to load: {s}\n", .{ item.name, @errorName(err) }); }; } } diff --git a/system/kernel/process.zig b/system/kernel/process.zig index c87231c..67b6550 100644 --- a/system/kernel/process.zig +++ b/system/kernel/process.zig @@ -27,7 +27,7 @@ const pmm = @import("pmm.zig"); const scheduler = @import("scheduler.zig"); const sync = @import("sync.zig"); const ipc = @import("ipc-synchronous.zig"); -const device_service = @import("device-service.zig"); +const devices_broker = @import("devices-broker.zig"); const irq = @import("irq.zig"); const log = @import("log.zig"); @@ -206,12 +206,12 @@ fn systemDeviceEnumerate(state: *architecture.CpuState) void { const sz = @sizeOf(danos.DeviceDescriptor); const cap = @min(maximum, (user_half_end - buffer_ptr) / sz); // clamp to the user half const out: [*]danos.DeviceDescriptor = @ptrFromInt(buffer_ptr); - architecture.setSystemCallResult(state, device_service.enumerate(out[0..@intCast(cap)])); + architecture.setSystemCallResult(state, devices_broker.enumerate(out[0..@intCast(cap)])); } /// device_claim(id) -> 0/-1: take exclusive ownership of a device for this process. fn systemDeviceClaim(state: *architecture.CpuState) void { - if (device_service.claim(architecture.systemCallArg(state, 0), scheduler.current().id)) + if (devices_broker.claim(architecture.systemCallArg(state, 0), scheduler.current().id)) architecture.setSystemCallResult(state, 0) else fail(state); @@ -225,9 +225,9 @@ fn systemMmioMap(state: *architecture.CpuState) void { const resource_index = architecture.systemCallArg(state, 1); const t = scheduler.current(); if (t.aspace == 0) return fail(state); - const owner = device_service.ownerOf(device_id) orelse return fail(state); + const owner = devices_broker.ownerOf(device_id) orelse return fail(state); if (owner != t.id) return fail(state); // not claimed by this process - const r = device_service.resourceOf(device_id, resource_index) orelse return fail(state); + const r = devices_broker.resourceOf(device_id, resource_index) orelse return fail(state); if (r.kind != @intFromEnum(danos.ResourceKind.memory)) return fail(state); if (t.device_map_next == 0) t.device_map_next = device_arena_base; @@ -263,7 +263,7 @@ fn systemDeviceRegister(state: *architecture.CpuState) void { var descriptor: danos.DeviceDescriptor = undefined; if (!ipc.copyFromUser(t.aspace, descriptor_ptr, std.mem.asBytes(&descriptor))) return fail(state); - const id = device_service.register(parent_id, t.id, &descriptor) catch return fail(state); + const id = devices_broker.register(parent_id, t.id, &descriptor) catch return fail(state); architecture.setSystemCallResult(state, id); } @@ -281,9 +281,9 @@ fn releaseIrqs(t: *scheduler.Task) void { /// by discovery. Neither a raw GSI nor an unclaimed device can get through — which /// is why irq_bind takes a resource index and not an interrupt number. fn ownedGsi(t: *scheduler.Task, device_id: u64, resource_index: u64) ?u32 { - const owner = device_service.ownerOf(device_id) orelse return null; + const owner = devices_broker.ownerOf(device_id) orelse return null; if (owner != t.id) return null; - const r = device_service.resourceOf(device_id, resource_index) orelse return null; + const r = devices_broker.resourceOf(device_id, resource_index) orelse return null; if (r.kind != @intFromEnum(danos.ResourceKind.irq)) return null; if (r.start >= irq.maximum_gsi) return null; return @intCast(r.start); diff --git a/system/kernel/tests.zig b/system/kernel/tests.zig index 1df4f0b..02d275e 100644 --- a/system/kernel/tests.zig +++ b/system/kernel/tests.zig @@ -12,7 +12,7 @@ const std = @import("std"); const danos = @import("danos"); const architecture = @import("architecture"); -const device_service = @import("device-service.zig"); +const devices_broker = @import("devices-broker.zig"); const platform = @import("platform"); const pmm = @import("pmm.zig"); const heap = @import("heap.zig"); @@ -22,7 +22,7 @@ const ipcsync = @import("ipc-synchronous.zig"); const irq = @import("irq.zig"); const sync = @import("sync.zig"); const process = @import("process.zig"); -const initrd = @import("initrd"); +const initial_ramdisk = @import("initial-ramdisk"); /// Formatted write straight to serial, independent of the framebuffer console. fn log(comptime fmt: []const u8, args: anytype) void { @@ -108,8 +108,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void { initTest(boot_information); } else if (eql(case, "process")) { processTest(boot_information); - } else if (eql(case, "initrd")) { - initrdTest(boot_information); + } else if (eql(case, "initial-ramdisk")) { + initialRamdiskTest(boot_information); } else if (eql(case, "vfs")) { vfsTest(boot_information); } else if (eql(case, "hpet")) { @@ -952,24 +952,24 @@ fn initTest(boot_information: *const BootInformation) void { result(); } -/// The initrd path: the bootloader handed over an image bundling extra user +/// The initial_ramdisk path: the bootloader handed over an image bundling extra user /// binaries; parse it, spawn every program, and confirm one (the vfs stub) /// reaches ring 3 and heartbeats — proving the whole ferry-parse-spawn pipeline. -fn initrdTest(boot_information: *const BootInformation) void { - log("DANOS-TEST-BEGIN: initrd\n", .{}); - check("bootloader handed over an initrd", boot_information.initrd_len != 0); - if (boot_information.initrd_len == 0) { +fn initialRamdiskTest(boot_information: *const BootInformation) void { + log("DANOS-TEST-BEGIN: initial_ramdisk\n", .{}); + check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0); + if (boot_information.initial_ramdisk_len == 0) { result(); return; } - const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; - const rd = initrd.Reader.init(image) orelse { - check("initrd image is valid", false); + const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; + const rd = initial_ramdisk.Reader.init(image) orelse { + check("initial_ramdisk image is valid", false); result(); return; }; - check("initrd image is valid", true); - check("initrd contains at least one binary", rd.count >= 1); + check("initial_ramdisk image is valid", true); + check("initial_ramdisk contains at least one binary", rd.count >= 1); process.write_count = 0; process.write_from_user = false; @@ -981,7 +981,7 @@ fn initrdTest(boot_information: *const BootInformation) void { log("DANOS-INITRD-ERR: {s}: {s}\n", .{ item.name, @errorName(err) }); } } - check("every initrd binary spawned", spawned == rd.count); + check("every initial_ramdisk binary spawned", spawned == rd.count); // Wait for the spawned programs to run and make syscalls (they write + sleep). scheduler.setPriority(1); @@ -989,33 +989,33 @@ fn initrdTest(boot_information: *const BootInformation) void { while (process.write_count < 2 and architecture.millis() < deadline) scheduler.yield(); scheduler.setPriority(4); - check("initrd processes ran and made syscalls (>=2)", process.write_count >= 2); + check("initial_ramdisk processes ran and made syscalls (>=2)", process.write_count >= 2); check("syscalls came from user mode (CPL 3)", process.write_from_user); result(); } /// The full VFS path: spawn the user-space VFS server and a client from the -/// initrd. The client opens a file through the runtime file API, writes, seeks, reads +/// initial_ramdisk. The client opens a file through the runtime file API, writes, seeks, reads /// it back, and — only if the round trip matched — heartbeats "vfstest: ok". So /// seeing that marker proves client open/write/read reached the server over IPC /// and came back correct. (The client retries until the server registers.) fn vfsTest(boot_information: *const BootInformation) void { log("DANOS-TEST-BEGIN: vfs\n", .{}); - if (boot_information.initrd_len == 0) { - check("bootloader handed over an initrd", false); + if (boot_information.initial_ramdisk_len == 0) { + check("bootloader handed over an initial_ramdisk", false); result(); return; } - const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; - const rd = initrd.Reader.init(image) orelse { - check("initrd image is valid", false); + const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; + const rd = initial_ramdisk.Reader.init(image) orelse { + check("initial_ramdisk image is valid", false); result(); return; }; process.write_count = 0; process.write_from_user = false; - // Spawn just the server and its client (other initrd binaries would write to + // Spawn just the server and its client (other initial_ramdisk binaries would write to // the shared evidence buffer and confuse the marker check). _ = spawnNamed(rd, "vfs"); _ = spawnNamed(rd, "vfs-test"); @@ -1037,9 +1037,9 @@ fn vfsTest(boot_information: *const BootInformation) void { result(); } -/// Spawn the initrd binary named `name` as a ring-3 process. Returns false if it +/// Spawn the initial_ramdisk binary named `name` as a ring-3 process. Returns false if it /// isn't in the image or fails to load. -fn spawnNamed(rd: initrd.Reader, name: []const u8) bool { +fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool { var i: u32 = 0; while (i < rd.count) : (i += 1) { const item = rd.entry(i) orelse continue; @@ -1051,36 +1051,36 @@ fn spawnNamed(rd: initrd.Reader, name: []const u8) bool { } /// IO passthrough + IRQ-as-IPC: a user-space driver drives real hardware and is -/// *woken by it*. Spawn hpetd, which claims the HPET, maps its registers into its +/// *woken by it*. Spawn hpet, which claims the HPET, maps its registers into its /// own ring-3 address space, arms a level-triggered comparator, binds the interrupt -/// to an IPC endpoint, and then blocks. It prints "hpetd: ok" only after being woken +/// to an IPC endpoint, and then blocks. It prints "hpet: ok" only after being woken /// `target_ticks` times — it cannot reach that line by polling, because the loop's /// only exit is through `replyWait` returning a notification badge. /// -/// The interesting assertion is the last one, which doesn't trust hpetd at all: it +/// The interesting assertion is the last one, which doesn't trust hpet at all: it /// reads the I/O APIC's redirection entry back and checks the kernel really routed /// the line (our vector, level-triggered) and really left it unmasked after the -/// driver's final `irq_ack`. hpetd disables its comparator on the last interrupt, so +/// driver's final `irq_ack`. hpet disables its comparator on the last interrupt, so /// that state is quiescent and not a race. fn hpetTest(boot_information: *const BootInformation) void { log("DANOS-TEST-BEGIN: hpet\n", .{}); - if (boot_information.initrd_len == 0) { - check("bootloader handed over an initrd", false); + if (boot_information.initial_ramdisk_len == 0) { + check("bootloader handed over an initial_ramdisk", false); result(); return; } - const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; - const rd = initrd.Reader.init(image) orelse { - check("initrd image is valid", false); + const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; + const rd = initial_ramdisk.Reader.init(image) orelse { + check("initial_ramdisk image is valid", false); result(); return; }; process.write_count = 0; process.write_from_user = false; - check("hpetd spawned from the initrd", spawnNamed(rd, "hpetd")); + check("hpet spawned from the initial_ramdisk", spawnNamed(rd, "hpet")); - const prefix = "hpetd: ok"; + const prefix = "hpet: ok"; scheduler.setPriority(1); const deadline = architecture.millis() + 10000; while (architecture.millis() < deadline) { @@ -1114,7 +1114,7 @@ fn hpetRouteOk() bool { /// The GSI discovery recorded for the HPET, from the same device table the driver saw. fn hpetGsi() ?u32 { var buffer: [16]danos.DeviceDescriptor = undefined; - const n = @min(device_service.enumerate(&buffer), buffer.len); + const n = @min(devices_broker.enumerate(&buffer), buffer.len); for (buffer[0..n]) |d| { if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue; if (d.parent != danos.no_parent) continue; // the block, not a comparator child @@ -1130,34 +1130,34 @@ fn hpetGsi() ?u32 { /// them from the hardware, and publishes each as a child via `device_register` — the /// primitive a PCI bridge or USB hub driver is built from. /// -/// `busd` treats the HPET's register block as a bus and its comparators as children, +/// `bus` treats the HPET's register block as a bus and its comparators as children, /// giving each a 0x20 sub-window. It checks its own work (children come back from the /// table with the right parent and a strictly narrower window) and, importantly, that /// the kernel **refuses** a child whose window escapes the parent's — without that, /// `device_register` would be a system_call for mapping arbitrary physical memory. It prints -/// "busd: ok" only if all of that holds. +/// "bus: ok" only if all of that holds. /// -/// The kernel-side check here is the one busd can't make: that the children really did +/// The kernel-side check here is the one bus can't make: that the children really did /// land in the device table with the containment invariant intact. fn busTest(boot_information: *const BootInformation) void { log("DANOS-TEST-BEGIN: bus\n", .{}); - if (boot_information.initrd_len == 0) { - check("bootloader handed over an initrd", false); + if (boot_information.initial_ramdisk_len == 0) { + check("bootloader handed over an initial_ramdisk", false); result(); return; } - const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; - const rd = initrd.Reader.init(image) orelse { - check("initrd image is valid", false); + const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; + const rd = initial_ramdisk.Reader.init(image) orelse { + check("initial_ramdisk image is valid", false); result(); return; }; process.write_count = 0; process.write_from_user = false; - check("busd spawned from the initrd", spawnNamed(rd, "busd")); + check("bus spawned from the initial_ramdisk", spawnNamed(rd, "bus")); - const prefix = "busd: ok"; + const prefix = "bus: ok"; scheduler.setPriority(1); const deadline = architecture.millis() + 10000; while (architecture.millis() < deadline) { @@ -1173,7 +1173,7 @@ fn busTest(boot_information: *const BootInformation) void { result(); } -/// Every child `busd` registered must have each of its resources inside a parent +/// Every child `bus` registered must have each of its resources inside a parent /// resource of the same kind — the invariant `device_register` exists to maintain, /// checked from the kernel's own table rather than the driver's word for it. /// @@ -1182,7 +1182,7 @@ fn busTest(boot_information: *const BootInformation) void { /// bridge's `bus_range`, because a bus-number range isn't an address window. fn childrenContained() bool { var buffer: [64]danos.DeviceDescriptor = undefined; - const n = @min(device_service.enumerate(&buffer), buffer.len); + const n = @min(devices_broker.enumerate(&buffer), buffer.len); const bus_id = hpetDeviceId() orelse return false; const p = buffer[@intCast(bus_id)]; @@ -1205,13 +1205,13 @@ fn childrenContained() bool { if (!ok) return false; } } - return children > 0; // busd must have published at least one + return children > 0; // bus must have published at least one } -/// Device id of the HPET (the bus busd claims), from the same table drivers see. +/// Device id of the HPET (the bus bus claims), from the same table drivers see. fn hpetDeviceId() ?u64 { var buffer: [64]danos.DeviceDescriptor = undefined; - const n = @min(device_service.enumerate(&buffer), buffer.len); + const n = @min(devices_broker.enumerate(&buffer), buffer.len); for (buffer[0..n]) |d| { if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue; if (d.parent != danos.no_parent) continue; // a comparator child, not the block @@ -1226,7 +1226,7 @@ fn hpetDeviceId() ?u64 { /// (so a dead driver's device goes quiet instead of storming) and the slot cleared /// (so an ISR never posts a notification into the endpoint that is about to be freed). /// -/// This is the path `hpetd` never takes — it runs forever — so it gets its own test. +/// This is the path `hpet` never takes — it runs forever — so it gets its own test. /// Two properties, both read back from the hardware rather than from our own state: /// /// 1. A bound GSI is routed and unmasked. diff --git a/system/services/vfs/protocol.zig b/system/services/vfs/protocol.zig index a5083fb..397be77 100644 --- a/system/services/vfs/protocol.zig +++ b/system/services/vfs/protocol.zig @@ -1,18 +1,22 @@ -//! The VFS wire protocol — the message format spoken between a client (via the -//! `runtime` file API) and the user-space VFS server over IPC. A request is a fixed -//! `Request` header followed by an inline payload (a path, or write bytes); a -//! reply is a fixed `Reply` header followed by an inline payload (read bytes, or -//! a Stat). Everything fits in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes). +//! The VFS wire protocol — the message format spoken between a client (via the file +//! API) and the user-space VFS server over IPC. A request is a fixed `Request` header +//! followed by an inline payload (a path, or write bytes); a reply is a fixed `Reply` +//! header followed by an inline payload (read bytes, or a FileStatus). Everything fits +//! in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes). +//! +//! This is a danos-native contract, so it uses danos names throughout — the POSIX +//! spellings (`stat`, `O_CREAT`, ...) live only in the POSIX layer +//! (library/posix/unistd.zig), which translates to these. //! //! This is user-space only — the kernel knows nothing of files or paths; it only -//! moves the bytes. Shared by library/runtime/unistd.zig (client) and system/services/vfs/vfs.zig (server). +//! moves the bytes. Shared by library/posix/unistd.zig (client) and system/services/vfs/vfs.zig (server). pub const Operation = enum(u32) { open, // open(path) -> node id close, // close(node) read, // read(node, offset, len) -> bytes write, // write(node, offset, bytes) -> count - stat, // stat(node) -> Stat + status, // status(node) -> FileStatus }; /// Request header. `node` is the server-side open-file id (from a prior open); @@ -28,7 +32,7 @@ pub const Request = extern struct { /// Reply header. `status` is 0 on success or a negative errno; `node` is the new /// open-file id (for `open`); `len` is the payload length (bytes read, or the -/// Stat size). +/// FileStatus size). pub const Reply = extern struct { status: i32, _padding: u32 = 0, @@ -37,7 +41,9 @@ pub const Reply = extern struct { _padding2: u32 = 0, }; -pub const Stat = extern struct { +/// A file's metadata (the danos-native answer to a `status` request). The POSIX +/// layer maps this onto `struct stat`. +pub const FileStatus = extern struct { size: u64, kind: u32, _padding: u32 = 0, @@ -49,5 +55,5 @@ pub const reply_size: usize = @sizeOf(Reply); /// Largest inline payload that still fits one IPC message alongside a header. pub const maximum_payload: usize = message_maximum - request_size; -/// Open flags. -pub const O_CREAT: u32 = 1; +/// Open flags (danos-native; the POSIX layer maps `O_CREAT` onto `create`). +pub const create: u32 = 1; diff --git a/system/services/vfs/vfs-test.zig b/system/services/vfs/vfs-test.zig index 00cabfe..1ad0361 100644 --- a/system/services/vfs/vfs-test.zig +++ b/system/services/vfs/vfs-test.zig @@ -1,13 +1,13 @@ //! /sbin/vfstest — a client that proves the VFS round trip end to end: open a //! file through the `runtime` file API, write to it, seek back, read it, and compare. //! On success it heartbeats "vfstest: ok" so the kernel test can observe it; -//! on failure it reports what went wrong. Shipped in the initrd alongside vfs. +//! on failure it reports what went wrong. Shipped in the initial_ramdisk alongside vfs. const std = @import("std"); const runtime = @import("runtime"); pub fn main() void { - const u = runtime.unistd; + const u = @import("posix").unistd; const payload = "hello-vfs"; // The VFS server may not have registered yet — retry open until it's up. diff --git a/system/services/vfs/vfs.zig b/system/services/vfs/vfs.zig index 0d91104..7fb0154 100644 --- a/system/services/vfs/vfs.zig +++ b/system/services/vfs/vfs.zig @@ -1,4 +1,4 @@ -//! /sbin/vfs — the user-space VFS server. Shipped in the initrd, spawned as a +//! /sbin/vfs — the user-space VFS server. Shipped in the initial_ramdisk, spawned as a //! ring-3 process, and reached by every other process through IPC (the `runtime` //! file API marshals open/read/write/stat/close into calls to this server's //! endpoint, published under the well-known `vfs` service id). @@ -101,10 +101,10 @@ fn handle(message: []const u8, out: []u8) usize { if (off + n > nd.size) nd.size = off + n; return writeReply(out, .{ .status = 0, .len = @intCast(n) }, &.{}); }, - .stat => { + .status => { const of = openAt(request.node) orelse return fail(out); - const st = protocol.Stat{ .size = nodes[of.node].size, .kind = 0 }; - return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.Stat) }, std.mem.asBytes(&st)); + const st = protocol.FileStatus{ .size = nodes[of.node].size, .kind = 0 }; + return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.FileStatus) }, std.mem.asBytes(&st)); }, .close => { if (request.node < opens.len) opens[@intCast(request.node)].used = false; diff --git a/test/qemu_test.py b/test/qemu_test.py index e4f4caa..15f471b 100644 --- a/test/qemu_test.py +++ b/test/qemu_test.py @@ -57,9 +57,9 @@ ARCHES = { ], "efi_app": ("EFI/BOOT/BOOTX64.efi", "BOOTX64.efi"), # (dest in ESP, name in zig-out/bin) "kernel": ("kernel", "kernel"), - # Further files shipped on the ESP: the init user program and the initrd + # Further files shipped on the ESP: the init user program and the initial_ramdisk # (VFS server + drivers), both copied from zig-out/bin. - "extra": [("sbin/init", "init"), ("initrd.img", "initrd.img")], + "extra": [("sbin/init", "init"), ("initial-ramdisk.img", "initial-ramdisk.img")], # Built as a function so we can splice in per-run paths. "qemu_args": lambda a, esp, vars_fd, serial: [ "-machine", "q35", "-m", "128M", @@ -178,9 +178,9 @@ CASES = [ "smp": 4, "expect": r"DANOS-TEST-RESULT: PASS", "fail": r"DANOS-TEST-RESULT: FAIL"}, - # The initrd: the loader ferries a bundle of user binaries; the kernel parses + # The initial_ramdisk: the loader ferries a bundle of user binaries; the kernel parses # it and spawns each as a ring-3 process (here the VFS-server stub heartbeats). - {"name": "initrd", + {"name": "initial-ramdisk", "expect": r"DANOS-TEST-RESULT: PASS", "fail": r"DANOS-TEST-RESULT: FAIL"}, # The user-space VFS: a client opens/writes/reads a file through the rt file @@ -202,7 +202,7 @@ CASES = [ "fail": r"DANOS-TEST-RESULT: FAIL"}, # IRQ teardown: an exiting driver's line is masked and its slot cleared (so no # ISR notifies a freed endpoint), and a sibling owner sharing that endpoint - # keeps its own binding. The path hpetd never takes, since it runs forever. + # keeps its own binding. The path hpet never takes, since it runs forever. {"name": "irqfree", "expect": r"DANOS-TEST-RESULT: PASS", "fail": r"DANOS-TEST-RESULT: FAIL"}, diff --git a/tools/mkinitrd.py b/tools/make-initial-ramdisk.py similarity index 78% rename from tools/mkinitrd.py rename to tools/make-initial-ramdisk.py index 22ec573..88a0194 100644 --- a/tools/mkinitrd.py +++ b/tools/make-initial-ramdisk.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Build-time initrd packer. Concatenates user binaries into one image the +"""Build-time initial_ramdisk packer. Concatenates user binaries into one image the bootloader ferries to the kernel. -Usage: mkinitrd.py [ ]... +Usage: make-initial-ramdisk.py [ ]... -Image layout (little-endian), mirroring src/user/proto/initrd.zig: +Image layout (little-endian), mirroring src/user/proto/initial-ramdisk.zig: Header : magic u32 ("DNRD"=0x444E5244), count u32 Entry*N : name [32]u8 (NUL-padded), offset u64, len u64 blobs : each entry's file bytes at its offset @@ -21,7 +21,7 @@ def main() -> int: out_path = sys.argv[1] rest = sys.argv[2:] if len(rest) % 2 != 0: - sys.stderr.write("usage: mkinitrd.py [ ]...\n") + sys.stderr.write("usage: make-initial-ramdisk.py [ ]...\n") return 2 items = [(rest[i], rest[i + 1]) for i in range(0, len(rest), 2)]