summaryrefslogtreecommitdiffstats
path: root/rsnix/src/fs.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 15:00:47 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 15:00:47 +0200
commitd562e9df2bc5e69bba7298a9439e2adca5c18faf (patch)
tree8d763e60d512e7a4230413f13caaf9153cb2ec54 /rsnix/src/fs.rs
parentklog: [ssss.mmm] millisecond timestamps from the PIT (diff)
downloadrs-dos-d562e9df2bc5e69bba7298a9439e2adca5c18faf.tar.gz
rs-dos-d562e9df2bc5e69bba7298a9439e2adca5c18faf.zip
rsnix: runtime device detection — rescan for hotplugged/removed disks
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/<UID> 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.
Diffstat (limited to '')
-rw-r--r--rsnix/src/fs.rs84
1 files changed, 63 insertions, 21 deletions
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) {
183 ); 183 );
184 184
185 // Mount every FAT32 volume under /disk/<UID>/ (UID = label-serial). 185 // Mount every FAT32 volume under /disk/<UID>/ (UID = label-serial).
186 let disks = crate::fat::volume_count(); 186 // `rescan_disks()` also keeps this table current after hotplug events.
187 if disks > 0 { 187 let _ = rescan_disks();
188 let disk_dir = add("disk", Some(root), FileType::Dir, NodeData::Dir(Vec::new()));
189 for vol in 0..disks {
190 let uid = String::from(crate::fat::uid(vol));
191 let root_cl = crate::fat::root_cluster(vol);
192 add(
193 &uid,
194 Some(disk_dir),
195 FileType::Dir,
196 NodeData::Disk(DiskInfo {
197 vol,
198 parent: root_cl,
199 name: String::new(),
200 is_dir: true,
201 size: 0,
202 cluster: root_cl,
203 }),
204 );
205 }
206 }
207 188
208 let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count(); 189 let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count();
209 let files = nodes().len() - dirs; 190 let files = nodes().len() - dirs;
210 (dirs, files) 191 (dirs, files)
211} 192}
212 193
194/// (Re)sync the /disk mount table with the FAT volumes: create the /disk
195/// dir lazily, ensure a mount node for every mounted volume (refreshing
196/// its backing info), and tombstone nodes whose volume was unmounted.
197/// Returns (added, removed) node ops.
198pub fn rescan_disks() -> (usize, usize) {
199 let mut added = 0usize;
200 let mut removed = 0usize;
201 let disk_dir = match lookup(0, "disk") {
202 Some(h) => h,
203 None => add("disk", Some(0), FileType::Dir, NodeData::Dir(Vec::new())),
204 };
205 for vol in 0..crate::fat::volume_count() {
206 if !crate::fat::is_mounted(vol) {
207 continue;
208 }
209 let uid = String::from(crate::fat::uid(vol));
210 let root_cl = crate::fat::root_cluster(vol);
211 match lookup(disk_dir, &uid) {
212 Some(h) => {
213 // Refresh the backing info in case the volume's slot moved.
214 if let NodeData::Disk(i) = &mut nodes()[h].data {
215 i.vol = vol;
216 i.parent = root_cl;
217 i.cluster = root_cl;
218 }
219 nodes()[h].removed = false;
220 }
221 None => {
222 add(
223 &uid,
224 Some(disk_dir),
225 FileType::Dir,
226 NodeData::Disk(DiskInfo {
227 vol,
228 parent: root_cl,
229 name: String::new(),
230 is_dir: true,
231 size: 0,
232 cluster: root_cl,
233 }),
234 );
235 added += 1;
236 }
237 }
238 }
239 if let NodeData::Dir(v) = &nodes()[disk_dir].data {
240 for &c in v.clone().iter() {
241 if nodes()[c].removed {
242 continue;
243 }
244 if let NodeData::Disk(i) = &nodes()[c].data
245 && !crate::fat::is_mounted(i.vol)
246 {
247 nodes()[c].removed = true;
248 removed += 1;
249 }
250 }
251 }
252 (added, removed)
253}
254
213fn lookup(dir: usize, name: &str) -> Option<usize> { 255fn lookup(dir: usize, name: &str) -> Option<usize> {
214 let children = match &nodes()[dir].data { 256 let children = match &nodes()[dir].data {
215 NodeData::Dir(v) => v.clone(), 257 NodeData::Dir(v) => v.clone(),