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
+30 -28
View File
@@ -10,7 +10,9 @@
//! exception report the handler prints (which also reaches serial).
const std = @import("std");
const system = @import("system");
const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const device_abi = @import("device-abi");
const architecture = @import("architecture");
const devices_broker = @import("devices-broker.zig");
const platform = @import("platform");
@@ -155,7 +157,7 @@ fn powerTest(comptime action: enum { off, reboot }) void {
result();
}
const BootInformation = system.BootInformation;
const BootInformation = boot_handoff.BootInformation;
fn eql(a: []const u8, b: []const u8) bool {
return std.mem.eql(u8, a, b);
@@ -167,7 +169,7 @@ fn smoke(boot_information: *const BootInformation) void {
// The memory map has some usable RAM.
const mm = boot_information.memory_map;
const regions = @as([*]const system.MemoryRegion, @ptrFromInt(system.physicalToVirtual(mm.regions)))[0..mm.len];
const regions = @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(mm.regions)))[0..mm.len];
var usable: u64 = 0;
for (regions) |r| {
if (r.kind == .usable) usable += r.pages;
@@ -179,7 +181,7 @@ fn smoke(boot_information: *const BootInformation) void {
const b = pmm.alloc();
check("alloc returns a frame", a != null);
check("alloc returns distinct frames", a != null and b != null and a.? != b.?);
check("frames are page-aligned", (a orelse 1) % system.page_size == 0);
check("frames are page-aligned", (a orelse 1) % abi.page_size == 0);
// Freeing restores the count.
const before = pmm.stats().free_frames;
@@ -189,7 +191,7 @@ fn smoke(boot_information: *const BootInformation) void {
// Paging is active on our own tables (the root is non-zero and page-aligned).
const root = architecture.activePageTable();
check("paging active (page-table root set)", root != 0 and root % system.page_size == 0);
check("paging active (page-table root set)", root != 0 and root % abi.page_size == 0);
result();
}
@@ -575,7 +577,7 @@ fn smpTest() void {
const tramp = architecture.trampolinePage();
check("trampoline frame reserved", tramp != 0);
if (tramp != 0) {
const bytes: [*]const u8 = @ptrFromInt(system.physicalToVirtual(tramp));
const bytes: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(tramp));
var zeroed = true;
for (0..4096) |b| {
if (bytes[b] != 0) zeroed = false;
@@ -759,7 +761,7 @@ fn userMemTest() void {
var mapped: usize = 0;
while (mapped < npages) : (mapped += 1) {
frames[mapped] = pmm.alloc() orelse break;
architecture.mapUserPageInto(aspace, arena + mapped * system.page_size, frames[mapped], true, false);
architecture.mapUserPageInto(aspace, arena + mapped * abi.page_size, frames[mapped], true, false);
}
check("granted three user pages", mapped == npages);
@@ -767,13 +769,13 @@ fn userMemTest() void {
var translate_ok = true;
var rw_ok = true;
for (0..npages) |i| {
const va = arena + i * system.page_size;
const va = arena + i * abi.page_size;
const physical = architecture.translate(aspace, va) orelse {
translate_ok = false;
continue;
};
if (physical != frames[i]) translate_ok = false;
const p: [*]u8 = @ptrFromInt(system.physicalToVirtual(physical));
const p: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(physical));
p[0] = 0xA5;
if (p[0] != 0xA5) rw_ok = false;
}
@@ -782,7 +784,7 @@ fn userMemTest() void {
// Release them the way munmap does, then tear down the address space.
for (0..npages) |i| {
const va = arena + i * system.page_size;
const va = arena + i * abi.page_size;
if (architecture.translate(aspace, va)) |physical| {
architecture.unmapUserPageInto(aspace, va);
pmm.free(physical);
@@ -879,7 +881,7 @@ fn processTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.write_count = 0;
process.write_from_user = false;
@@ -930,7 +932,7 @@ fn initTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.write_count = 0;
const spawned = if (process.spawnProcess(image, 4)) true else |err| blk: {
log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
@@ -964,7 +966,7 @@ fn initialRamdiskTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.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();
@@ -1008,7 +1010,7 @@ fn vfsTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.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();
@@ -1071,7 +1073,7 @@ fn hpetTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.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();
@@ -1115,14 +1117,14 @@ fn hpetRouteOk() bool {
/// The GSI discovery recorded for the HPET, from the same device table the driver saw.
fn hpetGsi() ?u32 {
var buffer: [16]system.DeviceDescriptor = undefined;
var buffer: [16]device_abi.DeviceDescriptor = undefined;
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(system.DeviceClass.timer)) continue;
if (d.parent != system.no_parent) continue; // the block, not a comparator child
if (d.class != @intFromEnum(device_abi.DeviceClass.timer)) continue;
if (d.parent != device_abi.no_parent) continue; // the block, not a comparator child
for (0..d.resource_count) |j| {
const r = d.resources[j];
if (r.kind == @intFromEnum(system.ResourceKind.irq)) return @intCast(r.start);
if (r.kind == @intFromEnum(device_abi.ResourceKind.irq)) return @intCast(r.start);
}
}
return null;
@@ -1148,7 +1150,7 @@ fn busTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.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();
@@ -1187,7 +1189,7 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const image = @as([*]const u8, @ptrFromInt(boot_handoff.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();
@@ -1221,7 +1223,7 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
/// trusted and doesn't obey containment: a PCI function's BAR is not inside its host
/// bridge's `bus_range`, because a bus-number range isn't an address window.
fn childrenContained() bool {
var buffer: [64]system.DeviceDescriptor = undefined;
var buffer: [64]device_abi.DeviceDescriptor = undefined;
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
const bus_id = hpetDeviceId() orelse return false;
@@ -1237,7 +1239,7 @@ fn childrenContained() bool {
for (0..p.resource_count) |j| {
const pr = p.resources[j];
if (pr.kind != r.kind) continue;
if (r.kind == @intFromEnum(system.ResourceKind.irq)) {
if (r.kind == @intFromEnum(device_abi.ResourceKind.irq)) {
if (pr.start == r.start) ok = true;
} else if (r.len != 0 and r.start >= pr.start and
r.start + r.len <= pr.start + pr.len) ok = true;
@@ -1250,13 +1252,13 @@ fn childrenContained() bool {
/// Device id of the HPET (the bus bus claims), from the same table drivers see.
fn hpetDeviceId() ?u64 {
var buffer: [64]system.DeviceDescriptor = undefined;
var buffer: [64]device_abi.DeviceDescriptor = undefined;
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(system.DeviceClass.timer)) continue;
if (d.parent != system.no_parent) continue; // a comparator child, not the block
if (d.class != @intFromEnum(device_abi.DeviceClass.timer)) continue;
if (d.parent != device_abi.no_parent) continue; // a comparator child, not the block
for (0..d.resource_count) |j| {
if (d.resources[j].kind == @intFromEnum(system.ResourceKind.memory)) return d.id;
if (d.resources[j].kind == @intFromEnum(device_abi.ResourceKind.memory)) return d.id;
}
}
return null;
@@ -1350,7 +1352,7 @@ fn ioPassTest() void {
return;
};
// Map it the way mmio_map does (device grant), then tear the space down.
architecture.mapUserDeviceInto(aspace, process.device_arena_base, frame, system.page_size);
architecture.mapUserDeviceInto(aspace, process.device_arena_base, frame, abi.page_size);
architecture.destroyAddressSpace(aspace);
// The page tables were reclaimed; the device-granted frame must not have been.