diff --git a/system/services/volume-manager/volume-manager.zig b/system/services/volume-manager/volume-manager.zig index 587799d..6d72f4e 100644 --- a/system/services/volume-manager/volume-manager.zig +++ b/system/services/volume-manager/volume-manager.zig @@ -81,16 +81,24 @@ var fs_failed = false; var restart_pending = false; var restart_due_ns: u64 = 0; -/// The device-manager id of the mass-storage provider currently in the tree, or -/// null if none. Presence only — no consumer-hello, so calling it every poll -/// leaks nothing. This is how removal (the id disappears) and insertion (it -/// appears) are detected. -fn findStorageDevice() ?u64 { - const manager = manager_handle orelse opened: { - const handle = channel.openEndpoint("device-manager") orelse return null; - manager_handle = handle; - break :opened handle; - }; +fn deviceManager() ?ipc.Handle { + if (manager_handle) |h| return h; + const handle = channel.openEndpoint("device-manager") orelse return null; + manager_handle = handle; + return handle; +} + +const OpenedStorage = struct { device_id: u64, device: block.Device }; + +/// The first mass-storage provider whose block channel actually opens, with its +/// device id. A device-manager tree can carry more than one entry of the +/// mass-storage identity — a phantom that no driver is bound to answers a +/// consumer hello with NO channel — so this tries each and takes the first that +/// yields a channel, exactly as a filesystem's own acquisition loop does. +/// Called only when there is no volume (an insertion), so the hellos it makes +/// are not per-poll churn. +fn openAnyStorage() ?OpenedStorage { + const manager = deviceManager() orelse return null; const Entry = device_manager_protocol.ChildEntry; var start: u64 = 0; while (true) { @@ -108,19 +116,38 @@ fn findStorageDevice() ?u64 { 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; - return entry.device_id; + const exchanged = driver.helloOn(manager, .consumer, entry.device_id, null, true) orelse continue; + const provider = exchanged.channel orelse continue; // a phantom / not-yet-bound entry + return .{ .device_id = entry.device_id, .device = .{ .endpoint = provider } }; } start += count; } } -/// Consumer-hello the device manager for `device_id`'s block channel. Called -/// once per insertion (not per poll), so no per-poll handle churn. -fn openStorage(device_id: u64) ?block.Device { - const manager = manager_handle orelse return null; - const exchanged = driver.helloOn(manager, .consumer, device_id, null, true) orelse return null; - const provider = exchanged.channel orelse return null; - return .{ .endpoint = provider }; +/// Whether `device_id` is still in the device-manager tree — a bare enumerate, +/// no consumer-hello, so it is cheap to call every poll. This is how removal is +/// detected: the specific device the mounted volume sits on disappears. +fn isDevicePresent(device_id: u64) bool { + const manager = deviceManager() orelse return false; + 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 false; + const status = envelope.statusOf(reply[0..length]) orelse return false; + if (status.status != 0) return false; + 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 false; + 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_id) return true; + } + start += count; + } } /// Spawn the filesystem for `v`, confine it to the volume's range, and record @@ -157,12 +184,13 @@ fn armRestart() void { /// present-but-unreadable device does not leak a handle every poll) and `volume` /// stays null — the next poll retries. A fresh medium gets a fresh supervision /// budget. -fn bringUpVolume(device_id: u64) void { +fn bringUpVolume() void { if (!bounce_ready) { bounce = memory.dmaAlloc(512, memory.dma_coherent | memory.dma_shareable) orelse return; bounce_ready = true; } - const device = openStorage(device_id) orelse return; + const opened = openAnyStorage() orelse return; + 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 to the next // device after a replug. @@ -193,7 +221,7 @@ fn bringUpVolume(device_id: u64) void { fs_restarts = 0; fs_failed = false; restart_pending = false; - volume = .{ .storage = device, .storage_device_id = device_id, .base_lba = found.base_lba, .block_count = found.block_count, .identity = found.identity, .id = volume_id }; + volume = .{ .storage = device, .storage_device_id = opened.device_id, .base_lba = found.base_lba, .block_count = found.block_count, .identity = found.identity, .id = volume_id }; spawnFilesystem(&volume.?); } @@ -220,10 +248,12 @@ fn pollTick() void { if (volume) |*v| spawnFilesystem(v); return; } - if (findStorageDevice()) |device_id| { - if (volume == null) bringUpVolume(device_id); + if (volume) |v| { + // Serving: watch for the specific device leaving (a pulled stick). + if (!isDevicePresent(v.storage_device_id)) removeVolume(); } else { - if (volume != null) removeVolume(); + // Idle: try to bring a present storage device up. + bringUpVolume(); } }