From ff7f3028a534ce07a32522da37865fa92a9abf63 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:49:14 -0700 Subject: [PATCH] drive: always set FUA on READ(10) to bypass the drive cache Every Drive::read now forces the read from physical media instead of letting the drive satisfy it from cache. A recovery tool must not trust the cache: on a re-read of a marginal sector the BU40N can return a cached miss (~4 ms) instead of giving the surface a fresh physical attempt (~40-250 ms), masking a sector that would otherwise read on a real media hit. FUA (READ(10) byte 1 bit 3) is validated on the BU40N/Initio bridge. --- src/drive/mod.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/drive/mod.rs b/src/drive/mod.rs index 3d3041b..e0c7f04 100644 --- a/src/drive/mod.rs +++ b/src/drive/mod.rs @@ -723,7 +723,16 @@ impl Drive { ) -> Result { let cdb = [ crate::scsi::SCSI_READ_10, - 0x00, + // FUA (Force Unit Access, bit 3 = 0x08): every read comes from the + // physical MEDIA, never the drive's cache. A recovery tool must not + // trust the cache — the BU40N caches aggressively (a re-read of a + // good sector returns in ~4 ms vs ~250 ms from media), so without + // FUA a retry of a marginal sector could return a cached result + // instead of giving the surface a fresh physical attempt. A + // stochastic sector that would read on a real media hit is otherwise + // masked by a cached miss. Cache-bypass is validated on the + // BU40N/Initio bridge (FUA reads execute, media 40 ms vs cache 4 ms). + 0x08, (lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, @@ -1403,8 +1412,9 @@ mod command_tests { #[test] fn read_builds_read10_cdb_with_be_lba_and_count() { // Drive::read issues READ(10) (0x28). LBA bytes 2..5 big-endian, - // transfer length bytes 7..8 big-endian (MMC-6). No FUA on this - // path (byte 1 == 0). Distinct nibbles catch a swapped shift. + // transfer length bytes 7..8 big-endian (MMC-6). FUA is set (byte 1 == + // 0x08) so every read bypasses the drive cache and hits physical media. + // Distinct nibbles catch a swapped shift. let RecordingHarness { drive: mut d, cdb, @@ -1415,7 +1425,10 @@ mod command_tests { assert_eq!(n, 4096, "returns transport bytes_transferred"); let c = cdb.lock().unwrap(); assert_eq!(c[0], crate::scsi::SCSI_READ_10); - assert_eq!(c[1], 0x00, "Drive::read path sets no FUA"); + assert_eq!( + c[1], 0x08, + "Drive::read sets FUA (force media, bypass cache)" + ); assert_eq!(&c[2..6], &[0x00, 0xAB, 0xCD, 0xEF], "LBA big-endian"); assert_eq!(&c[7..9], &[0x00, 0x02], "transfer length big-endian"); }