From d562e9df2bc5e69bba7298a9439e2adca5c18faf Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 15:00:47 +0200 Subject: rsnix: runtime device detection — rescan for hotplugged/removed disks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1: make the boot discovery pipeline re-runnable and diff it against live hardware state. - ahci: store ABAR/NP/port bitmap; poll_dirty() (cheap PxSSTS.DET check per port) and rescan() which starts newly-present ports and tombstones the slots of removed ones. Slots never shift, so disk indices stay valid. select() now rejects tombstoned slots. - fat: Volume gets a mounted flag; rescan() unmounts volumes whose disk left and mounts every disk without a mounted volume (re-trying failed boot mounts). UID dedup ignores tombstones so a re-plugged disk keeps its /disk/ name. - fs: init's mount loop factored into rescan_disks(), which lazily creates /disk, ensures a mount node per mounted volume and tombstones nodes of unmounted ones. - shell: 'rescan' command + automatic deep rescan before each prompt when ahci::poll_dirty() sees a port-presence change. Rescan events flow through serial_println so they land in klog too. Removal (PxSSTS) is a real-hardware path: QEMU q35 rejects disk hotplug on the ich9-ahci IDE buses, and PCIe device_del is a guest-cooperative protocol this kernel does not implement. --- rsnix/src/fat.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) (limited to 'rsnix/src/fat.rs') diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs index 335a160..ef75aa9 100644 --- a/rsnix/src/fat.rs +++ b/rsnix/src/fat.rs @@ -36,6 +36,7 @@ pub struct Entry { struct Volume { disk: usize, // AHCI disk index this volume was mounted from uid: String, // unique mount name: "LABEL-XXXXXXXX" + mounted: bool, // still backed by a present disk (false = tombstoned) sectors_per_cluster: u8, reserved: u16, fat_size: u32, // sectors per FAT @@ -65,6 +66,69 @@ pub fn volume_count() -> usize { vols().len() } +/// Is volume `vol` currently mounted (its disk present and FAT cached)? +pub fn is_mounted(vol: usize) -> bool { + vols().get(vol).map(|v| v.mounted).unwrap_or(false) +} + +/// Re-scan after a disk arrival/removal: unmount volumes whose disk left, +/// mount every disk that has no mounted volume (which also re-tries +/// mounts that failed at boot). Volume slots are tombstoned, never +/// removed, so VFS disk handles keep their indices. Returns +/// (mounted, unmounted). +pub fn rescan() -> (usize, usize) { + let disks = crate::ahci::disk_count(); + let mut mounted = 0usize; + let mut unmounted = 0usize; + // Unmount volumes whose disk is gone. + for i in 0..vols().len() { + if vols()[i].mounted && !crate::ahci::is_up(vols()[i].disk) { + vols()[i].mounted = false; + crate::serial_println!("fat: unmounted \"{}\"", vols()[i].uid); + unmounted += 1; + } + } + // Mount every disk that has no mounted volume. + for d in 0..disks { + if !crate::ahci::select(d) { + continue; + } + if vols().iter().any(|v| v.mounted && v.disk == d) { + continue; + } + if let Some(volume) = try_mount(d) { + let uid = volume.uid.clone(); + let vol = match vols().iter().position(|v| !v.mounted && v.disk == d) { + // Reuse the disk's own tombstoned slot (indices stay stable). + Some(slot) => { + vols()[slot] = volume; + slot + } + None => { + vols().push(volume); + vols().len() - 1 + } + }; + if select_vol(vol).is_ok() { + fsinfo_recount(); + } + let count = list_dir(vol, root_cluster(vol)) + .map(|es| es.len()) + .unwrap_or(0); + crate::serial_println!( + "fat: mounted \"{}\" at /disk/{} ({} clusters, {} B/cluster, root: {} entries)", + uid, + uid, + v().cluster_count, + cluster_size(), + count + ); + mounted += 1; + } + } + (mounted, unmounted) +} + /// Unique mount name of volume `vol` ("RSDOS-5BD1E55F"). pub fn uid(vol: usize) -> &'static str { vols().get(vol).map(|v| v.uid.as_str()).unwrap_or("") @@ -1099,11 +1163,12 @@ fn try_mount(d: usize) -> Option { if label.is_empty() { label = String::from("DISK"); } - // Deduplicate against already-mounted volumes (e.g. cloned images). + // Deduplicate against already-MOUNTED volumes (e.g. cloned images). + // Tombstoned slots are skipped so a re-plugged disk keeps its name. let mut uid = alloc::format!("{}-{:08X}", label, serial); let mut n = 2u32; loop { - let taken = vols().iter().any(|v| v.uid == uid); + let taken = vols().iter().any(|v| v.mounted && v.uid == uid); if !taken { break; } @@ -1113,6 +1178,7 @@ fn try_mount(d: usize) -> Option { Some(Volume { disk: d, uid, + mounted: true, sectors_per_cluster: spc, reserved, fat_size, -- cgit v1.2.3