diff --git a/system/services/volume-manager/volume-manager.zig b/system/services/volume-manager/volume-manager.zig index 6917ac3..e1ca1fb 100644 --- a/system/services/volume-manager/volume-manager.zig +++ b/system/services/volume-manager/volume-manager.zig @@ -9,12 +9,11 @@ //! supervises drivers. //! //! 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 -//! partition's badge-scoped block range, each supervised with its own budget. A -//! device leaving the tree takes its volumes with it. (This S3 increment lays the -//! tables in; multi-device adoption and per-partition spawn land in the next -//! step — for now it adopts one device and its first volume, behavior-identical -//! to before.) +//! VOLUMES on them: it adopts every storage device the device-manager tree +//! carries, probes each one's whole partition table, and spawns one filesystem +//! process per volume — each confined to its partition's badge-scoped block +//! range, each supervised with its own budget. A device leaving the tree takes +//! its volumes with it. const std = @import("std"); const channel = @import("channel"); @@ -129,7 +128,6 @@ var service_endpoint: ipc.Handle = 0; var manager_handle: ?ipc.Handle = null; var bounce: memory.DmaRegion = undefined; var bounce_ready = false; -var logged_no_volume = false; /// 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, /// 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; return null; } -fn anyDeviceUsed() bool { - for (&devices) |*d| if (d.used) return true; - return false; -} fn volumeById(id: u64) ?*Volume { for (&volumes) |*v| if (v.used and v.id == id) return v; 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"); } -/// A storage device appeared: adopt its channel, probe its partition table, and -/// spawn a filesystem per volume it carries. On any failure the channel is -/// dropped (so a present-but-unreadable device does not leak a handle every poll) -/// and the device stays unadopted — the next poll retries. This increment probes -/// only the first volume (behavior-identical to before); the next step lifts the -/// cap. -fn bringUpVolume() void { +/// Adopt the next present, not-yet-adopted storage device: take its channel, +/// probe its whole partition table, and spawn a filesystem per volume it carries. +/// Returns true when it consumed a device (so the caller can loop to adopt every +/// present device in one tick), false when none remain or the device table is full. +/// +/// A device is adopted exactly once and kept until it leaves the tree — even when +/// 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) { - 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; } - const opened = openAnyStorage() orelse return; + const opened = openAnyStorage() orelse return false; const dev = claimDevice() orelse { _ = logging.write("volume-manager: device table full; a storage device is left unadopted\n"); _ = ipc.close(opened.device.endpoint); - return; + return false; }; dev.* = .{ .used = true, .device_id = opened.device_id, .channel = opened.device }; const device = opened.device; // 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 (!device.attach(handle)) { - dropDevice(dev); - return; + _ = logging.write("volume-manager: could not attach the read buffer to a storage device; no volume served\n"); + return true; } } const geometry = device.geometry() orelse { - dropDevice(dev); - return; + _ = logging.write("volume-manager: could not read a storage device's geometry; no volume served\n"); + return true; }; const ProbeReader = struct { device: block.Device, @@ -332,17 +334,11 @@ fn bringUpVolume() void { var probe = ProbeReader{ .device = device }; const reader = partition.SectorReader{ .context = &probe, .readFn = ProbeReader.readSector }; 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 (!logged_no_volume) { - _ = logging.write("volume-manager: storage present but no recognizable volume\n"); - logged_no_volume = true; - } - dropDevice(dev); - return; + std.log.info("device {d} present but carries no recognizable volume", .{dev.device_id}); + return true; } - logged_no_volume = false; - var spawned = false; for (found[0..n]) |fv| { // Pick the service binary from the volume's content signature. A signature // no filesystems.csv row serves goes unserved (logged), like an unbound @@ -367,10 +363,8 @@ fn bringUpVolume() void { }; next_volume_id += 1; spawnFilesystem(&volumes[slot]); - spawned = true; } - // The device carried nothing we could serve — drop it so a re-poll retries. - if (!spawned) dropDevice(dev); + return true; } /// Close a device's channel and free its slot. No volumes are touched (the caller @@ -415,7 +409,12 @@ fn pollTick() void { 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