Files
danos/library/protocol/build.zig
T
Daniel Samson 301bdcaf5b volume-manager: the flip — fat is spawned, confined, and handed its channel (V3b)
The load-bearing step. The FAT service stops acquiring its own volume: the
volume manager spawns it (per volume), defines its partition range on the
storage driver BEFORE it runs, and answers its startup hello with the
range-confined block channel over a new volume-manager protocol. fat never
finds its storage by name and never sees the whole device — establishment
by lineage, one layer up from the driver tree.

- New library/protocol/volume-manager: one verb, hello(volume-id) -> the
  block channel as the reply capability (the P0 reply-cap path).
- The volume manager becomes the confinement CONTROLLER: it defines the first
  range on usb-storage, so no other party can confine a filesystem. It
  supervises the filesystems it spawns and respawns one on death (the reap-
  and-rebuild the device manager proved, one layer up).
- fat: drops acquireVolume(device-manager); hellos the volume manager for its
  channel; reads its volume id from argv[1]. main takes process.Init now.
- init.csv no longer spawns fat (the volume manager does); protocol.csv
  rewires fat to be supervised by the volume manager (bind vfs, open
  volume-manager) and drops fat open device-manager.
- The block-range fixture boots registry + device-manager only (not the full
  tree), so the volume manager is absent and the fixture stays the sole
  confinement definer — otherwise the volume manager would take the
  controller first and refuse it.

Verified end to end (VM probes -> spawns fat -> confines it -> hands over the
channel -> fat mounts) and neutral: 18/18 across the fat family, logging,
shutdown, both IOMMU variants, usb restart, vfs, conformance, confinement.
2026-08-09 18:28:44 +01:00

76 lines
4.1 KiB
Zig

//! The "protocol" library domain: the wire protocols — each service's public
//! interface, exposed as its own module (docs/driver-model.md). Both sides of
//! every conversation depend on the contract by name; neither reaches into the
//! other's files. Pure flat wire types: no protocol module imports anything.
//!
//! One module here is not a protocol but the shape the others are written in,
//! and therefore the one module every other one imports:
//!
//! envelope : the packet prefix + comptime Define (docs/os-development/protocol-namespace.md)
//!
//! vfs-protocol : the VFS server <-> the file layer (unistd/stdio)
//! input-protocol : the input fan-out service <-> sources + subscribers
//! block-protocol : a filesystem <-> a block driver (usb-storage)
//! usb-transfer-protocol : a USB class driver <-> the xHCI bus driver
//! device-manager-protocol : the device manager <-> drivers + discovery
//! display-protocol : the compositor's client-facing surface
//! scanout-protocol : the compositor -> a native scanout driver (docs/display-v2.md)
//! power-protocol : system power's domain-named surface (docs/power.md)
const std = @import("std");
pub fn build(b: *std.Build) void {
// Not a protocol, hence not `-protocol`: the envelope is what a protocol is
// defined *through*, so it is built first and handed to every protocol
// below as their one import.
const envelope = b.addModule("envelope", .{ .root_source_file = b.path("envelope/envelope.zig") });
for ([_]struct { name: []const u8, root: []const u8 }{
.{ .name = "vfs-protocol", .root = "vfs/vfs-protocol.zig" },
.{ .name = "input-protocol", .root = "input/input-protocol.zig" },
.{ .name = "block-protocol", .root = "block/block-protocol.zig" },
.{ .name = "usb-transfer-protocol", .root = "usb-transfer/usb-transfer-protocol.zig" },
.{ .name = "device-manager-protocol", .root = "device-manager/device-manager-protocol.zig" },
.{ .name = "volume-manager-protocol", .root = "volume-manager/volume-manager-protocol.zig" },
.{ .name = "display-protocol", .root = "display/display-protocol.zig" },
.{ .name = "scanout-protocol", .root = "scanout/scanout-protocol.zig" },
.{ .name = "power-protocol", .root = "power/power-protocol.zig" },
}) |protocol| {
_ = b.addModule(protocol.name, .{
.root_source_file = b.path(protocol.root),
.imports = &.{.{ .name = "envelope", .module = envelope }},
});
}
// Standalone `zig build test` for this domain alone; the root build keeps
// its aggregate test step.
const test_step = b.step("test", "Run the protocol unit tests");
// The envelope tests itself with no import of its own — everything else
// imports it, so it is built separately rather than importing itself.
const envelope_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("envelope/envelope.zig"), // framing round trips, verb numbering, dispatch, the floors
.target = b.resolveTargetQuery(.{}),
}),
});
test_step.dependOn(&b.addRunArtifact(envelope_tests).step);
for ([_][]const u8{
"vfs/vfs-protocol.zig", // NodeKind / DirectoryEntry sizes + op values
"input/input-protocol.zig", // event numbering + the push-floor budget
"display/display-protocol.zig", // pack(): native pixel encoding per format
"device-manager/device-manager-protocol.zig", // the dual-use report, exactly on the push floor
"power/power-protocol.zig", // the event kind as the packet's verb
"usb-transfer/usb-transfer-protocol.zig", // the tail-carried control stage + the trimmed report
}) |root| {
const protocol_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path(root),
.target = b.resolveTargetQuery(.{}),
.imports = &.{.{ .name = "envelope", .module = envelope }},
}),
});
test_step.dependOn(&b.addRunArtifact(protocol_tests).step);
}
}