recovery: parameterize the read primitive with ReadParams (speed/FUA/timeout)

Add ReadParams { speed: SpeedPref, fua: bool, timeout: TimeoutPref } and
thread it through read_span so every wedge-safe handler read can request a
spindle speed (SET CD SPEED issued only on change, restored to max when the
handler exits), set the READ(10) FUA bit, and pick the 10s vs 60s timeout.

- SectorSource gains read_sectors_fua (default ignores fua); Drive sets the
  CDB bit, DecryptingSectorSource threads fua to its inner read.
- recovery_read gains a fua param.
- Linear becomes { direction, params }; Bisect/Jump take params. Existing
  tier-0/1 instances keep identical behavior (max speed, no FUA, fast/deep).
- Scoreboard keys on the full-config String name (linear:fwd:max:fast, ...).
- FakeDisc observes speed + FUA + approach so specialist techniques are
  provably exercised in later commits.

cargo test -p libfreemkv green (2193 passed).
This commit is contained in:
Matthew Jackson
2026-07-01 13:50:35 -07:00
parent e94319c099
commit 0c8153304e
5 changed files with 545 additions and 133 deletions
+49
View File
@@ -53,6 +53,33 @@ pub trait SectorSource: Send {
recovery: bool,
) -> Result<usize>;
/// Like [`read_sectors`], but with an explicit Force Unit Access request.
///
/// `fua = true` asks the drive to bypass its readahead cache and physically
/// re-fetch the medium — a Pass-N marginal-sector lever: a cached hit can
/// mask a *stochastic* sector that would land differently off the platter on
/// each physical read, so FuaRetry re-reads it FUA. It is never blanket-
/// applied to the bulk path (forcing every sequential read past the cache
/// collapses streaming throughput ~10×).
///
/// The default ignores `fua` and delegates to [`read_sectors`]: only a live
/// [`Drive`] sets the CDB bit; file- / memory-backed sources have no drive
/// cache to bypass, so FUA is meaningless to them.
///
/// [`read_sectors`]: SectorSource::read_sectors
/// [`Drive`]: crate::drive::Drive
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
let _ = fua;
self.read_sectors(lba, count, buf, recovery)
}
/// Optional speed control for sources that map to a physical
/// drive. No-op for everything else.
fn set_speed(&mut self, _kbs: u16) {}
@@ -87,6 +114,17 @@ impl SectorSource for Box<dyn SectorSource> {
(**self).read_sectors(lba, count, buf, recovery)
}
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
(**self).read_sectors_fua(lba, count, buf, recovery, fua)
}
fn set_speed(&mut self, kbs: u16) {
(**self).set_speed(kbs)
}
@@ -107,6 +145,17 @@ impl SectorSource for &mut (dyn SectorSource + '_) {
(**self).read_sectors(lba, count, buf, recovery)
}
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
(**self).read_sectors_fua(lba, count, buf, recovery, fua)
}
fn set_speed(&mut self, kbs: u16) {
(**self).set_speed(kbs)
}