volume-manager: discovery and probe — V3a

The storage layer gains its policy home (storage-architecture.md): a new
system/services/volume-manager, spawned by init, that acquires the mass-
storage block channel through the device manager (the same lineage a
filesystem uses), reads block 0, and parses the first volume out of it. The
partition-table walk that lived in the FAT engine moves here, above the
driver where it belongs (partition.zig, host-tested: MBR entry, bare-FAT,
no-signature). Identity is the MBR disk signature + partition index — the
weak rung of the ladder; GPT GUID and FAT serial refine identityOf without
changing shape.

This increment is discovery + probe + log only, additive: the FAT service
still acquires its own volume, so nothing changes for it. Confining each
filesystem to its partition and spawning one per volume (the flip) lands
next, keeping fat working throughout.

Grants + wiring: init.csv spawns it after the device manager; protocol.csv
grants bind volume-manager + open device-manager. Verified: volume-probe
asserts the parse (bare-FAT volume at lba 0), neutral 10/10 across storage,
restart, display, logging, confinement — the volume manager now runs in
every boot and disturbs nothing.
This commit is contained in:
Daniel Samson
2026-08-09 18:10:43 +01:00
parent bc67771bfd
commit d56b1b81c0
9 changed files with 293 additions and 0 deletions
@@ -0,0 +1,135 @@
//! system/services/volume-manager — the storage layer's policy home
//! (docs/file-system-development/storage-architecture.md). It sits beside the
//! device manager: the device manager owns the DEVICE tree; this owns the VOLUME
//! layer. It hears about storage providers, probes their partition tables and
//! content identity, and — in later increments — confines each filesystem to its
//! partition and spawns one per volume, answering that filesystem's startup
//! hello with the (range-confined) block channel.
//!
//! This increment (V3a) is discovery and probe only: find the mass-storage
//! provider, read block 0, parse the first volume out of it, and log what it
//! found — additive, with the FAT service still acquiring its own volume. The
//! delegation (confine + spawn + hand over the channel) and the mount map land
//! next, keeping the FAT service working throughout.
const std = @import("std");
const channel = @import("channel");
const device_manager_protocol = @import("device-manager-protocol");
const driver = @import("driver");
const ipc = @import("ipc");
const block = @import("block");
const memory = @import("memory");
const logging = @import("logging");
const process = @import("process");
const service = @import("service");
const time = @import("time");
const envelope = @import("envelope");
const partition = @import("partition.zig");
var service_endpoint: ipc.Handle = 0;
var manager_handle: ?ipc.Handle = null;
var bounce: memory.DmaRegion = undefined;
var bounce_ready = false;
var probed = false;
const probe_retry_ms = 500;
/// The first mass-storage provider's block channel, via the device manager's
/// tree — the same lineage acquisition a filesystem makes (block is not a
/// registry name). One enumerate sweep; null until the chain is up.
fn acquireStorage() ?block.Device {
const manager = manager_handle orelse opened: {
const handle = channel.openEndpoint("device-manager") orelse return null;
manager_handle = handle;
break :opened handle;
};
const Entry = device_manager_protocol.ChildEntry;
var start: u64 = 0;
while (true) {
const enumerate = envelope.Header{ .operation = envelope.operation_enumerate, .target = start };
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
const length = ipc.call(manager, std.mem.asBytes(&enumerate), &reply) catch return null;
const status = envelope.statusOf(reply[0..length]) orelse return null;
if (status.status != 0) return null;
const carried = @min(@as(usize, status.len), length -| envelope.prefix_size);
const tail = reply[envelope.prefix_size..][0..carried];
const count = tail.len / @sizeOf(Entry);
if (count == 0) return null;
var index: usize = 0;
while (index < count) : (index += 1) {
const entry = std.mem.bytesToValue(Entry, tail[index * @sizeOf(Entry) ..][0..@sizeOf(Entry)]);
if (entry.device_id == device_manager_protocol.no_device) continue;
if ((entry.identity >> 16) & 0xff != 0x08 or (entry.identity >> 8) & 0xff != 0x06) continue;
const exchanged = driver.helloOn(manager, .consumer, entry.device_id, null, true) orelse return null;
const provider = exchanged.channel orelse continue;
return .{ .endpoint = provider };
}
start += count;
}
}
/// One probe attempt: acquire the storage channel, read block 0, and parse the
/// first volume. Sets `probed` and logs on success; a failure leaves everything
/// for the next tick.
fn tryProbe() void {
if (probed) return;
if (!bounce_ready) {
bounce = memory.dmaAlloc(512, memory.dma_coherent | memory.dma_shareable) orelse return;
bounce_ready = true;
}
const device = acquireStorage() orelse return;
// Attach the read buffer to the controller (a no-op success without an
// enforcing IOMMU). The volume manager is unconfined — it reads the whole
// device to probe — so no range is defined here.
if (bounce.handle) |handle| {
if (!device.attach(handle)) return;
_ = ipc.close(handle);
bounce.handle = null; // attached once; do not re-forward on a retry
}
const geometry = device.geometry() orelse return;
if (!device.read(0, 1, bounce.physical)) return;
const sector: [*]const u8 = @ptrFromInt(bounce.virtual);
const volume = partition.firstVolume(sector[0..512], geometry.block_count) orelse {
_ = logging.write("volume-manager: no volume found on the storage device\n");
probed = true; // a device with no recognizable volume is not retried
return;
};
std.log.info("volume 0x{x} at lba {d}, {d} blocks", .{ volume.identity, volume.base_lba, volume.block_count });
probed = true;
}
fn initialise(endpoint: ipc.Handle) bool {
service_endpoint = endpoint;
_ = logging.write("volume-manager: starting, waiting for a storage device\n");
tryProbe();
if (!probed) _ = time.timerOnce(endpoint, probe_retry_ms);
return true;
}
fn onNotification(badge: u64) void {
const got = ipc.Received{ .len = 0, .badge = badge, .cap = null };
if (got.isTimer()) {
tryProbe();
if (!probed) _ = time.timerOnce(service_endpoint, probe_retry_ms);
}
}
/// No clients yet: a filesystem hello lands here in the next increment. Until
/// then, refuse politely.
fn onMessage(message: []const u8, out: []u8, sender: u32, arrived: *ipc.Arrival) usize {
_ = message;
_ = sender;
_ = arrived;
const status = envelope.Status{ .status = -envelope.ENOSYS, .len = 0 };
@memcpy(out[0..envelope.prefix_size], std.mem.asBytes(&status));
return envelope.prefix_size;
}
pub fn main(init: process.Init) void {
_ = init;
service.run(device_manager_protocol.message_maximum, .{
.service = "volume-manager",
.init = initialise,
.on_message = onMessage,
.on_notification = onNotification,
});
}