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).
const init_file_name = std.unicode.utf8ToUtf16LeStringLiteral("sbin\\init");
/// Path of the initrd image on the boot volume (the VFS server + drivers).
const initrd_file_name = std.unicode.utf8ToUtf16LeStringLiteral("initrd.img");
/// Path of the initial_ramdisk image on the boot volume (the VFS server + drivers).
const initial_ramdisk_file_name = std.unicode.utf8ToUtf16LeStringLiteral("initial-ramdisk.img");
/// Physical page size, and the sentinel UEFI uses to seek to end-of-file.
const page_size = 4096;
@@ -68,9 +68,9 @@ fn boot() !noreturn {
log(") - booting without user space\r\n");
};
// Best effort: the initrd (VFS server + drivers) is optional too.
loadInitrd(bs, &boot_information) catch |err| {
log("danos: no initrd (");
// Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
loadInitialRamdisk(bs, &boot_information) catch |err| {
log("danos: no initial_ramdisk (");
logBytes(@errorName(err));
log(")\r\n");
};
@@ -396,12 +396,12 @@ fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !
log("danos: sbin/init loaded\r\n");
}
/// Ferry the initrd (the VFS server + drivers) to the kernel, same as init.
fn loadInitrd(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void {
const image = try loadFile(bs, initrd_file_name);
boot_information.initrd_base = @intFromPtr(image.ptr);
boot_information.initrd_len = image.len;
log("danos: initrd loaded\r\n");
/// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init.
fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void {
const image = try loadFile(bs, initial_ramdisk_file_name);
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
boot_information.initial_ramdisk_len = image.len;
log("danos: initial_ramdisk loaded\r\n");
}
/// Validate the ELF, copy every PT_LOAD segment to its physical address, and
+50 -31
View File
@@ -58,6 +58,7 @@ fn addUserBinary(
b: *std.Build,
target: std.Build.ResolvedTarget,
runtime_module: *std.Build.Module,
posix_module: *std.Build.Module,
name: []const u8,
root: []const u8,
) *std.Build.Step.Compile {
@@ -74,6 +75,9 @@ fn addUserBinary(
.stack_protector = false,
.imports = &.{
.{ .name = "runtime", .module = runtime_module },
// POSIX/C compatibility layer, available to any program that wants it
// (danos-native code uses `runtime` directly). See library/posix/.
.{ .name = "posix", .module = posix_module },
},
}),
});
@@ -144,11 +148,13 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("system/services/vfs/protocol.zig"),
});
// The user-space runtime library (a nascent libc): system_call wrappers, the
// C-convention heap, IPC helpers, the process start shim. Compiled into every
// user binary (see addUserBinary), so it inherits each exe's `.large` code
// model — do NOT set a target/code_model here. It imports `danos` for the
// shared SystemCall numbers and `vfs-protocol` for the file API.
// The danos-native user-space runtime: system_call wrappers, the C-convention
// heap, IPC helpers, the process start shim, device access. This is the stable
// application ABI; POSIX compatibility is a separate library on top (see below).
// Compiled into every user binary (see addUserBinary), so it inherits each exe's
// `.large` code model — do NOT set a target/code_model here. It imports `danos`
// for the shared SystemCall numbers and re-exports `vfs-protocol` for the VFS
// server.
const runtime_module = b.addModule("runtime", .{
.root_source_file = b.path("library/runtime/runtime.zig"),
.imports = &.{
@@ -157,10 +163,23 @@ pub fn build(b: *std.Build) void {
},
});
// The initrd container format, shared by the kernel (unpacks it) and the
// build-time packer tools/mkinitrd.zig (produces it). No dependencies.
const initrd_module = b.addModule("initrd", .{
.root_source_file = b.path("system/initrd.zig"),
// The POSIX / C compatibility layer, a separate library layered strictly over the
// runtime (it calls the runtime's IPC/heap, never system calls directly). This is
// the one place POSIX/C spellings are allowed verbatim — see docs/coding-standards.md
// and library/posix/posix.zig.
const posix_module = b.addModule("posix", .{
.root_source_file = b.path("library/posix/posix.zig"),
.imports = &.{
.{ .name = "runtime", .module = runtime_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module },
.{ .name = "danos", .module = danos_module },
},
});
// The initial_ramdisk container format, shared by the kernel (unpacks it) and the
// build-time packer tools/make-initial-ramdisk.py (produces it). No dependencies.
const initial_ramdisk_module = b.addModule("initial-ramdisk", .{
.root_source_file = b.path("system/initial-ramdisk.zig"),
});
// Compile-time configuration the kernel reads as `@import("build_options")`. The
@@ -198,7 +217,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "platform", .module = platform_module },
.{ .name = "parameters", .module = parameters_module },
.{ .name = "build_options", .module = build_options_module },
.{ .name = "initrd", .module = initrd_module },
.{ .name = "initial-ramdisk", .module = initial_ramdisk_module },
},
}),
});
@@ -220,37 +239,37 @@ pub fn build(b: *std.Build) void {
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
// linked into the kernel's user region against the `runtime` runtime library, and
// started in ring 3 by the kernel's user-ELF loader.
const init_exe = addUserBinary(b, kernel_target, runtime_module, "init", "system/services/init/init.zig");
const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "init", "system/services/init/init.zig");
b.installArtifact(init_exe);
// --- initrd: a bundle of extra user binaries (VFS server + drivers) ---
// --- initial_ramdisk: a bundle of extra user binaries (VFS server + drivers) ---
// Each is built by the same user-binary recipe, then packed into one image by
// the host-side mkinitrd tool. The bootloader ferries the image to the kernel,
// which unpacks it and spawns each program (system/initrd.zig).
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, "vfs", "system/services/vfs/vfs.zig");
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, "vfs-test", "system/services/vfs/vfs-test.zig");
const hpetd_exe = addUserBinary(b, kernel_target, runtime_module, "hpetd", "system/drivers/hpetd/hpetd.zig");
const busd_exe = addUserBinary(b, kernel_target, runtime_module, "busd", "system/drivers/busd/busd.zig");
// the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel,
// which unpacks it and spawns each program (system/initial-ramdisk.zig).
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "vfs", "system/services/vfs/vfs.zig");
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "vfs-test", "system/services/vfs/vfs-test.zig");
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "hpet", "system/drivers/hpet/hpet.zig");
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "bus", "system/drivers/bus/bus.zig");
// Pack the user binaries into the initrd image with the host-side Python tool
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool
// (the container format is trivial, and Python sidesteps std API churn). Args:
// mkinitrd.py <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"});
mk_run.addFileArg(b.path("tools/mkinitrd.py"));
const initrd_img = mk_run.addOutputFileArg("initrd.img");
mk_run.addFileArg(b.path("tools/make-initial-ramdisk.py"));
const initial_ramdisk_img = mk_run.addOutputFileArg("initial-ramdisk.img");
mk_run.addArg("vfs");
mk_run.addFileArg(vfs_exe.getEmittedBin());
mk_run.addArg("vfs-test");
mk_run.addFileArg(vfstest_exe.getEmittedBin());
mk_run.addArg("hpetd");
mk_run.addFileArg(hpetd_exe.getEmittedBin());
mk_run.addArg("busd");
mk_run.addFileArg(busd_exe.getEmittedBin());
mk_run.addArg("hpet");
mk_run.addFileArg(hpet_exe.getEmittedBin());
mk_run.addArg("bus");
mk_run.addFileArg(bus_exe.getEmittedBin());
// Install the image to zig-out/bin (so the QEMU test harness picks it up like
// the other binaries). The run-x86-64 ESP install is added below.
const initrd_install = b.addInstallFile(initrd_img, "bin/initrd.img");
b.getInstallStep().dependOn(&initrd_install.step);
const initial_ramdisk_install = b.addInstallFile(initial_ramdisk_img, "bin/initial-ramdisk.img");
b.getInstallStep().dependOn(&initial_ramdisk_install.step);
// Boot methods live in boot/, one per way of getting the kernel running.
// Each is its own binary/entry (a loader is built for its own target); today
@@ -314,8 +333,8 @@ pub fn build(b: *std.Build) void {
const init_install = b.addInstallArtifact(init_exe, .{
.dest_dir = .{ .override = .{ .custom = "esp/sbin" } },
});
// ...and the initrd (VFS server + drivers) from the volume root.
const initrd_esp_install = b.addInstallFile(initrd_img, "esp/initrd.img");
// ...and the initial_ramdisk (VFS server + drivers) from the volume root.
const initial_ramdisk_esp_install = b.addInstallFile(initial_ramdisk_img, "esp/initial-ramdisk.img");
// The firmware needs to write NVRAM, so give it a writable copy of the vars.
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars });
@@ -353,7 +372,7 @@ pub fn build(b: *std.Build) void {
run_efi.step.dependOn(&efi_install.step);
run_efi.step.dependOn(&kernel_install.step);
run_efi.step.dependOn(&init_install.step);
run_efi.step.dependOn(&initrd_esp_install.step);
run_efi.step.dependOn(&initial_ramdisk_esp_install.step);
const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/run-x86-64-serial0-<timestamp>.log");
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)
danos.zig the kernel↔user ABI contract (the `danos` module)
parameters.zig initrd.zig shared contracts
parameters.zig initial-ramdisk.zig shared contracts
kernel/ IPC, memory, scheduling, the private syscall dispatch
architecture/x86_64/ the `architecture` module (never named by generic code)
devices/ the device model /system/devices reflects (+ aml/)
drivers/ hpetd/ busd/ one sub-project per driver → /system/drivers
drivers/ hpet/ bus/ one sub-project per driver → /system/drivers
services/ init/ vfs/ system servers → /system/services (vfs/ holds
vfs.zig, vfs-test.zig, protocol.zig)
library/ → /lib the runtime library (the stable application ABI)
library/ → /lib libraries, one sub-directory each
runtime/ the danos-native runtime — the stable application ABI
posix/ POSIX/C compatibility, layered over runtime
boot/ → /boot the loaders
tools/ test/ host-side build + QEMU test harness
```
A sub-project exposes its **public interface as a module**: `system/services/vfs/` owns
the VFS wire protocol (`protocol.zig`, the `vfs-protocol` module), which the runtime's
file layer imports by name. `usb`/`block` drivers will expose their protocols the same
way.
the VFS wire protocol (`protocol.zig`, the `vfs-protocol` module), which the POSIX
layer imports by name. `usb`/`block` drivers will expose their protocols the same way.
`library/posix/` is special: it is the **one place** POSIX/C spellings are allowed
verbatim (`stat`, `O_CREAT`, `fopen`, `errno`). Everywhere else follows the danos
naming rule with no exception — see [coding-standards.md](coding-standards.md). The
POSIX layer calls the runtime, never the kernel's system calls directly, so it never
appears in the private-ABI path.
## Source map
@@ -159,14 +166,15 @@ way.
| IPC channels between kernel threads (message passing) | `system/kernel/ipc.zig` |
| IPC endpoints: cross-address-space call/reply, handles, notifications | `system/kernel/ipc-synchronous.zig` |
| User processes: ELF loading, address spaces, the syscall table | `system/kernel/process.zig` |
| Device tree + claim capability + `device_register` containment | `system/kernel/device-service.zig` |
| Device tree + claim capability + `device_register` containment | `system/kernel/devices-broker.zig` |
| IRQ-as-IPC: routing a device interrupt to a driver's endpoint | `system/kernel/irq.zig` |
| Hardware discovery (ACPI/device tree) behind one neutral device model | `system/devices/` |
| Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` |
| In-kernel test cases | `system/kernel/tests.zig` |
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |
| Runtime library (`runtime`): syscall wrappers, heap, stdio, IPC, device access — the stable application ABI | `library/runtime/` |
| danos-native runtime (`runtime`): syscall wrappers, heap, IPC, device access — the stable application ABI | `library/runtime/` |
| POSIX/C compatibility (`posix`): unistd, stdio — the one place POSIX names are allowed | `library/posix/` |
| System services (init, the VFS server + its `protocol` module) | `system/services/` |
| Device drivers, one sub-project each (`hpetd` leaf driver, `busd` bus driver) | `system/drivers/` |
| Device drivers, one sub-project each (`hpet` leaf driver, `bus` bus driver) | `system/drivers/` |
| Build + `run-x86-64` (QEMU/OVMF) | `build.zig` |
| QEMU integration test harness | `test/qemu_test.py` |
+25 -15
View File
@@ -6,7 +6,7 @@ Conventions for danos source. The overriding one, from which most of the rest fo
> abbreviation is an acronym.**
`interruptDispatch`, not `intDisp`. `message_len`, not `message_len` (`msg` expands, `len`
is a Zig idiom — see the exceptions). `device_service`, not `device_service`. `scheduler`, not
is a Zig idiom — see the exceptions). `devices_broker`, not `devices_broker`. `scheduler`, not
`sched`. The cost of a longer name is paid once, at the keyboard; the cost of a
cryptic one is paid every time the code is read, by everyone who reads it. In a
microkernel whose whole argument is that a human can hold each piece in their head,
@@ -58,13 +58,22 @@ abbreviation, expand it.
Three, and only three.
1. **Foreign ABI names are spelled exactly as the ABI spells them.** A function that
*is* the C or POSIX interface keeps its name: `fopen`, `fwrite`, `fread`, `malloc`,
`calloc`, `realloc`, `free`, `memcpy`, `mmap`, `munmap`, `open`, `read`, `write`,
`close`, `lseek`, `stat`, `errno`. We don't get to rename `fwrite` to
`fileWrite` — it wouldn't be `fwrite` any more. This also covers the syscall
*wrappers* that exist to match those names. It does **not** license inventing new
abbreviated names in that style.
1. **Foreign ABI names are spelled exactly as the ABI spells them — but only inside
the layer that *is* that ABI.** A function that *is* the C or POSIX interface keeps
its name: `fopen`, `fwrite`, `fread`, `malloc`, `calloc`, `realloc`, `free`,
`memcpy`, `mmap`, `munmap`, `open`, `read`, `write`, `close`, `lseek`, `stat`,
`errno`, `O_CREAT`. We don't get to rename `fwrite` to `fileWrite` — it wouldn't be
`fwrite` any more.
**This exception is scoped to one place: `library/posix/`.** A file under
`library/posix/` *is* the foreign ABI, so it keeps the ABI's spellings — that is the
whole rule for that directory. **Everywhere else, Zig/danos naming applies with no
POSIX exception**, so there is nothing to get wrong: if you're not in
`library/posix/`, expand it. A concept POSIX also has gets a danos name outside that
layer — the VFS wire protocol carries a `FileStatus`, not a `Stat`, and a `create`
flag, not `O_CREAT`; `library/posix/` is what maps `stat`→`status` and
`O_CREAT`→`create` at the boundary. (The `syscall` *wrappers* elsewhere are not an
exception to this — they wrap the private danos ABI, so they use danos names.)
2. **Zig idioms are spelled the way Zig spells them.** Three names are the language's,
not ours, and are left alone:
@@ -83,11 +92,12 @@ Three, and only three.
keep `i`; a coordinate may be `x`, `y`. The moment the scope is big enough that the
letter's meaning isn't obvious on sight, give it a real name. When in doubt, name it.
4. **Established Unix filesystem and program conventions.** Top-level directories keep
their conventional names — `src`, `lib`, `sbin`, `bin`, `docs` — as do daemon
programs by their `d` suffix (`hpetd`, `busd`, following `sshd`/`httpd`). These are
names a Unix reader already knows; expanding them fights the convention rather than
serving it.
That's all — no Unix-abbreviation exception. The source directories are full words
(`system`, `library`, not `src`/`lib`), and there is no daemon `d` suffix: a driver
lives in `system/drivers/` and a service in `system/services/`, so the *location*
already says what it is. Encoding the role in the name too (`busd`, `vfsd`) is
redundant — the program is just `bus`, `vfs`. Don't put in a name what its directory
already tells you.
## A note on collisions
@@ -118,11 +128,11 @@ Within those spelling rules, follow Zig's own conventions:
- **Types** — `PascalCase`: `DeviceDescriptor`, `Endpoint`, `WaitQueue`.
- **Functions** — `camelCase`: `mapUserDeviceInto`, `notifyFromIsr`.
- **Variables, fields, constants** — `snake_case`: `message_length`, `device_service`,
- **Variables, fields, constants** — `snake_case`: `message_length`, `devices_broker`,
`notify_badge_bit`.
**File names are `kebab-case`.** A file named for a multi-word thing hyphenates it:
`device-tree.zig`, `ipc-synchronous.zig`, `vfs-protocol.zig`, `device-service.zig`. A
`device-tree.zig`, `ipc-synchronous.zig`, `vfs-protocol.zig`, `devices-broker.zig`. A
single word or acronym needs no hyphen: `scheduler.zig`, `paging.zig`, `apic.zig`,
`idt.zig`. (The module *alias* a file is imported under still follows the code
conventions above — `snake_case` — because it's an identifier, not a filename.)
+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. |
| /bin | Essential command binaries that need to be available in single-user mode, including to bring up the system or repair it, for all users (e.g., cat, ls, cp). |
| /boot | Boot loader files (e.g., EFI, initrd.img ). |
| /boot | Boot loader files (e.g., EFI, initial-ramdisk.img ). |
| /dev | POSIX Device files (e.g., /dev/null, /dev/disk0, /dev/tty, /dev/random). |
| /etc | Host-specific system-wide configuration files. |
| /home | Users' home directories, containing saved files, personal settings, etc. |
@@ -17,7 +17,7 @@ Most modern Unix and Unix-like operating systems follow the FHS. DanOS has its o
| /srv | Site-specific data served by this system, such as data and scripts for web servers, data offered by FTP servers, and repositories for version control systems |
| /system | DanOS operating system files (similar idea to C:\Windows). A true representation of danos — its layout mirrors the source tree, so `/system` is what danos *is*. |
| /system/devices | danos virtual device tree e.g. similar to /sys on linux but with danos device tree conventions (the structures in the devices module) |
| /system/drivers | driver binaries, one sub-project each (e.g. /system/drivers/hpetd) |
| /system/drivers | driver binaries, one sub-project each (e.g. /system/drivers/hpet) |
| /system/services | system-service binaries — the VFS server, init, and other user-mode servers (e.g. /system/services/vfs, /system/services/init) |
| /system/kernel | the kernel image |
| /tmp | Directory for temporary files (see also /var/tmp). Often not preserved between system reboots and may be severely size-restricted. |
@@ -61,7 +61,7 @@ to the driver in the order written, and a read consumes what is there. Terminals
serial lines, keyboards and mice are all of this shape. These are the natural first
device nodes in danos, because a character driver needs nothing the kernel doesn't
already provide — it claims its device, maps its registers with `mmio_map`, and blocks
on `replyWait` for either an interrupt or a client request. `system/drivers/hpetd/hpetd.zig` is already
on `replyWait` for either an interrupt or a client request. `system/drivers/hpet/hpet.zig` is already
that program, minus the client half.
The obstacle is not the file type, it is which hardware a ring-3 driver can actually
@@ -91,7 +91,7 @@ device to a driver process, with no IOMMU programmed, is equivalent to granting
which would forfeit the isolation that motivates user-space drivers in the first place.
Block devices therefore wait on DMA-capable memory, memory barriers, and VT-d/DMAR —
the M14–M16 work in [driver-model.md](driver-model.md). A ramdisk over the initrd is
the M14–M16 work in [driver-model.md](driver-model.md). A ramdisk over the initial ramdisk is
the one block-shaped thing implementable now, and it needs no driver process.
### Pseudo-devices
+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
danos already has the right central structure. `system/kernel/device-service.zig` holds a table of
danos already has the right central structure. `system/kernel/devices-broker.zig` holds a table of
`DeviceDesc`, each with a parent, a class, and a set of resources. Firmware discovery
seeds it ([discovery.md](discovery.md)); `device_register` grows it.
Three invariants make it a capability system rather than a directory:
1. **A claim is exclusive.** `device_claim(id)` succeeds once. Everything downstream —
`mmio_map`, `irq_bind`, `device_register` — checks `device_service.ownerOf(id) == me`.
`mmio_map`, `irq_bind`, `device_register` — checks `devices_broker.ownerOf(id) == me`.
2. **A descriptor is a licence to map physical memory.** Whoever claims a device may
map its `.memory` resources and bind its `.irq` resources. This is why
`device_register` cannot be a free-for-all.
3. **Therefore: containment.** Every resource of a registered child must lie inside a
resource of the same kind on its parent (`device_service.contains`). A bus driver can only
resource of the same kind on its parent (`devices_broker.contains`). A bus driver can only
ever *subdivide* what it already holds. Without this, `device_register` would be a
syscall named "map any physical page you like."
@@ -57,7 +57,7 @@ is not an address window. Discovery is trusted; user space is not.
### What a bus driver looks like
`system/drivers/busd/busd.zig` is the smallest honest one. Its "bus" is the HPET's register block and
`system/drivers/bus/bus.zig` is the smallest honest one. Its "bus" is the HPET's register block and
its "devices" are the block's comparators:
```zig
@@ -78,7 +78,7 @@ for (0..n) |i| { // 3. publish each child
Each child is left **unclaimed**, which is the handoff: a comparator driver can now
`device_claim` one and `mmio_map` it, and will see only its own 0x20-byte window. A child
whose window escapes the bus is refused — `busd` asserts that, and the `bus` test
whose window escapes the bus is refused — `bus` asserts that, and the `bus` test
asserts the kernel's table upholds it.
A USB device has *no* resources at all: `resource_count = 0`, because it's addressed
@@ -247,7 +247,7 @@ barrier, or per-arch inline asm — which is what `library/mmio.zig` should hide
**The blocker, and it's a hard one.** No PCI device can take an interrupt today.
[`addBars`](system/devices/acpi.zig) records `.memory` and `.io_port` BARs and never an
`.irq`; there is no `_PRT` parsing anywhere in the tree. `hpetd` only works because the
`.irq`; there is no `_PRT` parsing anywhere in the tree. `hpet` only works because the
HPET advertises its own routing options in its own registers — a privilege no ordinary
device has.
@@ -271,7 +271,7 @@ which means **discovery should give each `pci_device` a `.memory` resource for i
4 KiB ECAM slot**. That's a small change to `parseMcfg` and it unblocks the whole
capability walk (MSI, MSI-X, PCIe extended caps) without any new syscall.
Note QEMU's HPET reports `Tn_FSB_INT_DEL_CAP = 0` — no MSI — so `hpetd` can never
Note QEMU's HPET reports `Tn_FSB_INT_DEL_CAP = 0` — no MSI — so `hpet` can never
exercise this path. The first MSI driver will be the first PCI driver.
## M16 — the IOMMU, and the honest caveat
@@ -291,7 +291,7 @@ gap should be named rather than implied.
`M13` (capability passing) is independent of `M14`/`M15` and is the cheapest. It
unlocks class drivers, which are the shape with no hardware requirements at all — you
could write a real one against `busd`'s comparators tomorrow.
could write a real one against `bus`'s comparators tomorrow.
`M14` and `M15` together unlock the first HCD. `M14`'s barrier layer is worth landing
on its own regardless: it's small, obviously correct, and stops every future driver
+7 -7
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
the same check at the top of `sysMmioMap`):
- `device_service.ownerOf(dev_id) == me` — you claimed it, and claims are exclusive
- `devices_broker.ownerOf(dev_id) == me` — you claimed it, and claims are exclusive
- the resource at `res_idx` is of the right *kind* — `memory` for `mmio_map`, `irq`
for `irq_bind`
@@ -138,7 +138,7 @@ Two properties worth knowing:
## A whole driver
`system/drivers/hpetd/hpetd.zig` is ~150 lines and does all of it. The shape:
`system/drivers/hpet/hpet.zig` is ~150 lines and does all of it. The shape:
```zig
const hpet = findHpet(buf) orelse return; // device_enumerate, look for
@@ -206,7 +206,7 @@ bus driver may only ever subdivide what it already owns.
A device with **no resources** is legal and common. A USB device is reached through its
controller, not by MMIO, so it gets `resource_count = 0`.
See [`system/drivers/busd/busd.zig`](../system/drivers/busd/busd.zig) for a complete one, and
See [`system/drivers/bus/bus.zig`](../system/drivers/bus/bus.zig) for a complete one, and
[driver-model.md](driver-model.md) for how bus drivers, class drivers and host
controller drivers fit together.
@@ -269,8 +269,8 @@ Worth knowing before you write the second driver:
## Verifying it
The `hpet` test spawns `hpetd` from the initrd and watches the serial log. The driver
prints `hpetd: ok` only after being woken five times, and its loop's only exit is
The `hpet` test spawns `hpet` from the initial ramdisk and watches the serial log. The driver
prints `hpet: ok` only after being woken five times, and its loop's only exit is
through `replyWait` returning a notification — it cannot reach that line by polling.
The last check doesn't trust the driver's self-report at all: the kernel reads the I/O
@@ -285,7 +285,7 @@ $ python3 test/qemu_test.py hpet irqfree iopass
iopass ... PASS (matched 'DANOS-TEST-RESULT: PASS')
```
Two companions cover what `hpetd` can't, because it never exits:
Two companions cover what `hpet` can't, because it never exits:
- **`irqfree`** — the teardown path. Binds two owners to one shared endpoint, releases
one, and reads the I/O APIC back: the departing owner's line is masked, the sibling's
@@ -305,7 +305,7 @@ controller drivers), and the IOMMU — have proposed signatures in
I/O permission bitmap swapped on context switch, or `io_in`/`io_out` syscalls gated
by the same claim. The legacy devices that need it are all low-rate, so the syscall
is likely fast enough.
- **Releasing a claim.** There is no `dev_release`, and `device_service` never drops a claim on
- **Releasing a claim.** There is no `dev_release`, and `devices_broker` never drops a claim on
exit — only IRQ bindings are released. A dead driver's device stays owned forever,
which blocks restart.
- **Unregistering children.** `device_register` only appends. A USB device that is
+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 unistd = @import("unistd.zig");
const heap = @import("heap.zig");
const heap = @import("runtime").heap;
pub const SEEK_SET = unistd.SEEK_SET;
pub const SEEK_CURRENT = unistd.SEEK_CURRENT;
@@ -5,10 +5,10 @@
const std = @import("std");
const protocol = @import("vfs-protocol");
const ipc = @import("ipc.zig");
const ipc = @import("runtime").ipc;
const danos = @import("danos");
pub const O_CREAT = protocol.O_CREAT;
pub const O_CREAT = protocol.create;
pub const SEEK_SET: u32 = 0;
pub const SEEK_CURRENT: u32 = 1;
pub const SEEK_END: u32 = 2;
@@ -110,11 +110,11 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 {
SEEK_SET => 0,
SEEK_CURRENT => @intCast(f.offset),
SEEK_END => blk: {
const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined;
const request = protocol.Request{ .operation = .status, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
var sbuf: [@sizeOf(protocol.FileStatus)]u8 = undefined;
const r = transact(request, &.{}, &sbuf) orelse return -1;
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1;
const st = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]);
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.FileStatus)) return -1;
const st = std.mem.bytesToValue(protocol.FileStatus, sbuf[0..@sizeOf(protocol.FileStatus)]);
break :blk @intCast(st.size);
},
else => return -1,
@@ -126,17 +126,17 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 {
}
/// Stat `path`. Returns 0 or -1.
pub fn stat(path: []const u8, out: *protocol.Stat) i32 {
pub fn stat(path: []const u8, out: *protocol.FileStatus) i32 {
// Open, stat by node, close — simple and enough for now.
const fd = open(path, 0);
if (fd < 0) return -1;
defer close(fd);
const f = fdPtr(fd).?;
const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined;
const request = protocol.Request{ .operation = .status, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
var sbuf: [@sizeOf(protocol.FileStatus)]u8 = undefined;
const r = transact(request, &.{}, &sbuf) orelse return -1;
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1;
out.* = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]);
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.FileStatus)) return -1;
out.* = std.mem.bytesToValue(protocol.FileStatus, sbuf[0..@sizeOf(protocol.FileStatus)]);
return 0;
}
-2
View File
@@ -17,9 +17,7 @@ pub const start = @import("start.zig");
/// The VFS wire protocol (shared with the VFS server).
pub const vfs_protocol = @import("vfs-protocol");
/// POSIX-style file API: open/read/write/lseek/stat/close.
pub const unistd = @import("unistd.zig");
/// C stdio: fopen/fread/fwrite/fseek/ftell/fclose over unistd.
pub const stdio = @import("stdio.zig");
/// Device access for drivers: enumerate/claim/mmioMap.
pub const device = @import("device.zig");
+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
/// into memory that survives the handoff (classified reserved, so the kernel
/// identity-maps it and never allocates over it). 0/0 = no init found — the
/// kernel boots without user space. Grows into a full initrd handoff later.
/// kernel boots without user space. Grows into a full initial_ramdisk handoff later.
init_base: u64 = 0,
init_len: u64 = 0,
/// The initrd image (a bundle of extra user binaries — the VFS server and
/// The initial_ramdisk image (a bundle of extra user binaries — the VFS server and
/// device drivers), read off the boot volume into memory that survives the
/// handoff, same as `init` above. 0/0 = no initrd. See system/initrd.zig.
initrd_base: u64 = 0,
initrd_len: u64 = 0,
/// handoff, same as `init` above. 0/0 = no initial_ramdisk. See system/initial-ramdisk.zig.
initial_ramdisk_base: u64 = 0,
initial_ramdisk_len: u64 = 0,
};
+5 -5
View File
@@ -3,7 +3,7 @@
//!
//! This module has two stages. `parser.zig` walks the entire byte stream and
//! records every named object into a namespace tree (`namespace.zig`), capturing
//! method bodies and field/region layout. `interp.zig` then *evaluates* control
//! method bodies and field/region layout. `interpreter.zig` then *evaluates* control
//! methods on demand — running operators, control flow, and OperationRegion field
//! access — so callers can resolve device status (`_STA`), current resource
//! settings (`_CRS`), sleep states (`_Sx`), and the like against the live namespace.
@@ -17,10 +17,10 @@ pub const Node = @import("namespace.zig").Node;
pub const NodeKind = @import("namespace.zig").NodeKind;
/// The AML evaluator: interprets control methods (and reads Names/Fields) far
/// enough for device discovery. See `interp.zig`.
pub const Interpreter = @import("interp.zig").Interpreter;
pub const Object = @import("interp.zig").Object;
pub const EvaluateHal = @import("interp.zig").Hal;
/// enough for device discovery. See `interpreter.zig`.
pub const Interpreter = @import("interpreter.zig").Interpreter;
pub const Object = @import("interpreter.zig").Object;
pub const EvaluateHal = @import("interpreter.zig").Hal;
/// The SLP_TYP values written to PM1a/PM1b control to enter a sleep state.
pub const SleepType = struct {
@@ -1,4 +1,4 @@
//! /sbin/busd — a user-space **bus driver**, and the smallest honest example of one.
//! /sbin/bus — a user-space **bus driver**, and the smallest honest example of one.
//!
//! A bus driver owns a device that *contains other devices*, enumerates them by some
//! bus-specific protocol, and publishes each one into the kernel's device table so a
@@ -6,10 +6,10 @@
//! the "bus" is the HPET's register block and the "devices" are its comparators, each
//! a 0x20-byte window at 0x100 + 0x20*n that can be driven independently.
//!
//! It's a toy bus, but nothing about the mechanism is: `busd` reads how many children
//! It's a toy bus, but nothing about the mechanism is: `bus` reads how many children
//! exist from the hardware (GENERAL_CAP bits [12:8]), publishes one `DeviceDescriptor` per
//! child with a sub-window of its own MMIO plus the shared IRQ, and the kernel checks
//! every one of those resources is contained in what `busd` was granted. A comparator
//! every one of those resources is contained in what `bus` was granted. A comparator
//! driver then claims a child and maps only *its* registers — not the whole block.
//!
//! It also proves the negative: registering a child whose window escapes the parent's
@@ -65,30 +65,30 @@ fn firstChildOf(buffer: []device.DeviceDescriptor, total: usize, parent_id: u64)
pub fn main() void {
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("busd: out of memory\n");
_ = runtime.system.write("bus: out of memory\n");
return;
};
const parent = findHpet(buffer) orelse {
_ = runtime.system.write("busd: no HPET\n");
_ = runtime.system.write("bus: no HPET\n");
return;
};
const resource = resourcesOf(parent);
// Claim the bus. Everything below is subdivision of what this claim granted.
//
// Claims are exclusive, and at a normal boot the kernel spawns every initrd
// binary — so hpetd may own the HPET already. That's not an error, it's the
// Claims are exclusive, and at a normal boot the kernel spawns every initial_ramdisk
// binary — so hpet may own the HPET already. That's not an error, it's the
// capability model working: exit quietly and leave the device to its owner. The
// `bus` test spawns busd alone, so there it wins the claim.
// `bus` test spawns bus alone, so there it wins the claim.
if (!device.claim(parent.id)) {
_ = runtime.system.write("busd: HPET already claimed by another driver, nothing to do\n");
_ = runtime.system.write("bus: HPET already claimed by another driver, nothing to do\n");
return;
}
// Enumerate the bus: ask the hardware how many children it has.
const base = device.mmioMap(parent.id, 0) orelse {
_ = runtime.system.write("busd: mmio_map failed\n");
_ = runtime.system.write("bus: mmio_map failed\n");
return;
};
const cap: *volatile u64 = @ptrFromInt(base + register_general_cap);
@@ -112,7 +112,7 @@ pub fn main() void {
}
if (device.register(parent.id, &child) == null) {
_ = runtime.system.write("busd: register failed\n");
_ = runtime.system.write("bus: register failed\n");
return;
}
published += 1;
@@ -133,11 +133,11 @@ pub fn main() void {
.len = 0x1000,
};
if (device.register(parent.id, &rogue) != null) {
_ = runtime.system.write("busd: FAIL out-of-window child was accepted\n");
_ = runtime.system.write("bus: FAIL out-of-window child was accepted\n");
return;
}
if (device.enumerate(buffer) != before) {
_ = runtime.system.write("busd: FAIL rogue child leaked into the table\n");
_ = runtime.system.write("bus: FAIL rogue child leaked into the table\n");
return;
}
@@ -149,18 +149,18 @@ pub fn main() void {
if (d.parent != parent.id) continue;
const w = d.resources[0];
if (w.start < resource.mmio.start or w.len >= resource.mmio.len) {
_ = runtime.system.write("busd: FAIL child window is not inside the bus\n");
_ = runtime.system.write("bus: FAIL child window is not inside the bus\n");
return;
}
seen += 1;
}
if (seen != published) {
_ = runtime.system.write("busd: FAIL child count mismatch\n");
_ = runtime.system.write("bus: FAIL child count mismatch\n");
return;
}
// Delegation, end to end: claim a child and map *it*. A real class driver would be
// a different process; here busd plays both parts, which exercises the same path.
// a different process; here bus plays both parts, which exercises the same path.
// The child's window is 0x20 bytes at parent+0x100, so the register it sees at
// offset 0 must be the same timer-0 configuration register the bus sees at 0x100.
//
@@ -168,21 +168,21 @@ pub fn main() void {
// 4 KiB the HPET lives in — the granularity limit documented in docs/drivers.md.
// The *resource* is narrow even though the page isn't.)
const child_id = firstChildOf(buffer, device.enumerate(buffer), parent.id) orelse {
_ = runtime.system.write("busd: FAIL no child to claim\n");
_ = runtime.system.write("bus: FAIL no child to claim\n");
return;
};
if (!device.claim(child_id)) {
_ = runtime.system.write("busd: FAIL could not claim own child\n");
_ = runtime.system.write("bus: FAIL could not claim own child\n");
return;
}
const child_base = device.mmioMap(child_id, 0) orelse {
_ = runtime.system.write("busd: FAIL child mmio_map refused\n");
_ = runtime.system.write("bus: FAIL child mmio_map refused\n");
return;
};
const via_child: *volatile u64 = @ptrFromInt(child_base);
const via_bus: *volatile u64 = @ptrFromInt(base + 0x100);
if (via_child.* != via_bus.*) {
_ = runtime.system.write("busd: FAIL child window does not alias the bus register\n");
_ = runtime.system.write("bus: FAIL child window does not alias the bus register\n");
return;
}
@@ -195,12 +195,12 @@ pub fn main() void {
_ = runtime.system.munmap(scratch, 0x1000);
const descriptor: *const device.DeviceDescriptor = @ptrFromInt(scratch);
if (device.register(parent.id, descriptor) != null) {
_ = runtime.system.write("busd: FAIL register accepted an unmapped descriptor\n");
_ = runtime.system.write("bus: FAIL register accepted an unmapped descriptor\n");
return;
}
}
_ = runtime.system.write("busd: ok\n");
_ = runtime.system.write("bus: ok\n");
while (true) runtime.system.sleep(1000);
}
@@ -1,4 +1,4 @@
//! /sbin/hpetd — a user-space HPET driver. It proves the whole driver model end to
//! /sbin/hpet — a user-space HPET driver. It proves the whole driver model end to
//! end: enumerate the device table, find the HPET, claim it, map its registers into
//! this ring-3 address space (strong-uncacheable), **bind its interrupt to an IPC
//! endpoint**, then sit blocked in `replyWait` until the hardware wakes it.
@@ -13,8 +13,8 @@
//! the full cycle to be correct:
//!
//! kernel ISR mask the GSI -> EOI -> notify this endpoint
//! hpetd wake, clear GENERAL_INT_STATUS (deasserts the line), re-arm
//! hpetd irq_ack -> kernel unmasks the GSI
//! hpet wake, clear GENERAL_INT_STATUS (deasserts the line), re-arm
//! hpet irq_ack -> kernel unmasks the GSI
//!
//! Clear the status bit *before* acking, or the line is still asserted when the
//! kernel unmasks and the I/O APIC redelivers forever.
@@ -64,7 +64,7 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(device.DeviceClass.timer)) continue;
// Skip comparator children a bus driver may have published below the block
// (see system/drivers/busd/busd.zig) — we want the register block itself.
// (see system/drivers/bus/bus.zig) — we want the register block itself.
if (d.parent != device.no_parent) continue;
var mmio: ?u64 = null;
var irq: ?u64 = null;
@@ -85,21 +85,21 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
pub fn main() void {
// Enumerate into a heap buffer (too big for the one-page user stack).
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch {
_ = runtime.system.write("hpetd: out of memory\n");
_ = runtime.system.write("hpet: out of memory\n");
return;
};
const hpet = findHpet(buffer) orelse {
_ = runtime.system.write("hpetd: no HPET with an IRQ\n");
_ = runtime.system.write("hpet: no HPET with an IRQ\n");
return;
};
if (!device.claim(hpet.device_id)) {
_ = runtime.system.write("hpetd: claim failed\n");
_ = runtime.system.write("hpet: claim failed\n");
return;
}
const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse {
_ = runtime.system.write("hpetd: mmio_map failed\n");
_ = runtime.system.write("hpet: mmio_map failed\n");
return;
};
@@ -108,7 +108,7 @@ pub fn main() void {
const gsi = hpet.gsi;
const endpoint = ipc.createEndpoint() orelse {
_ = runtime.system.write("hpetd: create_endpoint failed\n");
_ = runtime.system.write("hpet: create_endpoint failed\n");
return;
};
@@ -116,7 +116,7 @@ pub fn main() void {
// Counter period, so we can arm the comparator a fixed wall-clock distance out.
const femtos_per_tick = register(base, register_general_cap).* >> 32;
if (femtos_per_tick == 0) {
_ = runtime.system.write("hpetd: bad HPET period\n");
_ = runtime.system.write("hpet: bad HPET period\n");
return;
}
const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick;
@@ -139,10 +139,10 @@ pub fn main() void {
register(base, register_general_configuration).* |= configuration_enable;
if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) {
_ = runtime.system.write("hpetd: irq_bind failed\n");
_ = runtime.system.write("hpet: irq_bind failed\n");
return;
}
_ = runtime.system.write("hpetd: bound, sleeping until the hardware speaks\n");
_ = runtime.system.write("hpet: bound, sleeping until the hardware speaks\n");
// --- the driver loop -----------------------------------------------------
// Blocked in replyWait. No polling, no spinning: the next line of this function
@@ -170,14 +170,14 @@ pub fn main() void {
register(base, register_timer0_configuration).* &= ~tn_int_enb;
}
_ = runtime.system.write("hpetd: irq\n");
_ = runtime.system.write("hpet: irq\n");
if (!device.irqAck(hpet.device_id, hpet.irq)) {
_ = runtime.system.write("hpetd: irq_ack failed\n");
_ = runtime.system.write("hpet: irq_ack failed\n");
return;
}
}
_ = runtime.system.write("hpetd: ok\n");
_ = runtime.system.write("hpet: ok\n");
while (true) runtime.system.sleep(1000);
}
@@ -1,5 +1,5 @@
//! The initrd (initial ramdisk) container format — shared by the build-time
//! packer (tools/mkinitrd.zig) and the kernel that unpacks it. Deliberately
//! The initial_ramdisk (initial ramdisk) container format — shared by the build-time
//! packer (tools/make-initial-ramdisk.py) and the kernel that unpacks it. Deliberately
//! trivial: a header, a table of fixed-size entries, then the concatenated file
//! blobs. We own both producer and consumer, so it need be no fancier.
//!
@@ -10,7 +10,7 @@
const std = @import("std");
/// "DNRD" — identifies a danos initrd image.
/// "DNRD" — identifies a danos initial_ramdisk image.
pub const magic: u32 = 0x444E5244;
pub const Header = extern struct {
@@ -24,7 +24,7 @@ pub const Entry = extern struct {
len: u64, // blob length in bytes
};
/// A validated view over an initrd image. `init` checks the magic and that the
/// A validated view over an initial_ramdisk image. `init` checks the magic and that the
/// entry table fits; `entry` bounds-checks each blob against the image.
pub const Reader = struct {
image: []const u8,
+2 -2
View File
@@ -22,7 +22,7 @@
//!
//! Binding is capability-gated exactly like `mmio_map`: the caller must have
//! `device_claim`ed the device, and the GSI must come from one of that device's `irq`
//! resources in the discovered device table (system/kernel/device-service.zig). A driver can
//! resources in the discovered device table (system/kernel/devices-broker.zig). A driver can
//! therefore never bind an interrupt it doesn't own — a raw-GSI system_call would let
//! any process steal the keyboard's line.
//!
@@ -152,7 +152,7 @@ pub fn bind(gsi: u32, endpoint: *ipc_sync.Endpoint, owner: u32) BindError!void {
bound_owner[gsi] = owner;
// Level-triggered, active-high. Level is the general case a driver must survive
// (and what hpetd configures its comparator for); an edge source simply never
// (and what hpet configures its comparator for); an edge source simply never
// leaves the line asserted, so the mask/ack cycle is harmless there.
//
// Hardcoded for now: a device whose MADT interrupt-source override declares the
+15 -15
View File
@@ -8,9 +8,9 @@ const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const scheduler = @import("scheduler.zig");
const process = @import("process.zig");
const device_service = @import("device-service.zig");
const devices_broker = @import("devices-broker.zig");
const irq = @import("irq.zig");
const initrd = @import("initrd");
const initial_ramdisk = @import("initial-ramdisk");
const platform = @import("platform");
const tests = @import("tests.zig");
const build_options = @import("build_options");
@@ -161,10 +161,10 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// Snapshot the device tree for user-space drivers (device_enumerate/claim/
// mmio_map operate on this flat, id-indexed table + claim map).
device_service.init(&device_tree);
if (device_service.dropped > 0) {
devices_broker.init(&device_tree);
if (devices_broker.dropped > 0) {
// Otherwise entirely silent: drivers would just never see that hardware.
log.print("danos: WARNING {d} device(s) dropped — table full\n", .{device_service.dropped});
log.print("danos: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped});
}
// Install the device-IRQ trampolines, so a driver's irq_bind has vectors to
@@ -285,10 +285,10 @@ fn kmain(boot_information: *const BootInformation) noreturn {
status("no /sbin/init on the boot volume.\n");
}
// Spawn the extra user binaries the loader ferried in the initrd (the VFS
// Spawn the extra user binaries the loader ferried in the initial_ramdisk (the VFS
// server, and later device drivers). For now the kernel launches them all;
// once init is a real service supervisor it will spawn them itself (system_spawn).
startInitrdBinaries(boot_information);
startInitialRamdiskBinaries(boot_information);
// Become the idle task: drop below every real task and halt until an
// interrupt. The timer keeps preempting into init and any other work.
@@ -297,22 +297,22 @@ fn kmain(boot_information: *const BootInformation) noreturn {
architecture.halt();
}
/// Spawn every program bundled in the initrd as its own ring-3 process. A bad
/// Spawn every program bundled in the initial_ramdisk as its own ring-3 process. A bad
/// image or a program that fails to load is logged and skipped — the rest of the
/// system still runs.
fn startInitrdBinaries(boot_information: *const danos.BootInformation) void {
if (boot_information.initrd_len == 0) return;
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
const rd = initrd.Reader.init(image) orelse {
status("initrd: bad image, skipping\n");
fn startInitialRamdiskBinaries(boot_information: *const danos.BootInformation) void {
if (boot_information.initial_ramdisk_len == 0) return;
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
status("initial_ramdisk: bad image, skipping\n");
return;
};
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
statusPrint("starting /sbin/{s} (from initrd)...\n", .{item.name});
statusPrint("starting /sbin/{s} (from initial_ramdisk)...\n", .{item.name});
process.spawnProcess(item.blob, 4) catch |err| {
statusPrint("initrd: {s} failed to load: {s}\n", .{ item.name, @errorName(err) });
statusPrint("initial_ramdisk: {s} failed to load: {s}\n", .{ item.name, @errorName(err) });
};
}
}
+8 -8
View File
@@ -27,7 +27,7 @@ const pmm = @import("pmm.zig");
const scheduler = @import("scheduler.zig");
const sync = @import("sync.zig");
const ipc = @import("ipc-synchronous.zig");
const device_service = @import("device-service.zig");
const devices_broker = @import("devices-broker.zig");
const irq = @import("irq.zig");
const log = @import("log.zig");
@@ -206,12 +206,12 @@ fn systemDeviceEnumerate(state: *architecture.CpuState) void {
const sz = @sizeOf(danos.DeviceDescriptor);
const cap = @min(maximum, (user_half_end - buffer_ptr) / sz); // clamp to the user half
const out: [*]danos.DeviceDescriptor = @ptrFromInt(buffer_ptr);
architecture.setSystemCallResult(state, device_service.enumerate(out[0..@intCast(cap)]));
architecture.setSystemCallResult(state, devices_broker.enumerate(out[0..@intCast(cap)]));
}
/// device_claim(id) -> 0/-1: take exclusive ownership of a device for this process.
fn systemDeviceClaim(state: *architecture.CpuState) void {
if (device_service.claim(architecture.systemCallArg(state, 0), scheduler.current().id))
if (devices_broker.claim(architecture.systemCallArg(state, 0), scheduler.current().id))
architecture.setSystemCallResult(state, 0)
else
fail(state);
@@ -225,9 +225,9 @@ fn systemMmioMap(state: *architecture.CpuState) void {
const resource_index = architecture.systemCallArg(state, 1);
const t = scheduler.current();
if (t.aspace == 0) return fail(state);
const owner = device_service.ownerOf(device_id) orelse return fail(state);
const owner = devices_broker.ownerOf(device_id) orelse return fail(state);
if (owner != t.id) return fail(state); // not claimed by this process
const r = device_service.resourceOf(device_id, resource_index) orelse return fail(state);
const r = devices_broker.resourceOf(device_id, resource_index) orelse return fail(state);
if (r.kind != @intFromEnum(danos.ResourceKind.memory)) return fail(state);
if (t.device_map_next == 0) t.device_map_next = device_arena_base;
@@ -263,7 +263,7 @@ fn systemDeviceRegister(state: *architecture.CpuState) void {
var descriptor: danos.DeviceDescriptor = undefined;
if (!ipc.copyFromUser(t.aspace, descriptor_ptr, std.mem.asBytes(&descriptor))) return fail(state);
const id = device_service.register(parent_id, t.id, &descriptor) catch return fail(state);
const id = devices_broker.register(parent_id, t.id, &descriptor) catch return fail(state);
architecture.setSystemCallResult(state, id);
}
@@ -281,9 +281,9 @@ fn releaseIrqs(t: *scheduler.Task) void {
/// by discovery. Neither a raw GSI nor an unclaimed device can get through — which
/// is why irq_bind takes a resource index and not an interrupt number.
fn ownedGsi(t: *scheduler.Task, device_id: u64, resource_index: u64) ?u32 {
const owner = device_service.ownerOf(device_id) orelse return null;
const owner = devices_broker.ownerOf(device_id) orelse return null;
if (owner != t.id) return null;
const r = device_service.resourceOf(device_id, resource_index) orelse return null;
const r = devices_broker.resourceOf(device_id, resource_index) orelse return null;
if (r.kind != @intFromEnum(danos.ResourceKind.irq)) return null;
if (r.start >= irq.maximum_gsi) return null;
return @intCast(r.start);
+53 -53
View File
@@ -12,7 +12,7 @@
const std = @import("std");
const danos = @import("danos");
const architecture = @import("architecture");
const device_service = @import("device-service.zig");
const devices_broker = @import("devices-broker.zig");
const platform = @import("platform");
const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
@@ -22,7 +22,7 @@ const ipcsync = @import("ipc-synchronous.zig");
const irq = @import("irq.zig");
const sync = @import("sync.zig");
const process = @import("process.zig");
const initrd = @import("initrd");
const initial_ramdisk = @import("initial-ramdisk");
/// Formatted write straight to serial, independent of the framebuffer console.
fn log(comptime fmt: []const u8, args: anytype) void {
@@ -108,8 +108,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
initTest(boot_information);
} else if (eql(case, "process")) {
processTest(boot_information);
} else if (eql(case, "initrd")) {
initrdTest(boot_information);
} else if (eql(case, "initial-ramdisk")) {
initialRamdiskTest(boot_information);
} else if (eql(case, "vfs")) {
vfsTest(boot_information);
} else if (eql(case, "hpet")) {
@@ -952,24 +952,24 @@ fn initTest(boot_information: *const BootInformation) void {
result();
}
/// The initrd path: the bootloader handed over an image bundling extra user
/// The initial_ramdisk path: the bootloader handed over an image bundling extra user
/// binaries; parse it, spawn every program, and confirm one (the vfs stub)
/// reaches ring 3 and heartbeats — proving the whole ferry-parse-spawn pipeline.
fn initrdTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: initrd\n", .{});
check("bootloader handed over an initrd", boot_information.initrd_len != 0);
if (boot_information.initrd_len == 0) {
fn initialRamdiskTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: initial_ramdisk\n", .{});
check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
if (boot_information.initial_ramdisk_len == 0) {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
const rd = initrd.Reader.init(image) orelse {
check("initrd image is valid", false);
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
check("initrd image is valid", true);
check("initrd contains at least one binary", rd.count >= 1);
check("initial_ramdisk image is valid", true);
check("initial_ramdisk contains at least one binary", rd.count >= 1);
process.write_count = 0;
process.write_from_user = false;
@@ -981,7 +981,7 @@ fn initrdTest(boot_information: *const BootInformation) void {
log("DANOS-INITRD-ERR: {s}: {s}\n", .{ item.name, @errorName(err) });
}
}
check("every initrd binary spawned", spawned == rd.count);
check("every initial_ramdisk binary spawned", spawned == rd.count);
// Wait for the spawned programs to run and make syscalls (they write + sleep).
scheduler.setPriority(1);
@@ -989,33 +989,33 @@ fn initrdTest(boot_information: *const BootInformation) void {
while (process.write_count < 2 and architecture.millis() < deadline) scheduler.yield();
scheduler.setPriority(4);
check("initrd processes ran and made syscalls (>=2)", process.write_count >= 2);
check("initial_ramdisk processes ran and made syscalls (>=2)", process.write_count >= 2);
check("syscalls came from user mode (CPL 3)", process.write_from_user);
result();
}
/// The full VFS path: spawn the user-space VFS server and a client from the
/// initrd. The client opens a file through the runtime file API, writes, seeks, reads
/// initial_ramdisk. The client opens a file through the runtime file API, writes, seeks, reads
/// it back, and — only if the round trip matched — heartbeats "vfstest: ok". So
/// seeing that marker proves client open/write/read reached the server over IPC
/// and came back correct. (The client retries until the server registers.)
fn vfsTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: vfs\n", .{});
if (boot_information.initrd_len == 0) {
check("bootloader handed over an initrd", false);
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
const rd = initrd.Reader.init(image) orelse {
check("initrd image is valid", false);
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
process.write_count = 0;
process.write_from_user = false;
// Spawn just the server and its client (other initrd binaries would write to
// Spawn just the server and its client (other initial_ramdisk binaries would write to
// the shared evidence buffer and confuse the marker check).
_ = spawnNamed(rd, "vfs");
_ = spawnNamed(rd, "vfs-test");
@@ -1037,9 +1037,9 @@ fn vfsTest(boot_information: *const BootInformation) void {
result();
}
/// Spawn the initrd binary named `name` as a ring-3 process. Returns false if it
/// Spawn the initial_ramdisk binary named `name` as a ring-3 process. Returns false if it
/// isn't in the image or fails to load.
fn spawnNamed(rd: initrd.Reader, name: []const u8) bool {
fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
@@ -1051,36 +1051,36 @@ fn spawnNamed(rd: initrd.Reader, name: []const u8) bool {
}
/// IO passthrough + IRQ-as-IPC: a user-space driver drives real hardware and is
/// *woken by it*. Spawn hpetd, which claims the HPET, maps its registers into its
/// *woken by it*. Spawn hpet, which claims the HPET, maps its registers into its
/// own ring-3 address space, arms a level-triggered comparator, binds the interrupt
/// to an IPC endpoint, and then blocks. It prints "hpetd: ok" only after being woken
/// to an IPC endpoint, and then blocks. It prints "hpet: ok" only after being woken
/// `target_ticks` times — it cannot reach that line by polling, because the loop's
/// only exit is through `replyWait` returning a notification badge.
///
/// The interesting assertion is the last one, which doesn't trust hpetd at all: it
/// The interesting assertion is the last one, which doesn't trust hpet at all: it
/// reads the I/O APIC's redirection entry back and checks the kernel really routed
/// the line (our vector, level-triggered) and really left it unmasked after the
/// driver's final `irq_ack`. hpetd disables its comparator on the last interrupt, so
/// driver's final `irq_ack`. hpet disables its comparator on the last interrupt, so
/// that state is quiescent and not a race.
fn hpetTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: hpet\n", .{});
if (boot_information.initrd_len == 0) {
check("bootloader handed over an initrd", false);
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
const rd = initrd.Reader.init(image) orelse {
check("initrd image is valid", false);
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
process.write_count = 0;
process.write_from_user = false;
check("hpetd spawned from the initrd", spawnNamed(rd, "hpetd"));
check("hpet spawned from the initial_ramdisk", spawnNamed(rd, "hpet"));
const prefix = "hpetd: ok";
const prefix = "hpet: ok";
scheduler.setPriority(1);
const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) {
@@ -1114,7 +1114,7 @@ fn hpetRouteOk() bool {
/// The GSI discovery recorded for the HPET, from the same device table the driver saw.
fn hpetGsi() ?u32 {
var buffer: [16]danos.DeviceDescriptor = undefined;
const n = @min(device_service.enumerate(&buffer), buffer.len);
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue;
if (d.parent != danos.no_parent) continue; // the block, not a comparator child
@@ -1130,34 +1130,34 @@ fn hpetGsi() ?u32 {
/// them from the hardware, and publishes each as a child via `device_register` — the
/// primitive a PCI bridge or USB hub driver is built from.
///
/// `busd` treats the HPET's register block as a bus and its comparators as children,
/// `bus` treats the HPET's register block as a bus and its comparators as children,
/// giving each a 0x20 sub-window. It checks its own work (children come back from the
/// table with the right parent and a strictly narrower window) and, importantly, that
/// the kernel **refuses** a child whose window escapes the parent's — without that,
/// `device_register` would be a system_call for mapping arbitrary physical memory. It prints
/// "busd: ok" only if all of that holds.
/// "bus: ok" only if all of that holds.
///
/// The kernel-side check here is the one busd can't make: that the children really did
/// The kernel-side check here is the one bus can't make: that the children really did
/// land in the device table with the containment invariant intact.
fn busTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: bus\n", .{});
if (boot_information.initrd_len == 0) {
check("bootloader handed over an initrd", false);
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
const rd = initrd.Reader.init(image) orelse {
check("initrd image is valid", false);
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
process.write_count = 0;
process.write_from_user = false;
check("busd spawned from the initrd", spawnNamed(rd, "busd"));
check("bus spawned from the initial_ramdisk", spawnNamed(rd, "bus"));
const prefix = "busd: ok";
const prefix = "bus: ok";
scheduler.setPriority(1);
const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) {
@@ -1173,7 +1173,7 @@ fn busTest(boot_information: *const BootInformation) void {
result();
}
/// Every child `busd` registered must have each of its resources inside a parent
/// Every child `bus` registered must have each of its resources inside a parent
/// resource of the same kind — the invariant `device_register` exists to maintain,
/// checked from the kernel's own table rather than the driver's word for it.
///
@@ -1182,7 +1182,7 @@ fn busTest(boot_information: *const BootInformation) void {
/// bridge's `bus_range`, because a bus-number range isn't an address window.
fn childrenContained() bool {
var buffer: [64]danos.DeviceDescriptor = undefined;
const n = @min(device_service.enumerate(&buffer), buffer.len);
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
const bus_id = hpetDeviceId() orelse return false;
const p = buffer[@intCast(bus_id)];
@@ -1205,13 +1205,13 @@ fn childrenContained() bool {
if (!ok) return false;
}
}
return children > 0; // busd must have published at least one
return children > 0; // bus must have published at least one
}
/// Device id of the HPET (the bus busd claims), from the same table drivers see.
/// Device id of the HPET (the bus bus claims), from the same table drivers see.
fn hpetDeviceId() ?u64 {
var buffer: [64]danos.DeviceDescriptor = undefined;
const n = @min(device_service.enumerate(&buffer), buffer.len);
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue;
if (d.parent != danos.no_parent) continue; // a comparator child, not the block
@@ -1226,7 +1226,7 @@ fn hpetDeviceId() ?u64 {
/// (so a dead driver's device goes quiet instead of storming) and the slot cleared
/// (so an ISR never posts a notification into the endpoint that is about to be freed).
///
/// This is the path `hpetd` never takes — it runs forever — so it gets its own test.
/// This is the path `hpet` never takes — it runs forever — so it gets its own test.
/// Two properties, both read back from the hardware rather than from our own state:
///
/// 1. A bound GSI is routed and unmasked.
+17 -11
View File
@@ -1,18 +1,22 @@
//! The VFS wire protocol — the message format spoken between a client (via the
//! `runtime` file API) and the user-space VFS server over IPC. A request is a fixed
//! `Request` header followed by an inline payload (a path, or write bytes); a
//! reply is a fixed `Reply` header followed by an inline payload (read bytes, or
//! a Stat). Everything fits in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes).
//! The VFS wire protocol — the message format spoken between a client (via the file
//! API) and the user-space VFS server over IPC. A request is a fixed `Request` header
//! followed by an inline payload (a path, or write bytes); a reply is a fixed `Reply`
//! header followed by an inline payload (read bytes, or a FileStatus). Everything fits
//! in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes).
//!
//! This is a danos-native contract, so it uses danos names throughout — the POSIX
//! spellings (`stat`, `O_CREAT`, ...) live only in the POSIX layer
//! (library/posix/unistd.zig), which translates to these.
//!
//! This is user-space only — the kernel knows nothing of files or paths; it only
//! moves the bytes. Shared by library/runtime/unistd.zig (client) and system/services/vfs/vfs.zig (server).
//! moves the bytes. Shared by library/posix/unistd.zig (client) and system/services/vfs/vfs.zig (server).
pub const Operation = enum(u32) {
open, // open(path) -> node id
close, // close(node)
read, // read(node, offset, len) -> bytes
write, // write(node, offset, bytes) -> count
stat, // stat(node) -> Stat
status, // status(node) -> FileStatus
};
/// Request header. `node` is the server-side open-file id (from a prior open);
@@ -28,7 +32,7 @@ pub const Request = extern struct {
/// Reply header. `status` is 0 on success or a negative errno; `node` is the new
/// open-file id (for `open`); `len` is the payload length (bytes read, or the
/// Stat size).
/// FileStatus size).
pub const Reply = extern struct {
status: i32,
_padding: u32 = 0,
@@ -37,7 +41,9 @@ pub const Reply = extern struct {
_padding2: u32 = 0,
};
pub const Stat = extern struct {
/// A file's metadata (the danos-native answer to a `status` request). The POSIX
/// layer maps this onto `struct stat`.
pub const FileStatus = extern struct {
size: u64,
kind: u32,
_padding: u32 = 0,
@@ -49,5 +55,5 @@ pub const reply_size: usize = @sizeOf(Reply);
/// Largest inline payload that still fits one IPC message alongside a header.
pub const maximum_payload: usize = message_maximum - request_size;
/// Open flags.
pub const O_CREAT: u32 = 1;
/// Open flags (danos-native; the POSIX layer maps `O_CREAT` onto `create`).
pub const create: u32 = 1;
+2 -2
View File
@@ -1,13 +1,13 @@
//! /sbin/vfstest — a client that proves the VFS round trip end to end: open a
//! file through the `runtime` file API, write to it, seek back, read it, and compare.
//! On success it heartbeats "vfstest: ok" so the kernel test can observe it;
//! on failure it reports what went wrong. Shipped in the initrd alongside vfs.
//! on failure it reports what went wrong. Shipped in the initial_ramdisk alongside vfs.
const std = @import("std");
const runtime = @import("runtime");
pub fn main() void {
const u = runtime.unistd;
const u = @import("posix").unistd;
const payload = "hello-vfs";
// The VFS server may not have registered yet — retry open until it's up.
+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`
//! file API marshals open/read/write/stat/close into calls to this server's
//! endpoint, published under the well-known `vfs` service id).
@@ -101,10 +101,10 @@ fn handle(message: []const u8, out: []u8) usize {
if (off + n > nd.size) nd.size = off + n;
return writeReply(out, .{ .status = 0, .len = @intCast(n) }, &.{});
},
.stat => {
.status => {
const of = openAt(request.node) orelse return fail(out);
const st = protocol.Stat{ .size = nodes[of.node].size, .kind = 0 };
return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.Stat) }, std.mem.asBytes(&st));
const st = protocol.FileStatus{ .size = nodes[of.node].size, .kind = 0 };
return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.FileStatus) }, std.mem.asBytes(&st));
},
.close => {
if (request.node < opens.len) opens[@intCast(request.node)].used = false;
+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)
"kernel": ("kernel", "kernel"),
# Further files shipped on the ESP: the init user program and the initrd
# Further files shipped on the ESP: the init user program and the initial_ramdisk
# (VFS server + drivers), both copied from zig-out/bin.
"extra": [("sbin/init", "init"), ("initrd.img", "initrd.img")],
"extra": [("sbin/init", "init"), ("initial-ramdisk.img", "initial-ramdisk.img")],
# Built as a function so we can splice in per-run paths.
"qemu_args": lambda a, esp, vars_fd, serial: [
"-machine", "q35", "-m", "128M",
@@ -178,9 +178,9 @@ CASES = [
"smp": 4,
"expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# The initrd: the loader ferries a bundle of user binaries; the kernel parses
# The initial_ramdisk: the loader ferries a bundle of user binaries; the kernel parses
# it and spawns each as a ring-3 process (here the VFS-server stub heartbeats).
{"name": "initrd",
{"name": "initial-ramdisk",
"expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# The user-space VFS: a client opens/writes/reads a file through the rt file
@@ -202,7 +202,7 @@ CASES = [
"fail": r"DANOS-TEST-RESULT: FAIL"},
# IRQ teardown: an exiting driver's line is masked and its slot cleared (so no
# ISR notifies a freed endpoint), and a sibling owner sharing that endpoint
# keeps its own binding. The path hpetd never takes, since it runs forever.
# keeps its own binding. The path hpet never takes, since it runs forever.
{"name": "irqfree",
"expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"},
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
"""Build-time initrd packer. Concatenates user binaries into one image the
"""Build-time initial_ramdisk packer. Concatenates user binaries into one image the
bootloader ferries to the kernel.
Usage: mkinitrd.py <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
Entry*N : name [32]u8 (NUL-padded), offset u64, len u64
blobs : each entry's file bytes at its offset
@@ -21,7 +21,7 @@ def main() -> int:
out_path = sys.argv[1]
rest = sys.argv[2:]
if len(rest) % 2 != 0:
sys.stderr.write("usage: mkinitrd.py <out.img> [<name> <file>]...\n")
sys.stderr.write("usage: make-initial-ramdisk.py <out.img> [<name> <file>]...\n")
return 2
items = [(rest[i], rest[i + 1]) for i in range(0, len(rest), 2)]