C2: migrate consumers off the runtime shim to direct concern-module imports

Every user binary and the two device-logic library modules (pci, usb) now
`@import` the concern modules directly instead of aliasing through `runtime`:

  runtime.ipc/process/time/service/input/block/display  -> @import("<module>")
  runtime.device / runtime.device_manager               -> @import("driver")
  runtime.fs                                             -> @import("file-system")
  runtime.Thread                                         -> @import("thread").Thread
  runtime.system.{write,writeRecord,klog*}              -> logging.*
  runtime.system.{sleep,timerOnce,wallClock,clock}     -> time.*
  runtime.system.{spawn*,kill,exit,yield,processes,...}-> process.*
  runtime.system.{mmap,munmap,PROT_*}                  -> memory.*
  runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.*

Each consumer keeps its own alias name (e.g. `const device = @import("driver")`),
so call sites are unchanged and there are no collisions with local `driver`
variables. build.zig now injects the concern modules into every user binary via
`default_imports`; pci/usb module import lists were updated to match.

The `runtime` and `system` shims remain for one more step (root.zig still uses
runtime); they are deleted in C5. Nothing but root.zig imports `runtime` now.

Verified: zig build, zig build test, and 17 QEMU cases (smoke, device-manager,
logger, fat-mount, fat-mutations, usb-storage, usb-hid, display-native,
virtio-gpu, input, thread-spawn, thread-mutex, process-kill, shared-memory,
driver-restart, acpi-ps2, pci-scan).
This commit is contained in:
Daniel Samson
2026-07-22 23:28:34 +01:00
parent dded46726b
commit 23bcd77c58
37 changed files with 783 additions and 692 deletions
+29 -23
View File
@@ -10,19 +10,25 @@
//! it.
const std = @import("std");
const runtime = @import("runtime");
const ipc = @import("ipc");
const process = @import("process");
const service = @import("service");
const time = @import("time");
const block = @import("block");
const file_system = @import("file-system");
const memory = @import("memory");
const logging = @import("logging");
const engine = @import("engine.zig");
const on_disk = @import("on-disk.zig");
const vfs_protocol = @import("vfs-protocol");
const dma = runtime.dma;
const mount_point = "/mnt/usb";
// The engine's BlockDevice, backed by the `.block` driver plus a DMA bounce
// buffer the driver reads/writes by physical address.
const IpcBlock = struct {
device: runtime.block.Device,
bounce: dma.Region, // engine.max_transfer_sectors * 512 bytes
device: block.Device,
bounce: memory.DmaRegion, // engine.max_transfer_sectors * 512 bytes
fn readBlocks(context: *anyopaque, lba: u64, count: u32, buffer: []u8) bool {
const self: *IpcBlock = @ptrCast(@alignCast(context));
@@ -89,17 +95,17 @@ fn fail(out: []u8) usize {
const mount_retry_ms = 500;
var mounted = false;
var service_endpoint: runtime.ipc.Handle = 0;
var service_endpoint: ipc.Handle = 0;
fn initialise(endpoint: runtime.ipc.Handle) bool {
fn initialise(endpoint: ipc.Handle) bool {
service_endpoint = endpoint;
_ = runtime.system.write("/system/services/fat: starting, waiting for a block device\n");
_ = logging.write("/system/services/fat: starting, waiting for a block device\n");
// With the router in the kernel, clients hold OUR node ids directly; sweep
// a dead client's open handles via the published exit events (the pattern
// the old userspace router used for its own table).
_ = runtime.process.subscribeExits(endpoint);
_ = process.subscribeExits(endpoint);
tryBringUp();
if (!mounted) _ = runtime.system.timerOnce(endpoint, mount_retry_ms);
if (!mounted) _ = time.timerOnce(endpoint, mount_retry_ms);
return true; // serve regardless: requests fail politely until storage mounts
}
@@ -107,12 +113,12 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
/// `mounted` on success; a failure leaves everything untouched for the next tick.
fn tryBringUp() void {
if (mounted) return;
const device = runtime.block.tryOpen() orelse return;
const device = block.tryOpen() orelse return;
const geometry = device.geometry() orelse {
_ = runtime.system.write("/system/services/fat: block geometry unavailable\n");
_ = logging.write("/system/services/fat: block geometry unavailable\n");
return;
};
const bounce = dma.alloc(engine.max_transfer_sectors * 512, dma.coherent) orelse return;
const bounce = memory.dmaAlloc(engine.max_transfer_sectors * 512, memory.dma_coherent) orelse return;
ipc_block = .{ .device = device, .bounce = bounce };
const block_device = engine.BlockDevice{
@@ -123,7 +129,7 @@ fn tryBringUp() void {
.writeBlocksFn = IpcBlock.writeBlocks,
};
filesystem = engine.FileSystem.mount(block_device) orelse {
_ = runtime.system.write("/system/services/fat: not a FAT filesystem\n");
_ = logging.write("/system/services/fat: not a FAT filesystem\n");
return;
};
std.log.info("mounted FAT ({s}, {d} clusters, partition lba {d})", .{ @tagName(filesystem.geometry.fat_type), filesystem.geometry.cluster_count, filesystem.base_lba });
@@ -131,30 +137,30 @@ fn tryBringUp() void {
// Mount ourselves into the kernel VFS at /mnt/usb — and serve /var from the
// volume's /var subtree, so FHS paths (the logger's /var/log) stay decoupled
// from which volume carries them.
if (runtime.fs.mount(mount_point, endpointForMount())) {
if (file_system.mount(mount_point, endpointForMount())) {
std.log.info("mounted {s}", .{mount_point});
} else {
_ = runtime.system.write("/system/services/fat: could not mount /mnt/usb\n");
_ = logging.write("/system/services/fat: could not mount /mnt/usb\n");
}
if (runtime.fs.mountRewritten("/var", endpointForMount(), "/var")) {
if (file_system.mountRewritten("/var", endpointForMount(), "/var")) {
std.log.info("mounted /var", .{});
} else {
_ = runtime.system.write("/system/services/fat: could not mount /var\n");
_ = logging.write("/system/services/fat: could not mount /var\n");
}
mounted = true;
}
fn endpointForMount() runtime.ipc.Handle {
fn endpointForMount() ipc.Handle {
return service_endpoint;
}
/// A subscribed process-exit event: release every open handle the dead client
/// held, so a crashed reader can't pin table slots (or, later, locks).
fn onNotification(badge: u64) void {
const got = runtime.ipc.Received{ .len = 0, .badge = badge, .cap = null };
const got = ipc.Received{ .len = 0, .badge = badge, .cap = null };
if (got.isTimer()) {
tryBringUp();
if (!mounted) _ = runtime.system.timerOnce(service_endpoint, mount_retry_ms);
if (!mounted) _ = time.timerOnce(service_endpoint, mount_retry_ms);
return;
}
if (!got.isChildExit()) return;
@@ -199,7 +205,7 @@ fn handleOpen(out: []u8, path: []const u8, flags: u32, sender: u32) usize {
return writeReply(out, .{ .status = 0, .node = index }, &.{});
}
fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?ipc.Handle) usize {
_ = capability;
if (!mounted) return fail(out); // storage not up (yet): fail politely, clients retry
if (message.len < vfs_protocol.request_size) return fail(out);
@@ -208,7 +214,7 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
// Stamp create/write with the current wall-clock time (mtime). Cheap, and it
// keeps the engine pure (it takes the time as data, not a syscall).
filesystem.current_time_epoch = runtime.system.wallClock();
filesystem.current_time_epoch = time.wallClock();
switch (request.operation) {
.open => return handleOpen(out, payload[0..@min(payload.len, request.len)], request.flags, sender),
@@ -287,7 +293,7 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
}
pub fn main() void {
runtime.service.run(vfs_protocol.message_maximum, .{
service.run(vfs_protocol.message_maximum, .{
.service = .fat,
.init = initialise,
.on_message = onMessage,