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
+17 -1
View File
@@ -470,6 +470,20 @@ impl<S: SectorSource> SectorSource for DecryptingSectorSource<S> {
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
// Bulk path: no Force Unit Access (the cache IS the streaming
// throughput). FUA is a Pass-N recovery lever threaded through
// `read_sectors_fua`.
self.read_sectors_fua(lba, count, buf, recovery, false)
}
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
// Defense-in-depth: AACS aligned units are 3 sectors (6144 bytes) and
// `decrypt_sectors` anchors units at buffer offset 0. A read that does
@@ -488,7 +502,9 @@ impl<S: SectorSource> SectorSource for DecryptingSectorSource<S> {
return Err(crate::error::Error::DecryptFailed);
}
let read_t0 = std::time::Instant::now();
let n = self.inner.read_sectors(lba, count, buf, recovery)?;
let n = self
.inner
.read_sectors_fua(lba, count, buf, recovery, fua)?;
let read_ms = read_t0.elapsed().as_millis() as u64;
// Decrypt the bytes just read. Scheme-agnostic: `decrypt_sectors*`
// dispatches on the keys (None / CSS / AACS) and returns the count of
+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)
}