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:
Daniel Samson
2026-07-10 13:33:06 +01:00
parent 8754d4e46a
commit ceacc6b514
27 changed files with 308 additions and 254 deletions
+11 -11
View File
@@ -15,8 +15,8 @@ const kernel_file_name = std.unicode.utf8ToUtf16LeStringLiteral("kernel");
/// the FAT driver walks the components itself, so no directory dance needed). /// the FAT driver walks the components itself, so no directory dance needed).
const init_file_name = std.unicode.utf8ToUtf16LeStringLiteral("sbin\\init"); const init_file_name = std.unicode.utf8ToUtf16LeStringLiteral("sbin\\init");
/// Path of the initrd image on the boot volume (the VFS server + drivers). /// Path of the initial_ramdisk image on the boot volume (the VFS server + drivers).
const initrd_file_name = std.unicode.utf8ToUtf16LeStringLiteral("initrd.img"); 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. /// Physical page size, and the sentinel UEFI uses to seek to end-of-file.
const page_size = 4096; const page_size = 4096;
@@ -68,9 +68,9 @@ fn boot() !noreturn {
log(") - booting without user space\r\n"); log(") - booting without user space\r\n");
}; };
// Best effort: the initrd (VFS server + drivers) is optional too. // Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
loadInitrd(bs, &boot_information) catch |err| { loadInitialRamdisk(bs, &boot_information) catch |err| {
log("danos: no initrd ("); log("danos: no initial_ramdisk (");
logBytes(@errorName(err)); logBytes(@errorName(err));
log(")\r\n"); log(")\r\n");
}; };
@@ -396,12 +396,12 @@ fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !
log("danos: sbin/init loaded\r\n"); log("danos: sbin/init loaded\r\n");
} }
/// Ferry the initrd (the VFS server + drivers) to the kernel, same as init. /// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init.
fn loadInitrd(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void { fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void {
const image = try loadFile(bs, initrd_file_name); const image = try loadFile(bs, initial_ramdisk_file_name);
boot_information.initrd_base = @intFromPtr(image.ptr); boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
boot_information.initrd_len = image.len; boot_information.initial_ramdisk_len = image.len;
log("danos: initrd loaded\r\n"); log("danos: initial_ramdisk loaded\r\n");
} }
/// Validate the ELF, copy every PT_LOAD segment to its physical address, and /// Validate the ELF, copy every PT_LOAD segment to its physical address, and
+50 -31
View File
@@ -58,6 +58,7 @@ fn addUserBinary(
b: *std.Build, b: *std.Build,
target: std.Build.ResolvedTarget, target: std.Build.ResolvedTarget,
runtime_module: *std.Build.Module, runtime_module: *std.Build.Module,
posix_module: *std.Build.Module,
name: []const u8, name: []const u8,
root: []const u8, root: []const u8,
) *std.Build.Step.Compile { ) *std.Build.Step.Compile {
@@ -74,6 +75,9 @@ fn addUserBinary(
.stack_protector = false, .stack_protector = false,
.imports = &.{ .imports = &.{
.{ .name = "runtime", .module = runtime_module }, .{ .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"), .root_source_file = b.path("system/services/vfs/protocol.zig"),
}); });
// The user-space runtime library (a nascent libc): system_call wrappers, the // The danos-native user-space runtime: system_call wrappers, the C-convention
// C-convention heap, IPC helpers, the process start shim. Compiled into every // heap, IPC helpers, the process start shim, device access. This is the stable
// user binary (see addUserBinary), so it inherits each exe's `.large` code // application ABI; POSIX compatibility is a separate library on top (see below).
// model — do NOT set a target/code_model here. It imports `danos` for the // Compiled into every user binary (see addUserBinary), so it inherits each exe's
// shared SystemCall numbers and `vfs-protocol` for the file API. // `.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", .{ const runtime_module = b.addModule("runtime", .{
.root_source_file = b.path("library/runtime/runtime.zig"), .root_source_file = b.path("library/runtime/runtime.zig"),
.imports = &.{ .imports = &.{
@@ -157,10 +163,23 @@ pub fn build(b: *std.Build) void {
}, },
}); });
// The initrd container format, shared by the kernel (unpacks it) and the // The POSIX / C compatibility layer, a separate library layered strictly over the
// build-time packer tools/mkinitrd.zig (produces it). No dependencies. // runtime (it calls the runtime's IPC/heap, never system calls directly). This is
const initrd_module = b.addModule("initrd", .{ // the one place POSIX/C spellings are allowed verbatim — see docs/coding-standards.md
.root_source_file = b.path("system/initrd.zig"), // 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 // 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 = "platform", .module = platform_module },
.{ .name = "parameters", .module = parameters_module }, .{ .name = "parameters", .module = parameters_module },
.{ .name = "build_options", .module = build_options_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, // Built by the shared user-binary recipe (see addUserBinary): freestanding,
// linked into the kernel's user region against the `runtime` runtime library, and // linked into the kernel's user region against the `runtime` runtime library, and
// started in ring 3 by the kernel's user-ELF loader. // 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); 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 // 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, // the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel,
// which unpacks it and spawns each program (system/initrd.zig). // which unpacks it and spawns each program (system/initial-ramdisk.zig).
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, "vfs", "system/services/vfs/vfs.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, "vfs-test", "system/services/vfs/vfs-test.zig"); const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_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 hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "hpet", "system/drivers/hpet/hpet.zig");
const busd_exe = addUserBinary(b, kernel_target, runtime_module, "busd", "system/drivers/busd/busd.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: // (the container format is trivial, and Python sidesteps std API churn). Args:
// mkinitrd.py <out> [<name> <file>]... — one name/file pair per binary. // make-initial-ramdisk.py <out> [<name> <file>]... — one name/file pair per binary.
const mk_run = b.addSystemCommand(&.{"python3"}); const mk_run = b.addSystemCommand(&.{"python3"});
mk_run.addFileArg(b.path("tools/mkinitrd.py")); mk_run.addFileArg(b.path("tools/make-initial-ramdisk.py"));
const initrd_img = mk_run.addOutputFileArg("initrd.img"); const initial_ramdisk_img = mk_run.addOutputFileArg("initial-ramdisk.img");
mk_run.addArg("vfs"); mk_run.addArg("vfs");
mk_run.addFileArg(vfs_exe.getEmittedBin()); mk_run.addFileArg(vfs_exe.getEmittedBin());
mk_run.addArg("vfs-test"); mk_run.addArg("vfs-test");
mk_run.addFileArg(vfstest_exe.getEmittedBin()); mk_run.addFileArg(vfstest_exe.getEmittedBin());
mk_run.addArg("hpetd"); mk_run.addArg("hpet");
mk_run.addFileArg(hpetd_exe.getEmittedBin()); mk_run.addFileArg(hpet_exe.getEmittedBin());
mk_run.addArg("busd"); mk_run.addArg("bus");
mk_run.addFileArg(busd_exe.getEmittedBin()); mk_run.addFileArg(bus_exe.getEmittedBin());
// Install the image to zig-out/bin (so the QEMU test harness picks it up like // 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. // the other binaries). The run-x86-64 ESP install is added below.
const initrd_install = b.addInstallFile(initrd_img, "bin/initrd.img"); const initial_ramdisk_install = b.addInstallFile(initial_ramdisk_img, "bin/initial-ramdisk.img");
b.getInstallStep().dependOn(&initrd_install.step); b.getInstallStep().dependOn(&initial_ramdisk_install.step);
// Boot methods live in boot/, one per way of getting the kernel running. // 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 // 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, .{ const init_install = b.addInstallArtifact(init_exe, .{
.dest_dir = .{ .override = .{ .custom = "esp/sbin" } }, .dest_dir = .{ .override = .{ .custom = "esp/sbin" } },
}); });
// ...and the initrd (VFS server + drivers) from the volume root. // ...and the initial_ramdisk (VFS server + drivers) from the volume root.
const initrd_esp_install = b.addInstallFile(initrd_img, "esp/initrd.img"); 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. // The firmware needs to write NVRAM, so give it a writable copy of the vars.
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_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(&efi_install.step);
run_efi.step.dependOn(&kernel_install.step); run_efi.step.dependOn(&kernel_install.step);
run_efi.step.dependOn(&init_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-<timestamp>.log"); 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-<timestamp>.log");
run_efi_step.dependOn(&run_efi.step); run_efi_step.dependOn(&run_efi.step);
+17 -9
View File
@@ -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) system/ → /system danos's own internals (the self-representation)
danos.zig the kernel↔user ABI contract (the `danos` module) 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 kernel/ IPC, memory, scheduling, the private syscall dispatch
architecture/x86_64/ the `architecture` module (never named by generic code) architecture/x86_64/ the `architecture` module (never named by generic code)
devices/ the device model /system/devices reflects (+ aml/) 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 services/ init/ vfs/ system servers → /system/services (vfs/ holds
vfs.zig, vfs-test.zig, protocol.zig) 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 boot/ → /boot the loaders
tools/ test/ host-side build + QEMU test harness tools/ test/ host-side build + QEMU test harness
``` ```
A sub-project exposes its **public interface as a module**: `system/services/vfs/` owns 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 the VFS wire protocol (`protocol.zig`, the `vfs-protocol` module), which the POSIX
file layer imports by name. `usb`/`block` drivers will expose their protocols the same layer imports by name. `usb`/`block` drivers will expose their protocols the same way.
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 ## Source map
@@ -159,14 +166,15 @@ way.
| IPC channels between kernel threads (message passing) | `system/kernel/ipc.zig` | | 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` | | 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` | | 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` | | 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/` | | Hardware discovery (ACPI/device tree) behind one neutral device model | `system/devices/` |
| Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` | | Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` |
| In-kernel test cases | `system/kernel/tests.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/` | | 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/` | | 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` | | Build + `run-x86-64` (QEMU/OVMF) | `build.zig` |
| QEMU integration test harness | `test/qemu_test.py` | | QEMU integration test harness | `test/qemu_test.py` |
+25 -15
View File
@@ -6,7 +6,7 @@ Conventions for danos source. The overriding one, from which most of the rest fo
> abbreviation is an acronym.** > abbreviation is an acronym.**
`interruptDispatch`, not `intDisp`. `message_len`, not `message_len` (`msg` expands, `len` `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 `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 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, 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. Three, and only three.
1. **Foreign ABI names are spelled exactly as the ABI spells them.** A function that 1. **Foreign ABI names are spelled exactly as the ABI spells them — but only inside
*is* the C or POSIX interface keeps its name: `fopen`, `fwrite`, `fread`, `malloc`, the layer that *is* that ABI.** A function that *is* the C or POSIX interface keeps
`calloc`, `realloc`, `free`, `memcpy`, `mmap`, `munmap`, `open`, `read`, `write`, its name: `fopen`, `fwrite`, `fread`, `malloc`, `calloc`, `realloc`, `free`,
`close`, `lseek`, `stat`, `errno`. We don't get to rename `fwrite` to `memcpy`, `mmap`, `munmap`, `open`, `read`, `write`, `close`, `lseek`, `stat`,
`fileWrite` — it wouldn't be `fwrite` any more. This also covers the syscall `errno`, `O_CREAT`. We don't get to rename `fwrite` to `fileWrite` — it wouldn't be
*wrappers* that exist to match those names. It does **not** license inventing new `fwrite` any more.
abbreviated names in that style.
**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, 2. **Zig idioms are spelled the way Zig spells them.** Three names are the language's,
not ours, and are left alone: 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 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. 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 That's all — no Unix-abbreviation exception. The source directories are full words
their conventional names — `src`, `lib`, `sbin`, `bin`, `docs` — as do daemon (`system`, `library`, not `src`/`lib`), and there is no daemon `d` suffix: a driver
programs by their `d` suffix (`hpetd`, `busd`, following `sshd`/`httpd`). These are lives in `system/drivers/` and a service in `system/services/`, so the *location*
names a Unix reader already knows; expanding them fights the convention rather than already says what it is. Encoding the role in the name too (`busd`, `vfsd`) is
serving it. redundant — the program is just `bus`, `vfs`. Don't put in a name what its directory
already tells you.
## A note on collisions ## A note on collisions
@@ -118,11 +128,11 @@ Within those spelling rules, follow Zig's own conventions:
- **Types** — `PascalCase`: `DeviceDescriptor`, `Endpoint`, `WaitQueue`. - **Types** — `PascalCase`: `DeviceDescriptor`, `Endpoint`, `WaitQueue`.
- **Functions** — `camelCase`: `mapUserDeviceInto`, `notifyFromIsr`. - **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`. `notify_badge_bit`.
**File names are `kebab-case`.** A file named for a multi-word thing hyphenates it: **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`, 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 `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.) conventions above — `snake_case` — because it's an identifier, not a filename.)
+4 -4
View File
@@ -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. | | / | 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). | | /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). | | /dev | POSIX Device files (e.g., /dev/null, /dev/disk0, /dev/tty, /dev/random). |
| /etc | Host-specific system-wide configuration files. | | /etc | Host-specific system-wide configuration files. |
| /home | Users' home directories, containing saved files, personal settings, etc. | | /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 | | /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 | 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/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/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 | | /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. | | /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 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 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 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. that program, minus the client half.
The obstacle is not the file type, it is which hardware a ring-3 driver can actually 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. 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 — 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. the one block-shaped thing implementable now, and it needs no driver process.
### Pseudo-devices ### Pseudo-devices
+8 -8
View File
@@ -32,19 +32,19 @@ plain bus driver with no controller — a USB hub — is also a real thing.
## The device table is the spine ## 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 `DeviceDesc`, each with a parent, a class, and a set of resources. Firmware discovery
seeds it ([discovery.md](discovery.md)); `device_register` grows it. seeds it ([discovery.md](discovery.md)); `device_register` grows it.
Three invariants make it a capability system rather than a directory: Three invariants make it a capability system rather than a directory:
1. **A claim is exclusive.** `device_claim(id)` succeeds once. Everything downstream — 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 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 map its `.memory` resources and bind its `.irq` resources. This is why
`device_register` cannot be a free-for-all. `device_register` cannot be a free-for-all.
3. **Therefore: containment.** Every resource of a registered child must lie inside a 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 ever *subdivide* what it already holds. Without this, `device_register` would be a
syscall named "map any physical page you like." 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 ### 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: its "devices" are the block's comparators:
```zig ```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 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 `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. asserts the kernel's table upholds it.
A USB device has *no* resources at all: `resource_count = 0`, because it's addressed 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. **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 [`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 HPET advertises its own routing options in its own registers — a privilege no ordinary
device has. 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 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. 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. exercise this path. The first MSI driver will be the first PCI driver.
## M16 — the IOMMU, and the honest caveat ## 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 `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 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 `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 on its own regardless: it's small, obviously correct, and stops every future driver
+7 -7
View File
@@ -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 silently intercept it. Instead the kernel checks two things (`process.ownedGsi`, and
the same check at the top of `sysMmioMap`): 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` - the resource at `res_idx` is of the right *kind* — `memory` for `mmio_map`, `irq`
for `irq_bind` for `irq_bind`
@@ -138,7 +138,7 @@ Two properties worth knowing:
## A whole driver ## 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 ```zig
const hpet = findHpet(buf) orelse return; // device_enumerate, look for 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 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`. 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 [driver-model.md](driver-model.md) for how bus drivers, class drivers and host
controller drivers fit together. controller drivers fit together.
@@ -269,8 +269,8 @@ Worth knowing before you write the second driver:
## Verifying it ## Verifying it
The `hpet` test spawns `hpetd` from the initrd and watches the serial log. The driver The `hpet` test spawns `hpet` from the initial ramdisk and watches the serial log. The driver
prints `hpetd: ok` only after being woken five times, and its loop's only exit is 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. 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 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') 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 - **`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 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 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 by the same claim. The legacy devices that need it are all low-rate, so the syscall
is likely fast enough. 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, exit — only IRQ bindings are released. A dead driver's device stays owned forever,
which blocks restart. which blocks restart.
- **Unregistering children.** `device_register` only appends. A USB device that is - **Unregistering children.** `device_register` only appends. A USB device that is
+13
View File
@@ -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");
@@ -5,7 +5,7 @@
const std = @import("std"); const std = @import("std");
const unistd = @import("unistd.zig"); 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_SET = unistd.SEEK_SET;
pub const SEEK_CURRENT = unistd.SEEK_CURRENT; pub const SEEK_CURRENT = unistd.SEEK_CURRENT;
@@ -5,10 +5,10 @@
const std = @import("std"); const std = @import("std");
const protocol = @import("vfs-protocol"); const protocol = @import("vfs-protocol");
const ipc = @import("ipc.zig"); const ipc = @import("runtime").ipc;
const danos = @import("danos"); 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_SET: u32 = 0;
pub const SEEK_CURRENT: u32 = 1; pub const SEEK_CURRENT: u32 = 1;
pub const SEEK_END: u32 = 2; pub const SEEK_END: u32 = 2;
@@ -110,11 +110,11 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 {
SEEK_SET => 0, SEEK_SET => 0,
SEEK_CURRENT => @intCast(f.offset), SEEK_CURRENT => @intCast(f.offset),
SEEK_END => blk: { SEEK_END => blk: {
const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; const request = protocol.Request{ .operation = .status, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined; var sbuf: [@sizeOf(protocol.FileStatus)]u8 = undefined;
const r = transact(request, &.{}, &sbuf) orelse return -1; const r = transact(request, &.{}, &sbuf) orelse return -1;
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1; if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.FileStatus)) return -1;
const st = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]); const st = std.mem.bytesToValue(protocol.FileStatus, sbuf[0..@sizeOf(protocol.FileStatus)]);
break :blk @intCast(st.size); break :blk @intCast(st.size);
}, },
else => return -1, else => return -1,
@@ -126,17 +126,17 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 {
} }
/// Stat `path`. Returns 0 or -1. /// 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. // Open, stat by node, close — simple and enough for now.
const fd = open(path, 0); const fd = open(path, 0);
if (fd < 0) return -1; if (fd < 0) return -1;
defer close(fd); defer close(fd);
const f = fdPtr(fd).?; const f = fdPtr(fd).?;
const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; const request = protocol.Request{ .operation = .status, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined; var sbuf: [@sizeOf(protocol.FileStatus)]u8 = undefined;
const r = transact(request, &.{}, &sbuf) orelse return -1; const r = transact(request, &.{}, &sbuf) orelse return -1;
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1; if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.FileStatus)) return -1;
out.* = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]); out.* = std.mem.bytesToValue(protocol.FileStatus, sbuf[0..@sizeOf(protocol.FileStatus)]);
return 0; return 0;
} }
-2
View File
@@ -17,9 +17,7 @@ pub const start = @import("start.zig");
/// The VFS wire protocol (shared with the VFS server). /// The VFS wire protocol (shared with the VFS server).
pub const vfs_protocol = @import("vfs-protocol"); pub const vfs_protocol = @import("vfs-protocol");
/// POSIX-style file API: open/read/write/lseek/stat/close. /// 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. /// C stdio: fopen/fread/fwrite/fseek/ftell/fclose over unistd.
pub const stdio = @import("stdio.zig");
/// Device access for drivers: enumerate/claim/mmioMap. /// Device access for drivers: enumerate/claim/mmioMap.
pub const device = @import("device.zig"); pub const device = @import("device.zig");
+5 -5
View File
@@ -245,12 +245,12 @@ pub const BootInformation = extern struct {
/// The raw `/sbin/init` ELF image, read off the boot volume by the loader /// 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 /// 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 /// 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_base: u64 = 0,
init_len: 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 /// 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. /// handoff, same as `init` above. 0/0 = no initial_ramdisk. See system/initial-ramdisk.zig.
initrd_base: u64 = 0, initial_ramdisk_base: u64 = 0,
initrd_len: u64 = 0, initial_ramdisk_len: u64 = 0,
}; };
+5 -5
View File
@@ -3,7 +3,7 @@
//! //!
//! This module has two stages. `parser.zig` walks the entire byte stream and //! This module has two stages. `parser.zig` walks the entire byte stream and
//! records every named object into a namespace tree (`namespace.zig`), capturing //! 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 //! methods on demand — running operators, control flow, and OperationRegion field
//! access — so callers can resolve device status (`_STA`), current resource //! access — so callers can resolve device status (`_STA`), current resource
//! settings (`_CRS`), sleep states (`_Sx`), and the like against the live namespace. //! 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; pub const NodeKind = @import("namespace.zig").NodeKind;
/// The AML evaluator: interprets control methods (and reads Names/Fields) far /// The AML evaluator: interprets control methods (and reads Names/Fields) far
/// enough for device discovery. See `interp.zig`. /// enough for device discovery. See `interpreter.zig`.
pub const Interpreter = @import("interp.zig").Interpreter; pub const Interpreter = @import("interpreter.zig").Interpreter;
pub const Object = @import("interp.zig").Object; pub const Object = @import("interpreter.zig").Object;
pub const EvaluateHal = @import("interp.zig").Hal; pub const EvaluateHal = @import("interpreter.zig").Hal;
/// The SLP_TYP values written to PM1a/PM1b control to enter a sleep state. /// The SLP_TYP values written to PM1a/PM1b control to enter a sleep state.
pub const SleepType = struct { pub const SleepType = struct {
@@ -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 //! 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 //! 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 //! 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. //! 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 //! 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 //! 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. //! 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 //! 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 { pub fn main() void {
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { 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; return;
}; };
const parent = findHpet(buffer) orelse { const parent = findHpet(buffer) orelse {
_ = runtime.system.write("busd: no HPET\n"); _ = runtime.system.write("bus: no HPET\n");
return; return;
}; };
const resource = resourcesOf(parent); const resource = resourcesOf(parent);
// Claim the bus. Everything below is subdivision of what this claim granted. // 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 // Claims are exclusive, and at a normal boot the kernel spawns every initial_ramdisk
// binary — so hpetd may own the HPET already. That's not an error, it's the // 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 // 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)) { 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; return;
} }
// Enumerate the bus: ask the hardware how many children it has. // Enumerate the bus: ask the hardware how many children it has.
const base = device.mmioMap(parent.id, 0) orelse { 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; return;
}; };
const cap: *volatile u64 = @ptrFromInt(base + register_general_cap); const cap: *volatile u64 = @ptrFromInt(base + register_general_cap);
@@ -112,7 +112,7 @@ pub fn main() void {
} }
if (device.register(parent.id, &child) == null) { if (device.register(parent.id, &child) == null) {
_ = runtime.system.write("busd: register failed\n"); _ = runtime.system.write("bus: register failed\n");
return; return;
} }
published += 1; published += 1;
@@ -133,11 +133,11 @@ pub fn main() void {
.len = 0x1000, .len = 0x1000,
}; };
if (device.register(parent.id, &rogue) != null) { 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; return;
} }
if (device.enumerate(buffer) != before) { 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; return;
} }
@@ -149,18 +149,18 @@ pub fn main() void {
if (d.parent != parent.id) continue; if (d.parent != parent.id) continue;
const w = d.resources[0]; const w = d.resources[0];
if (w.start < resource.mmio.start or w.len >= resource.mmio.len) { 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; return;
} }
seen += 1; seen += 1;
} }
if (seen != published) { if (seen != published) {
_ = runtime.system.write("busd: FAIL child count mismatch\n"); _ = runtime.system.write("bus: FAIL child count mismatch\n");
return; return;
} }
// Delegation, end to end: claim a child and map *it*. A real class driver would be // 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 // 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. // 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. // 4 KiB the HPET lives in — the granularity limit documented in docs/drivers.md.
// The *resource* is narrow even though the page isn't.) // The *resource* is narrow even though the page isn't.)
const child_id = firstChildOf(buffer, device.enumerate(buffer), parent.id) orelse { 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; return;
}; };
if (!device.claim(child_id)) { 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; return;
} }
const child_base = device.mmioMap(child_id, 0) orelse { 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; return;
}; };
const via_child: *volatile u64 = @ptrFromInt(child_base); const via_child: *volatile u64 = @ptrFromInt(child_base);
const via_bus: *volatile u64 = @ptrFromInt(base + 0x100); const via_bus: *volatile u64 = @ptrFromInt(base + 0x100);
if (via_child.* != via_bus.*) { 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; return;
} }
@@ -195,12 +195,12 @@ pub fn main() void {
_ = runtime.system.munmap(scratch, 0x1000); _ = runtime.system.munmap(scratch, 0x1000);
const descriptor: *const device.DeviceDescriptor = @ptrFromInt(scratch); const descriptor: *const device.DeviceDescriptor = @ptrFromInt(scratch);
if (device.register(parent.id, descriptor) != null) { 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; return;
} }
} }
_ = runtime.system.write("busd: ok\n"); _ = runtime.system.write("bus: ok\n");
while (true) runtime.system.sleep(1000); while (true) runtime.system.sleep(1000);
} }
@@ -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 //! 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 //! this ring-3 address space (strong-uncacheable), **bind its interrupt to an IPC
//! endpoint**, then sit blocked in `replyWait` until the hardware wakes it. //! endpoint**, then sit blocked in `replyWait` until the hardware wakes it.
@@ -13,8 +13,8 @@
//! the full cycle to be correct: //! the full cycle to be correct:
//! //!
//! kernel ISR mask the GSI -> EOI -> notify this endpoint //! kernel ISR mask the GSI -> EOI -> notify this endpoint
//! hpetd wake, clear GENERAL_INT_STATUS (deasserts the line), re-arm //! hpet wake, clear GENERAL_INT_STATUS (deasserts the line), re-arm
//! hpetd irq_ack -> kernel unmasks the GSI //! hpet irq_ack -> kernel unmasks the GSI
//! //!
//! Clear the status bit *before* acking, or the line is still asserted when the //! Clear the status bit *before* acking, or the line is still asserted when the
//! kernel unmasks and the I/O APIC redelivers forever. //! kernel unmasks and the I/O APIC redelivers forever.
@@ -64,7 +64,7 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
for (buffer[0..n]) |d| { for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(device.DeviceClass.timer)) continue; if (d.class != @intFromEnum(device.DeviceClass.timer)) continue;
// Skip comparator children a bus driver may have published below the block // 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; if (d.parent != device.no_parent) continue;
var mmio: ?u64 = null; var mmio: ?u64 = null;
var irq: ?u64 = null; var irq: ?u64 = null;
@@ -85,21 +85,21 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
pub fn main() void { pub fn main() void {
// Enumerate into a heap buffer (too big for the one-page user stack). // Enumerate into a heap buffer (too big for the one-page user stack).
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch { 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; return;
}; };
const hpet = findHpet(buffer) orelse { 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; return;
}; };
if (!device.claim(hpet.device_id)) { if (!device.claim(hpet.device_id)) {
_ = runtime.system.write("hpetd: claim failed\n"); _ = runtime.system.write("hpet: claim failed\n");
return; return;
} }
const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse { 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; return;
}; };
@@ -108,7 +108,7 @@ pub fn main() void {
const gsi = hpet.gsi; const gsi = hpet.gsi;
const endpoint = ipc.createEndpoint() orelse { const endpoint = ipc.createEndpoint() orelse {
_ = runtime.system.write("hpetd: create_endpoint failed\n"); _ = runtime.system.write("hpet: create_endpoint failed\n");
return; return;
}; };
@@ -116,7 +116,7 @@ pub fn main() void {
// Counter period, so we can arm the comparator a fixed wall-clock distance out. // Counter period, so we can arm the comparator a fixed wall-clock distance out.
const femtos_per_tick = register(base, register_general_cap).* >> 32; const femtos_per_tick = register(base, register_general_cap).* >> 32;
if (femtos_per_tick == 0) { if (femtos_per_tick == 0) {
_ = runtime.system.write("hpetd: bad HPET period\n"); _ = runtime.system.write("hpet: bad HPET period\n");
return; return;
} }
const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick; 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; register(base, register_general_configuration).* |= configuration_enable;
if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) { 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; 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 ----------------------------------------------------- // --- the driver loop -----------------------------------------------------
// Blocked in replyWait. No polling, no spinning: the next line of this function // 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; 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)) { 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; return;
} }
} }
_ = runtime.system.write("hpetd: ok\n"); _ = runtime.system.write("hpet: ok\n");
while (true) runtime.system.sleep(1000); while (true) runtime.system.sleep(1000);
} }
@@ -1,5 +1,5 @@
//! The initrd (initial ramdisk) container format — shared by the build-time //! The initial_ramdisk (initial ramdisk) container format — shared by the build-time
//! packer (tools/mkinitrd.zig) and the kernel that unpacks it. Deliberately //! 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 //! 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. //! blobs. We own both producer and consumer, so it need be no fancier.
//! //!
@@ -10,7 +10,7 @@
const std = @import("std"); const std = @import("std");
/// "DNRD" — identifies a danos initrd image. /// "DNRD" — identifies a danos initial_ramdisk image.
pub const magic: u32 = 0x444E5244; pub const magic: u32 = 0x444E5244;
pub const Header = extern struct { pub const Header = extern struct {
@@ -24,7 +24,7 @@ pub const Entry = extern struct {
len: u64, // blob length in bytes 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. /// entry table fits; `entry` bounds-checks each blob against the image.
pub const Reader = struct { pub const Reader = struct {
image: []const u8, image: []const u8,
+2 -2
View File
@@ -22,7 +22,7 @@
//! //!
//! Binding is capability-gated exactly like `mmio_map`: the caller must have //! 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` //! `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 //! therefore never bind an interrupt it doesn't own — a raw-GSI system_call would let
//! any process steal the keyboard's line. //! 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; bound_owner[gsi] = owner;
// Level-triggered, active-high. Level is the general case a driver must survive // 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. // leaves the line asserted, so the mask/ack cycle is harmless there.
// //
// Hardcoded for now: a device whose MADT interrupt-source override declares the // Hardcoded for now: a device whose MADT interrupt-source override declares the
+15 -15
View File
@@ -8,9 +8,9 @@ const pmm = @import("pmm.zig");
const heap = @import("heap.zig"); const heap = @import("heap.zig");
const scheduler = @import("scheduler.zig"); const scheduler = @import("scheduler.zig");
const process = @import("process.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 irq = @import("irq.zig");
const initrd = @import("initrd"); const initial_ramdisk = @import("initial-ramdisk");
const platform = @import("platform"); const platform = @import("platform");
const tests = @import("tests.zig"); const tests = @import("tests.zig");
const build_options = @import("build_options"); 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/ // Snapshot the device tree for user-space drivers (device_enumerate/claim/
// mmio_map operate on this flat, id-indexed table + claim map). // mmio_map operate on this flat, id-indexed table + claim map).
device_service.init(&device_tree); devices_broker.init(&device_tree);
if (device_service.dropped > 0) { if (devices_broker.dropped > 0) {
// Otherwise entirely silent: drivers would just never see that hardware. // 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 // 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"); 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; // 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). // 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 // Become the idle task: drop below every real task and halt until an
// interrupt. The timer keeps preempting into init and any other work. // interrupt. The timer keeps preempting into init and any other work.
@@ -297,22 +297,22 @@ fn kmain(boot_information: *const BootInformation) noreturn {
architecture.halt(); 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 /// image or a program that fails to load is logged and skipped — the rest of the
/// system still runs. /// system still runs.
fn startInitrdBinaries(boot_information: *const danos.BootInformation) void { fn startInitialRamdiskBinaries(boot_information: *const danos.BootInformation) void {
if (boot_information.initrd_len == 0) return; if (boot_information.initial_ramdisk_len == 0) return;
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initrd.Reader.init(image) orelse { const rd = initial_ramdisk.Reader.init(image) orelse {
status("initrd: bad image, skipping\n"); status("initial_ramdisk: bad image, skipping\n");
return; return;
}; };
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; 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| { 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) });
}; };
} }
} }
+8 -8
View File
@@ -27,7 +27,7 @@ const pmm = @import("pmm.zig");
const scheduler = @import("scheduler.zig"); const scheduler = @import("scheduler.zig");
const sync = @import("sync.zig"); const sync = @import("sync.zig");
const ipc = @import("ipc-synchronous.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 irq = @import("irq.zig");
const log = @import("log.zig"); const log = @import("log.zig");
@@ -206,12 +206,12 @@ fn systemDeviceEnumerate(state: *architecture.CpuState) void {
const sz = @sizeOf(danos.DeviceDescriptor); const sz = @sizeOf(danos.DeviceDescriptor);
const cap = @min(maximum, (user_half_end - buffer_ptr) / sz); // clamp to the user half const cap = @min(maximum, (user_half_end - buffer_ptr) / sz); // clamp to the user half
const out: [*]danos.DeviceDescriptor = @ptrFromInt(buffer_ptr); 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. /// device_claim(id) -> 0/-1: take exclusive ownership of a device for this process.
fn systemDeviceClaim(state: *architecture.CpuState) void { 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) architecture.setSystemCallResult(state, 0)
else else
fail(state); fail(state);
@@ -225,9 +225,9 @@ fn systemMmioMap(state: *architecture.CpuState) void {
const resource_index = architecture.systemCallArg(state, 1); const resource_index = architecture.systemCallArg(state, 1);
const t = scheduler.current(); const t = scheduler.current();
if (t.aspace == 0) return fail(state); 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 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 (r.kind != @intFromEnum(danos.ResourceKind.memory)) return fail(state);
if (t.device_map_next == 0) t.device_map_next = device_arena_base; 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; var descriptor: danos.DeviceDescriptor = undefined;
if (!ipc.copyFromUser(t.aspace, descriptor_ptr, std.mem.asBytes(&descriptor))) return fail(state); 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); 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 /// 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. /// 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 { 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; 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.kind != @intFromEnum(danos.ResourceKind.irq)) return null;
if (r.start >= irq.maximum_gsi) return null; if (r.start >= irq.maximum_gsi) return null;
return @intCast(r.start); return @intCast(r.start);
+53 -53
View File
@@ -12,7 +12,7 @@
const std = @import("std"); const std = @import("std");
const danos = @import("danos"); const danos = @import("danos");
const architecture = @import("architecture"); const architecture = @import("architecture");
const device_service = @import("device-service.zig"); const devices_broker = @import("devices-broker.zig");
const platform = @import("platform"); const platform = @import("platform");
const pmm = @import("pmm.zig"); const pmm = @import("pmm.zig");
const heap = @import("heap.zig"); const heap = @import("heap.zig");
@@ -22,7 +22,7 @@ const ipcsync = @import("ipc-synchronous.zig");
const irq = @import("irq.zig"); const irq = @import("irq.zig");
const sync = @import("sync.zig"); const sync = @import("sync.zig");
const process = @import("process.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. /// Formatted write straight to serial, independent of the framebuffer console.
fn log(comptime fmt: []const u8, args: anytype) void { 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); initTest(boot_information);
} else if (eql(case, "process")) { } else if (eql(case, "process")) {
processTest(boot_information); processTest(boot_information);
} else if (eql(case, "initrd")) { } else if (eql(case, "initial-ramdisk")) {
initrdTest(boot_information); initialRamdiskTest(boot_information);
} else if (eql(case, "vfs")) { } else if (eql(case, "vfs")) {
vfsTest(boot_information); vfsTest(boot_information);
} else if (eql(case, "hpet")) { } else if (eql(case, "hpet")) {
@@ -952,24 +952,24 @@ fn initTest(boot_information: *const BootInformation) void {
result(); 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) /// binaries; parse it, spawn every program, and confirm one (the vfs stub)
/// reaches ring 3 and heartbeats — proving the whole ferry-parse-spawn pipeline. /// reaches ring 3 and heartbeats — proving the whole ferry-parse-spawn pipeline.
fn initrdTest(boot_information: *const BootInformation) void { fn initialRamdiskTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: initrd\n", .{}); log("DANOS-TEST-BEGIN: initial_ramdisk\n", .{});
check("bootloader handed over an initrd", boot_information.initrd_len != 0); check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
if (boot_information.initrd_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initrd.Reader.init(image) orelse { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initrd image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
return; return;
}; };
check("initrd image is valid", true); check("initial_ramdisk image is valid", true);
check("initrd contains at least one binary", rd.count >= 1); check("initial_ramdisk contains at least one binary", rd.count >= 1);
process.write_count = 0; process.write_count = 0;
process.write_from_user = false; 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) }); 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). // Wait for the spawned programs to run and make syscalls (they write + sleep).
scheduler.setPriority(1); 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(); while (process.write_count < 2 and architecture.millis() < deadline) scheduler.yield();
scheduler.setPriority(4); 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); check("syscalls came from user mode (CPL 3)", process.write_from_user);
result(); result();
} }
/// The full VFS path: spawn the user-space VFS server and a client from the /// 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 /// 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 /// seeing that marker proves client open/write/read reached the server over IPC
/// and came back correct. (The client retries until the server registers.) /// and came back correct. (The client retries until the server registers.)
fn vfsTest(boot_information: *const BootInformation) void { fn vfsTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: vfs\n", .{}); log("DANOS-TEST-BEGIN: vfs\n", .{});
if (boot_information.initrd_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initrd", false); check("bootloader handed over an initial_ramdisk", false);
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initrd.Reader.init(image) orelse { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initrd image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
return; return;
}; };
process.write_count = 0; process.write_count = 0;
process.write_from_user = false; 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). // the shared evidence buffer and confuse the marker check).
_ = spawnNamed(rd, "vfs"); _ = spawnNamed(rd, "vfs");
_ = spawnNamed(rd, "vfs-test"); _ = spawnNamed(rd, "vfs-test");
@@ -1037,9 +1037,9 @@ fn vfsTest(boot_information: *const BootInformation) void {
result(); 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. /// 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; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; 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 /// 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 /// 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 /// `target_ticks` times — it cannot reach that line by polling, because the loop's
/// only exit is through `replyWait` returning a notification badge. /// 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 /// 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 /// 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. /// that state is quiescent and not a race.
fn hpetTest(boot_information: *const BootInformation) void { fn hpetTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: hpet\n", .{}); log("DANOS-TEST-BEGIN: hpet\n", .{});
if (boot_information.initrd_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initrd", false); check("bootloader handed over an initial_ramdisk", false);
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initrd.Reader.init(image) orelse { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initrd image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
return; return;
}; };
process.write_count = 0; process.write_count = 0;
process.write_from_user = false; 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); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { 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. /// The GSI discovery recorded for the HPET, from the same device table the driver saw.
fn hpetGsi() ?u32 { fn hpetGsi() ?u32 {
var buffer: [16]danos.DeviceDescriptor = undefined; 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| { for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue; if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue;
if (d.parent != danos.no_parent) continue; // the block, not a comparator child 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 /// 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. /// 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 /// 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 /// 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, /// 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 /// `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. /// land in the device table with the containment invariant intact.
fn busTest(boot_information: *const BootInformation) void { fn busTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: bus\n", .{}); log("DANOS-TEST-BEGIN: bus\n", .{});
if (boot_information.initrd_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initrd", false); check("bootloader handed over an initial_ramdisk", false);
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len]; const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initrd.Reader.init(image) orelse { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initrd image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
return; return;
}; };
process.write_count = 0; process.write_count = 0;
process.write_from_user = false; 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); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
@@ -1173,7 +1173,7 @@ fn busTest(boot_information: *const BootInformation) void {
result(); 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, /// 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. /// 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. /// bridge's `bus_range`, because a bus-number range isn't an address window.
fn childrenContained() bool { fn childrenContained() bool {
var buffer: [64]danos.DeviceDescriptor = undefined; 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 bus_id = hpetDeviceId() orelse return false;
const p = buffer[@intCast(bus_id)]; const p = buffer[@intCast(bus_id)];
@@ -1205,13 +1205,13 @@ fn childrenContained() bool {
if (!ok) return false; 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 { fn hpetDeviceId() ?u64 {
var buffer: [64]danos.DeviceDescriptor = undefined; 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| { for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue; if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue;
if (d.parent != danos.no_parent) continue; // a comparator child, not the block 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 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). /// (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: /// Two properties, both read back from the hardware rather than from our own state:
/// ///
/// 1. A bound GSI is routed and unmasked. /// 1. A bound GSI is routed and unmasked.
+17 -11
View File
@@ -1,18 +1,22 @@
//! The VFS wire protocol — the message format spoken between a client (via the //! The VFS wire protocol — the message format spoken between a client (via the file
//! `runtime` file API) and the user-space VFS server over IPC. A request is a fixed //! API) and the user-space VFS server over IPC. A request is a fixed `Request` header
//! `Request` header followed by an inline payload (a path, or write bytes); a //! followed by an inline payload (a path, or write bytes); a reply is a fixed `Reply`
//! reply is a fixed `Reply` header followed by an inline payload (read bytes, or //! header followed by an inline payload (read bytes, or a FileStatus). Everything fits
//! a Stat). Everything fits in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes). //! 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 //! 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) { pub const Operation = enum(u32) {
open, // open(path) -> node id open, // open(path) -> node id
close, // close(node) close, // close(node)
read, // read(node, offset, len) -> bytes read, // read(node, offset, len) -> bytes
write, // write(node, offset, bytes) -> count 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); /// 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 /// 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 /// open-file id (for `open`); `len` is the payload length (bytes read, or the
/// Stat size). /// FileStatus size).
pub const Reply = extern struct { pub const Reply = extern struct {
status: i32, status: i32,
_padding: u32 = 0, _padding: u32 = 0,
@@ -37,7 +41,9 @@ pub const Reply = extern struct {
_padding2: u32 = 0, _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, size: u64,
kind: u32, kind: u32,
_padding: u32 = 0, _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. /// Largest inline payload that still fits one IPC message alongside a header.
pub const maximum_payload: usize = message_maximum - request_size; pub const maximum_payload: usize = message_maximum - request_size;
/// Open flags. /// Open flags (danos-native; the POSIX layer maps `O_CREAT` onto `create`).
pub const O_CREAT: u32 = 1; pub const create: u32 = 1;
+2 -2
View File
@@ -1,13 +1,13 @@
//! /sbin/vfstest — a client that proves the VFS round trip end to end: open a //! /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. //! 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 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 std = @import("std");
const runtime = @import("runtime"); const runtime = @import("runtime");
pub fn main() void { pub fn main() void {
const u = runtime.unistd; const u = @import("posix").unistd;
const payload = "hello-vfs"; const payload = "hello-vfs";
// The VFS server may not have registered yet — retry open until it's up. // The VFS server may not have registered yet — retry open until it's up.
+4 -4
View File
@@ -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` //! 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 //! file API marshals open/read/write/stat/close into calls to this server's
//! endpoint, published under the well-known `vfs` service id). //! 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; if (off + n > nd.size) nd.size = off + n;
return writeReply(out, .{ .status = 0, .len = @intCast(n) }, &.{}); return writeReply(out, .{ .status = 0, .len = @intCast(n) }, &.{});
}, },
.stat => { .status => {
const of = openAt(request.node) orelse return fail(out); const of = openAt(request.node) orelse return fail(out);
const st = protocol.Stat{ .size = nodes[of.node].size, .kind = 0 }; const st = protocol.FileStatus{ .size = nodes[of.node].size, .kind = 0 };
return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.Stat) }, std.mem.asBytes(&st)); return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.FileStatus) }, std.mem.asBytes(&st));
}, },
.close => { .close => {
if (request.node < opens.len) opens[@intCast(request.node)].used = false; if (request.node < opens.len) opens[@intCast(request.node)].used = false;
+5 -5
View File
@@ -57,9 +57,9 @@ ARCHES = {
], ],
"efi_app": ("EFI/BOOT/BOOTX64.efi", "BOOTX64.efi"), # (dest in ESP, name in zig-out/bin) "efi_app": ("EFI/BOOT/BOOTX64.efi", "BOOTX64.efi"), # (dest in ESP, name in zig-out/bin)
"kernel": ("kernel", "kernel"), "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. # (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. # Built as a function so we can splice in per-run paths.
"qemu_args": lambda a, esp, vars_fd, serial: [ "qemu_args": lambda a, esp, vars_fd, serial: [
"-machine", "q35", "-m", "128M", "-machine", "q35", "-m", "128M",
@@ -178,9 +178,9 @@ CASES = [
"smp": 4, "smp": 4,
"expect": r"DANOS-TEST-RESULT: PASS", "expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "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). # 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", "expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
# The user-space VFS: a client opens/writes/reads a file through the rt file # 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"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
# IRQ teardown: an exiting driver's line is masked and its slot cleared (so no # 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 # 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", {"name": "irqfree",
"expect": r"DANOS-TEST-RESULT: PASS", "expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/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. bootloader ferries to the kernel.
Usage: mkinitrd.py <out.img> [<name> <file>]... Usage: make-initial-ramdisk.py <out.img> [<name> <file>]...
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 Header : magic u32 ("DNRD"=0x444E5244), count u32
Entry*N : name [32]u8 (NUL-padded), offset u64, len u64 Entry*N : name [32]u8 (NUL-padded), offset u64, len u64
blobs : each entry's file bytes at its offset blobs : each entry's file bytes at its offset
@@ -21,7 +21,7 @@ def main() -> int:
out_path = sys.argv[1] out_path = sys.argv[1]
rest = sys.argv[2:] rest = sys.argv[2:]
if len(rest) % 2 != 0: if len(rest) % 2 != 0:
sys.stderr.write("usage: mkinitrd.py <out.img> [<name> <file>]...\n") sys.stderr.write("usage: make-initial-ramdisk.py <out.img> [<name> <file>]...\n")
return 2 return 2
items = [(rest[i], rest[i + 1]) for i in range(0, len(rest), 2)] items = [(rest[i], rest[i + 1]) for i in range(0, len(rest), 2)]