volume-manager: adopt every device, a filesystem per partition (S3)
Lift the one-device/one-volume cap. bringUpVolume now probes the whole partition table (allVolumes uncapped) and spawns a confined filesystem per volume; pollTick loops it to adopt every present, not-yet-adopted device each tick. The subtlety is adopt-once-and-keep: a device is recorded in the table the first time it is seen and kept until it leaves the tree, even when it carries no servable volume or its geometry cannot be read. Dropping an unservable device would make openAnyStorage hand back the same one every tick and starve the devices behind it; keeping it lets the scan advance past it. A genuine removal frees the slot; a re-insert (fresh device id) is probed anew. The boot image is a single bare-FAT volume, so the full suite is unchanged at 128/128.
This commit is contained in:
@@ -9,12 +9,11 @@
|
|||||||
//! supervises drivers.
|
//! supervises drivers.
|
||||||
//!
|
//!
|
||||||
//! The manager holds a table of adopted storage DEVICES and a table of the
|
//! The manager holds a table of adopted storage DEVICES and a table of the
|
||||||
//! VOLUMES on them: one filesystem process per volume, each confined to its
|
//! VOLUMES on them: it adopts every storage device the device-manager tree
|
||||||
//! partition's badge-scoped block range, each supervised with its own budget. A
|
//! carries, probes each one's whole partition table, and spawns one filesystem
|
||||||
//! device leaving the tree takes its volumes with it. (This S3 increment lays the
|
//! process per volume — each confined to its partition's badge-scoped block
|
||||||
//! tables in; multi-device adoption and per-partition spawn land in the next
|
//! range, each supervised with its own budget. A device leaving the tree takes
|
||||||
//! step — for now it adopts one device and its first volume, behavior-identical
|
//! its volumes with it.
|
||||||
//! to before.)
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const channel = @import("channel");
|
const channel = @import("channel");
|
||||||
@@ -129,7 +128,6 @@ var service_endpoint: ipc.Handle = 0;
|
|||||||
var manager_handle: ?ipc.Handle = null;
|
var manager_handle: ?ipc.Handle = null;
|
||||||
var bounce: memory.DmaRegion = undefined;
|
var bounce: memory.DmaRegion = undefined;
|
||||||
var bounce_ready = false;
|
var bounce_ready = false;
|
||||||
var logged_no_volume = false;
|
|
||||||
/// How often the poll checks device presence and fires due restarts. Fast enough
|
/// How often the poll checks device presence and fires due restarts. Fast enough
|
||||||
/// that an unplug unmounts promptly; the poll is a bare device-manager enumerate,
|
/// that an unplug unmounts promptly; the poll is a bare device-manager enumerate,
|
||||||
/// no channel work, so it is cheap to run continuously.
|
/// no channel work, so it is cheap to run continuously.
|
||||||
@@ -152,10 +150,6 @@ fn claimDevice() ?*StorageDevice {
|
|||||||
for (&devices) |*d| if (!d.used) return d;
|
for (&devices) |*d| if (!d.used) return d;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
fn anyDeviceUsed() bool {
|
|
||||||
for (&devices) |*d| if (d.used) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
fn volumeById(id: u64) ?*Volume {
|
fn volumeById(id: u64) ?*Volume {
|
||||||
for (&volumes) |*v| if (v.used and v.id == id) return v;
|
for (&volumes) |*v| if (v.used and v.id == id) return v;
|
||||||
return null;
|
return null;
|
||||||
@@ -288,36 +282,44 @@ fn composeMountPrefix(slot: usize, identity: partition.Identity) []const u8 {
|
|||||||
(std.fmt.bufPrint(&mount_prefix_bufs[slot], "/volumes/{s}", .{id}) catch "/volumes/unknown");
|
(std.fmt.bufPrint(&mount_prefix_bufs[slot], "/volumes/{s}", .{id}) catch "/volumes/unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A storage device appeared: adopt its channel, probe its partition table, and
|
/// Adopt the next present, not-yet-adopted storage device: take its channel,
|
||||||
/// spawn a filesystem per volume it carries. On any failure the channel is
|
/// probe its whole partition table, and spawn a filesystem per volume it carries.
|
||||||
/// dropped (so a present-but-unreadable device does not leak a handle every poll)
|
/// Returns true when it consumed a device (so the caller can loop to adopt every
|
||||||
/// and the device stays unadopted — the next poll retries. This increment probes
|
/// present device in one tick), false when none remain or the device table is full.
|
||||||
/// only the first volume (behavior-identical to before); the next step lifts the
|
///
|
||||||
/// cap.
|
/// A device is adopted exactly once and kept until it leaves the tree — even when
|
||||||
fn bringUpVolume() void {
|
/// it carries no volume we can serve, or its geometry cannot be read. Keeping the
|
||||||
|
/// empty/unreadable device adopted (rather than dropping and re-probing) is what
|
||||||
|
/// lets openAnyStorage advance PAST it to the devices behind it; dropping it would
|
||||||
|
/// make openAnyStorage hand back the same unservable device every tick and starve
|
||||||
|
/// the rest. A genuine removal frees the slot (removeDevice); a re-insert gets a
|
||||||
|
/// fresh device id and is probed anew.
|
||||||
|
fn bringUpVolume() bool {
|
||||||
if (!bounce_ready) {
|
if (!bounce_ready) {
|
||||||
bounce = memory.dmaAlloc(512, memory.dma_coherent | memory.dma_shareable) orelse return;
|
bounce = memory.dmaAlloc(512, memory.dma_coherent | memory.dma_shareable) orelse return false;
|
||||||
bounce_ready = true;
|
bounce_ready = true;
|
||||||
}
|
}
|
||||||
const opened = openAnyStorage() orelse return;
|
const opened = openAnyStorage() orelse return false;
|
||||||
const dev = claimDevice() orelse {
|
const dev = claimDevice() orelse {
|
||||||
_ = logging.write("volume-manager: device table full; a storage device is left unadopted\n");
|
_ = logging.write("volume-manager: device table full; a storage device is left unadopted\n");
|
||||||
_ = ipc.close(opened.device.endpoint);
|
_ = ipc.close(opened.device.endpoint);
|
||||||
return;
|
return false;
|
||||||
};
|
};
|
||||||
dev.* = .{ .used = true, .device_id = opened.device_id, .channel = opened.device };
|
dev.* = .{ .used = true, .device_id = opened.device_id, .channel = opened.device };
|
||||||
const device = opened.device;
|
const device = opened.device;
|
||||||
// Attach the read buffer to THIS device (a no-op without an enforcing IOMMU).
|
// Attach the read buffer to THIS device (a no-op without an enforcing IOMMU).
|
||||||
// The handle is kept, not closed, so it can be re-attached after a replug.
|
// The handle is kept, not closed, so it can be re-attached after a replug. A
|
||||||
|
// failed attach or geometry read leaves the device adopted but empty — we just
|
||||||
|
// cannot read it, and the slot still watches it for removal.
|
||||||
if (bounce.handle) |handle| {
|
if (bounce.handle) |handle| {
|
||||||
if (!device.attach(handle)) {
|
if (!device.attach(handle)) {
|
||||||
dropDevice(dev);
|
_ = logging.write("volume-manager: could not attach the read buffer to a storage device; no volume served\n");
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const geometry = device.geometry() orelse {
|
const geometry = device.geometry() orelse {
|
||||||
dropDevice(dev);
|
_ = logging.write("volume-manager: could not read a storage device's geometry; no volume served\n");
|
||||||
return;
|
return true;
|
||||||
};
|
};
|
||||||
const ProbeReader = struct {
|
const ProbeReader = struct {
|
||||||
device: block.Device,
|
device: block.Device,
|
||||||
@@ -332,17 +334,11 @@ fn bringUpVolume() void {
|
|||||||
var probe = ProbeReader{ .device = device };
|
var probe = ProbeReader{ .device = device };
|
||||||
const reader = partition.SectorReader{ .context = &probe, .readFn = ProbeReader.readSector };
|
const reader = partition.SectorReader{ .context = &probe, .readFn = ProbeReader.readSector };
|
||||||
var found: [maximum_volumes]partition.Volume = undefined;
|
var found: [maximum_volumes]partition.Volume = undefined;
|
||||||
const n = partition.allVolumes(reader, geometry.block_count, found[0..1]); // cap 1 this step
|
const n = partition.allVolumes(reader, geometry.block_count, found[0..]);
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
if (!logged_no_volume) {
|
std.log.info("device {d} present but carries no recognizable volume", .{dev.device_id});
|
||||||
_ = logging.write("volume-manager: storage present but no recognizable volume\n");
|
return true;
|
||||||
logged_no_volume = true;
|
|
||||||
}
|
|
||||||
dropDevice(dev);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
logged_no_volume = false;
|
|
||||||
var spawned = false;
|
|
||||||
for (found[0..n]) |fv| {
|
for (found[0..n]) |fv| {
|
||||||
// Pick the service binary from the volume's content signature. A signature
|
// Pick the service binary from the volume's content signature. A signature
|
||||||
// no filesystems.csv row serves goes unserved (logged), like an unbound
|
// no filesystems.csv row serves goes unserved (logged), like an unbound
|
||||||
@@ -367,10 +363,8 @@ fn bringUpVolume() void {
|
|||||||
};
|
};
|
||||||
next_volume_id += 1;
|
next_volume_id += 1;
|
||||||
spawnFilesystem(&volumes[slot]);
|
spawnFilesystem(&volumes[slot]);
|
||||||
spawned = true;
|
|
||||||
}
|
}
|
||||||
// The device carried nothing we could serve — drop it so a re-poll retries.
|
return true;
|
||||||
if (!spawned) dropDevice(dev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close a device's channel and free its slot. No volumes are touched (the caller
|
/// Close a device's channel and free its slot. No volumes are touched (the caller
|
||||||
@@ -415,7 +409,12 @@ fn pollTick() void {
|
|||||||
spawnFilesystem(v);
|
spawnFilesystem(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!anyDeviceUsed()) bringUpVolume();
|
// Adopt every present, not-yet-adopted storage device. Each call consumes at
|
||||||
|
// most one device (openAnyStorage skips the adopted), so the loop terminates
|
||||||
|
// once none remain; the maximum_devices guard is insurance against a logic
|
||||||
|
// slip, never the normal exit.
|
||||||
|
var adopted: usize = 0;
|
||||||
|
while (adopted < maximum_devices and bringUpVolume()) : (adopted += 1) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A filesystem announces itself for the volume it was spawned to serve. Reply
|
/// A filesystem announces itself for the volume it was spawned to serve. Reply
|
||||||
|
|||||||
Reference in New Issue
Block a user