volume-manager: try every mass-storage entry, watch the one we opened
The full suite caught a V4 regression: under AMD-Vi the device-manager tree carries more than one mass-storage-identity entry (a phantom no driver is bound to, which answers a consumer hello with NO channel). V4 split presence from acquisition and picked the FIRST identity match blindly, so it kept helloing the phantom (device 27) and never reached the real storage (device 31). V3's inline loop had skipped no-channel entries with `orelse continue`; the split lost that. Restore it: openAnyStorage tries each matching entry and takes the first whose channel opens, recording its device id. Removal detection then watches THAT specific device id leave the tree (isDevicePresent), not "any mass-storage" — so a phantom that never leaves cannot mask a real removal. Both are bare enumerates; the hello only happens while bringing a volume up. Green: amd-iommu-usb-storage, fat-mount, volume-removal.
This commit is contained in:
@@ -81,16 +81,24 @@ var fs_failed = false;
|
|||||||
var restart_pending = false;
|
var restart_pending = false;
|
||||||
var restart_due_ns: u64 = 0;
|
var restart_due_ns: u64 = 0;
|
||||||
|
|
||||||
/// The device-manager id of the mass-storage provider currently in the tree, or
|
fn deviceManager() ?ipc.Handle {
|
||||||
/// null if none. Presence only — no consumer-hello, so calling it every poll
|
if (manager_handle) |h| return h;
|
||||||
/// 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;
|
const handle = channel.openEndpoint("device-manager") orelse return null;
|
||||||
manager_handle = handle;
|
manager_handle = handle;
|
||||||
break :opened 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;
|
const Entry = device_manager_protocol.ChildEntry;
|
||||||
var start: u64 = 0;
|
var start: u64 = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -108,19 +116,38 @@ fn findStorageDevice() ?u64 {
|
|||||||
const entry = std.mem.bytesToValue(Entry, tail[index * @sizeOf(Entry) ..][0..@sizeOf(Entry)]);
|
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.device_id == device_manager_protocol.no_device) continue;
|
||||||
if ((entry.identity >> 16) & 0xff != 0x08 or (entry.identity >> 8) & 0xff != 0x06) 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;
|
start += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumer-hello the device manager for `device_id`'s block channel. Called
|
/// Whether `device_id` is still in the device-manager tree — a bare enumerate,
|
||||||
/// once per insertion (not per poll), so no per-poll handle churn.
|
/// no consumer-hello, so it is cheap to call every poll. This is how removal is
|
||||||
fn openStorage(device_id: u64) ?block.Device {
|
/// detected: the specific device the mounted volume sits on disappears.
|
||||||
const manager = manager_handle orelse return null;
|
fn isDevicePresent(device_id: u64) bool {
|
||||||
const exchanged = driver.helloOn(manager, .consumer, device_id, null, true) orelse return null;
|
const manager = deviceManager() orelse return false;
|
||||||
const provider = exchanged.channel orelse return null;
|
const Entry = device_manager_protocol.ChildEntry;
|
||||||
return .{ .endpoint = provider };
|
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
|
/// 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`
|
/// 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
|
/// stays null — the next poll retries. A fresh medium gets a fresh supervision
|
||||||
/// budget.
|
/// budget.
|
||||||
fn bringUpVolume(device_id: u64) void {
|
fn bringUpVolume() void {
|
||||||
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;
|
||||||
bounce_ready = true;
|
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).
|
// 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
|
// The handle is kept, not closed, so it can be re-attached to the next
|
||||||
// device after a replug.
|
// device after a replug.
|
||||||
@@ -193,7 +221,7 @@ fn bringUpVolume(device_id: u64) void {
|
|||||||
fs_restarts = 0;
|
fs_restarts = 0;
|
||||||
fs_failed = false;
|
fs_failed = false;
|
||||||
restart_pending = 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.?);
|
spawnFilesystem(&volume.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,10 +248,12 @@ fn pollTick() void {
|
|||||||
if (volume) |*v| spawnFilesystem(v);
|
if (volume) |*v| spawnFilesystem(v);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (findStorageDevice()) |device_id| {
|
if (volume) |v| {
|
||||||
if (volume == null) bringUpVolume(device_id);
|
// Serving: watch for the specific device leaving (a pulled stick).
|
||||||
|
if (!isDevicePresent(v.storage_device_id)) removeVolume();
|
||||||
} else {
|
} else {
|
||||||
if (volume != null) removeVolume();
|
// Idle: try to bring a present storage device up.
|
||||||
|
bringUpVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user