Split the system contract into boot-handoff / abi / device-abi

The `system` module (formerly `danos`) had become a grab-bag: it held the
loader<->kernel handoff *and* the kernel<->user ABI *and* the device wire types, in
one module three different audiences imported. Usage proved the seam — the
bootloader never touched the syscall/device ABI, and user space never touched the
boot handoff — so split it by audience, one module per contract:

  system/boot-handoff.zig       loader <-> kernel: BootInformation, Framebuffer,
                                MemoryMap, the VM layout + physicalToVirtual, kernel_abi
  system/abi.zig                kernel <-> user, core: SystemCall, mmap prot flags,
                                page_size, notify_badge_bit, ServiceId
  system/devices/device-abi.zig kernel <-> user, devices: DeviceDescriptor,
                                DeviceClass, ResourceDescriptor, ResourceKind, ...

device-abi is the devices sub-project's public interface, exposed as its own module
the way vfs exposes vfs-protocol — importable by user space, unlike the
kernel-internal device model it also feeds. That collapses a real duplication:
DeviceClass and ResourceKind were defined twice (device-model.zig and the contract,
kept "in sync by hand"); device-model now re-exports them from device-abi, so the
enum a driver matches on and the one the kernel classifies with are one type.

Each import now declares which contract it speaks: the bootloader imports only
boot-handoff; a driver only abi + device-abi (via the runtime); the kernel all
three. This also retires the `system` / `runtime.system` name overlap. page_size
lands in abi (it's part of the mmap contract user space aligns to); the bootloader
keeps its own local 4 KiB constant so it depends on nothing but the handoff.

All 21 importers rewired, docs updated to keep /system mapping to source. Build,
host tests, and the QEMU suite (36/36) all green.
This commit is contained in:
Daniel Samson
2026-07-10 18:08:51 +01:00
parent 47610e8ee2
commit be81394be3
37 changed files with 395 additions and 337 deletions
+51 -27
View File
@@ -95,11 +95,23 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Shared handoff definitions (BootInformation, Framebuffer, ...). No target is set,
// so the module inherits the target of whichever binary imports it — the
// freestanding kernel or the UEFI bootloader.
const system_module = b.addModule("system", .{
.root_source_file = b.path("system/system.zig"),
// The three shared contracts, each with its own audience so every import
// declares which one it speaks (no target is set, so each inherits the target of
// whichever binary imports it). See docs/coding-standards.md.
// boot-handoff : loader <-> kernel (BootInformation, framebuffer, VM layout)
// abi : kernel <-> user, core (SystemCall, mmap prot flags, page_size)
// device-abi : kernel <-> user, devices (DeviceDescriptor, DeviceClass, ...)
const boot_handoff_module = b.addModule("boot-handoff", .{
.root_source_file = b.path("system/boot-handoff.zig"),
});
const abi_module = b.addModule("abi", .{
.root_source_file = b.path("system/abi.zig"),
});
// The devices sub-project's public interface (the flat wire types), exposed as
// its own module like vfs-protocol — importable by user space, unlike the
// kernel-internal device model it also feeds (system/devices/device-model.zig).
const device_abi_module = b.addModule("device-abi", .{
.root_source_file = b.path("system/devices/device-abi.zig"),
});
// Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of
@@ -115,7 +127,8 @@ pub fn build(b: *std.Build) void {
const architecture_module = b.addModule("architecture", .{
.root_source_file = b.path("system/kernel/architecture/x86_64/cpu.zig"),
.imports = &.{
.{ .name = "system", .module = system_module }, // paging uses the shared BootInformation/memory-map types
.{ .name = "boot-handoff", .module = boot_handoff_module }, // paging uses BootInformation/memory-map + physicalToVirtual
.{ .name = "abi", .module = abi_module }, // paging works in page_size units
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus, ist_stack_size, timer_hz
},
});
@@ -134,7 +147,9 @@ pub fn build(b: *std.Build) void {
const platform_module = b.addModule("platform", .{
.root_source_file = b.path("system/devices/platform.zig"),
.imports = &.{
.{ .name = "system", .module = system_module }, // BootInformation (carries the ACPI RSDP)
.{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual
.{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units
.{ .name = "device-abi", .module = device_abi_module }, // device-model's DeviceClass/ResourceKind live here
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
},
});
@@ -152,13 +167,16 @@ pub fn build(b: *std.Build) void {
// 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.
// `.large` code model — do NOT set a target/code_model here. It imports `abi`
// for the shared SystemCall numbers / mmap flags, `device-abi` for the device
// types its `device` helper wraps, and re-exports `vfs-protocol` for the VFS
// server. It never touches `boot-handoff` — user space has no business with the
// loader↔kernel handoff.
const runtime_module = b.addModule("runtime", .{
.root_source_file = b.path("library/runtime/runtime.zig"),
.imports = &.{
.{ .name = "system", .module = system_module },
.{ .name = "abi", .module = abi_module },
.{ .name = "device-abi", .module = device_abi_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module },
},
});
@@ -172,7 +190,6 @@ pub fn build(b: *std.Build) void {
.imports = &.{
.{ .name = "runtime", .module = runtime_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module },
.{ .name = "system", .module = system_module },
},
});
@@ -212,7 +229,9 @@ pub fn build(b: *std.Build) void {
.stack_check = false, // stack-probe calls have no runtime to land in
.stack_protector = false,
.imports = &.{
.{ .name = "system", .module = system_module },
.{ .name = "boot-handoff", .module = boot_handoff_module },
.{ .name = "abi", .module = abi_module },
.{ .name = "device-abi", .module = device_abi_module },
.{ .name = "architecture", .module = architecture_module },
.{ .name = "platform", .module = platform_module },
.{ .name = "parameters", .module = parameters_module },
@@ -305,7 +324,8 @@ pub fn build(b: *std.Build) void {
}),
.optimize = optimize,
.imports = &.{
.{ .name = "system", .module = system_module },
// The bootloader speaks only the handoff contract — never the user ABI.
.{ .name = "boot-handoff", .module = boot_handoff_module },
},
}),
});
@@ -401,18 +421,22 @@ pub fn build(b: *std.Build) void {
// }
// Tests run on the host. The kernel and bootloader target freestanding/UEFI
// and can't be executed natively, so only the shared module is unit-tested
// here (compiled for the host rather than inheriting a freestanding target).
const mod_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("system/system.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_mod_tests = b.addRunArtifact(mod_tests);
// and can't be executed natively, so only the shared contracts are unit-tested
// here (compiled for the host rather than inheriting a freestanding target) —
// which also compile-checks that the three-way split stays self-consistent.
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
for ([_][]const u8{
"system/boot-handoff.zig",
"system/abi.zig",
"system/devices/device-abi.zig",
}) |root| {
const mod_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path(root),
.target = target,
.optimize = optimize,
}),
});
test_step.dependOn(&b.addRunArtifact(mod_tests).step);
}
}