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/fs.rs | 84 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 21 deletions(-) (limited to 'rsnix/src/fs.rs') diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs index 063560a..b560bfa 100644 --- a/rsnix/src/fs.rs +++ b/rsnix/src/fs.rs @@ -183,33 +183,75 @@ pub fn init() -> (usize, usize) { ); // Mount every FAT32 volume under /disk// (UID = label-serial). - let disks = crate::fat::volume_count(); - if disks > 0 { - let disk_dir = add("disk", Some(root), FileType::Dir, NodeData::Dir(Vec::new())); - for vol in 0..disks { - let uid = String::from(crate::fat::uid(vol)); - let root_cl = crate::fat::root_cluster(vol); - add( - &uid, - Some(disk_dir), - FileType::Dir, - NodeData::Disk(DiskInfo { - vol, - parent: root_cl, - name: String::new(), - is_dir: true, - size: 0, - cluster: root_cl, - }), - ); - } - } + // `rescan_disks()` also keeps this table current after hotplug events. + let _ = rescan_disks(); let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count(); let files = nodes().len() - dirs; (dirs, files) } +/// (Re)sync the /disk mount table with the FAT volumes: create the /disk +/// dir lazily, ensure a mount node for every mounted volume (refreshing +/// its backing info), and tombstone nodes whose volume was unmounted. +/// Returns (added, removed) node ops. +pub fn rescan_disks() -> (usize, usize) { + let mut added = 0usize; + let mut removed = 0usize; + let disk_dir = match lookup(0, "disk") { + Some(h) => h, + None => add("disk", Some(0), FileType::Dir, NodeData::Dir(Vec::new())), + }; + for vol in 0..crate::fat::volume_count() { + if !crate::fat::is_mounted(vol) { + continue; + } + let uid = String::from(crate::fat::uid(vol)); + let root_cl = crate::fat::root_cluster(vol); + match lookup(disk_dir, &uid) { + Some(h) => { + // Refresh the backing info in case the volume's slot moved. + if let NodeData::Disk(i) = &mut nodes()[h].data { + i.vol = vol; + i.parent = root_cl; + i.cluster = root_cl; + } + nodes()[h].removed = false; + } + None => { + add( + &uid, + Some(disk_dir), + FileType::Dir, + NodeData::Disk(DiskInfo { + vol, + parent: root_cl, + name: String::new(), + is_dir: true, + size: 0, + cluster: root_cl, + }), + ); + added += 1; + } + } + } + if let NodeData::Dir(v) = &nodes()[disk_dir].data { + for &c in v.clone().iter() { + if nodes()[c].removed { + continue; + } + if let NodeData::Disk(i) = &nodes()[c].data + && !crate::fat::is_mounted(i.vol) + { + nodes()[c].removed = true; + removed += 1; + } + } + } + (added, removed) +} + fn lookup(dir: usize, name: &str) -> Option { let children = match &nodes()[dir].data { NodeData::Dir(v) => v.clone(), -- cgit v1.2.3