Speed table: generic zone-based speed management

- SpeedTable: maps disc positions to optimal speeds
- Default: max speed everywhere (drive manages itself)
- After read_speed_table(): calibrated per-zone speeds
- One u32 comparison per read on hot path
- Error recovery: reduce() / resume() override table temporarily
- Replaces old tier-based speed management in ContentReader
- MT1959 split into mod.rs + variant_a.rs + variant_b.rs
- PlatformDriver: init() + read_speed_table() + is_ready()
This commit is contained in:
MattJackson
2026-04-09 13:33:02 -07:00
parent d7d13d2849
commit 5c73d9d5a0
7 changed files with 327 additions and 339 deletions
+131 -92
View File
@@ -1,41 +1,141 @@
//! Drive speed control — query and set read speeds.
//! Drive speed management — zone-based speed table.
//!
//! Uses MMC-6 SET CD SPEED (0xBB) command.
//! Reference: MMC-6 §6.30
//! Every DriveSession has a SpeedTable. Default: max speed everywhere.
//! After init(): calibrated per-zone speeds from disc surface probes.
//! One u32 comparison per read on the hot path.
/// Disc read speed.
/// Speed table — maps disc positions to optimal read speeds.
#[derive(Debug, Clone)]
pub struct SpeedTable {
zones: Vec<(u32, u16)>, // (start_lba, speed_kbs), sorted by lba
current_speed: u16,
next_boundary: u32,
}
impl SpeedTable {
/// Default: max speed, whole disc. Drive manages itself.
pub fn new() -> Self {
SpeedTable {
zones: vec![(0, 0xFFFF)],
current_speed: 0, // force first SET_CD_SPEED
next_boundary: 0, // force first lookup
}
}
/// Hot path: has the speed zone changed for this LBA?
/// Returns Some(speed_kbs) only when a SET_CD_SPEED is needed.
#[inline]
pub fn speed_for(&mut self, lba: u32) -> Option<u16> {
if lba < self.next_boundary {
return None;
}
self.transition(lba)
}
/// Zone transition — lookup + precompute next boundary.
fn transition(&mut self, lba: u32) -> Option<u16> {
let mut zone_idx = 0;
for (i, &(start, _)) in self.zones.iter().enumerate() {
if start <= lba {
zone_idx = i;
} else {
break;
}
}
let speed = self.zones[zone_idx].1;
self.next_boundary = if zone_idx + 1 < self.zones.len() {
self.zones[zone_idx + 1].0
} else {
u32::MAX
};
if speed == self.current_speed {
return None;
}
self.current_speed = speed;
Some(speed)
}
/// Load calibrated zones. Converts from platform probe data to generic (lba, kbs).
/// `disc_sectors`: total disc capacity from READ CAPACITY.
/// `probes`: (probe_address, speed_index) pairs from calibration scan.
/// `probe_range`: max probe address space (0x10000 for MT1959).
/// `speed_multiplier`: KB/s per speed unit (4500 for BD 1x).
pub fn load_calibration(
&mut self,
disc_sectors: u32,
probes: &[(u16, u8)],
probe_range: u32,
speed_multiplier: u16,
) {
if probes.is_empty() || disc_sectors == 0 {
return;
}
let mut zones: Vec<(u32, u16)> = Vec::new();
for &(probe_addr, speed_idx) in probes {
let lba = (probe_addr as u64 * disc_sectors as u64 / probe_range as u64) as u32;
let kbs = speed_idx as u16 * speed_multiplier;
zones.push((lba, kbs));
}
zones.sort_by_key(|&(lba, _)| lba);
// Deduplicate: keep only zone boundaries where speed changes
let mut deduped: Vec<(u32, u16)> = Vec::new();
for &(lba, kbs) in &zones {
if deduped.last().map_or(true, |&(_, prev_kbs)| prev_kbs != kbs) {
deduped.push((lba, kbs));
}
}
if deduped.is_empty() {
return;
}
self.zones = deduped;
self.current_speed = 0;
self.next_boundary = 0;
}
/// Temporarily reduce speed for error recovery.
pub fn reduce(&mut self) -> u16 {
let speed = (self.current_speed / 2).max(4500);
self.current_speed = speed;
speed
}
/// Resume table-driven speed at this LBA.
pub fn resume(&mut self, lba: u32) {
self.current_speed = 0;
self.next_boundary = 0;
self.transition(lba);
}
/// Current speed in KB/s.
pub fn current(&self) -> u16 {
self.current_speed
}
/// Number of zones.
pub fn zone_count(&self) -> usize {
self.zones.len()
}
}
// Keep DriveSpeed enum for CLI display
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DriveSpeed {
/// Blu-ray 1x = 4,500 KB/s
BD1x,
/// Blu-ray 2x = 9,000 KB/s
BD2x,
/// Blu-ray 4x = 18,000 KB/s
BD4x,
/// Blu-ray 6x = 27,000 KB/s
BD6x,
/// Blu-ray 8x = 36,000 KB/s
BD8x,
/// Blu-ray 10x = 45,000 KB/s
BD10x,
/// Blu-ray 12x = 54,000 KB/s
BD12x,
/// DVD 1x = 1,385 KB/s
DVD1x,
/// DVD 2x = 2,770 KB/s
DVD2x,
/// DVD 4x = 5,540 KB/s
DVD4x,
/// DVD 8x = 11,080 KB/s
DVD8x,
/// DVD 16x = 22,160 KB/s
DVD16x,
/// Maximum speed — drive decides
BD1x, BD2x, BD4x, BD6x, BD8x, BD10x, BD12x,
DVD1x, DVD2x, DVD4x, DVD8x, DVD16x,
Max,
}
impl DriveSpeed {
/// Convert to KB/s for MMC-6 SET CD SPEED command.
pub fn to_kbps(self) -> u16 {
match self {
DriveSpeed::BD1x => 4_500,
@@ -53,71 +153,10 @@ impl DriveSpeed {
DriveSpeed::Max => 0xFFFF,
}
}
/// Create from KB/s value, rounding to nearest standard speed.
pub fn from_kbps(kbps: u16) -> Self {
match kbps {
0..=2_000 => DriveSpeed::DVD1x,
2_001..=4_000 => DriveSpeed::DVD2x,
4_001..=6_000 => DriveSpeed::BD1x,
6_001..=13_000 => DriveSpeed::BD2x,
13_001..=22_000 => DriveSpeed::BD4x,
22_001..=31_000 => DriveSpeed::BD6x,
31_001..=40_000 => DriveSpeed::BD8x,
40_001..=49_000 => DriveSpeed::BD10x,
49_001..=u16::MAX => DriveSpeed::BD12x,
}
}
/// Human-readable label.
pub fn label(&self) -> &'static str {
match self {
DriveSpeed::BD1x => "BD 1x",
DriveSpeed::BD2x => "BD 2x",
DriveSpeed::BD4x => "BD 4x",
DriveSpeed::BD6x => "BD 6x",
DriveSpeed::BD8x => "BD 8x",
DriveSpeed::BD10x => "BD 10x",
DriveSpeed::BD12x => "BD 12x",
DriveSpeed::DVD1x => "DVD 1x",
DriveSpeed::DVD2x => "DVD 2x",
DriveSpeed::DVD4x => "DVD 4x",
DriveSpeed::DVD8x => "DVD 8x",
DriveSpeed::DVD16x => "DVD 16x",
DriveSpeed::Max => "Max",
}
}
/// All standard Blu-ray speeds.
pub fn all_bd() -> &'static [DriveSpeed] {
&[DriveSpeed::BD1x, DriveSpeed::BD2x, DriveSpeed::BD4x,
DriveSpeed::BD6x, DriveSpeed::BD8x, DriveSpeed::BD10x, DriveSpeed::BD12x]
}
/// All standard DVD speeds.
pub fn all_dvd() -> &'static [DriveSpeed] {
&[DriveSpeed::DVD1x, DriveSpeed::DVD2x, DriveSpeed::DVD4x,
DriveSpeed::DVD8x, DriveSpeed::DVD16x]
}
}
impl std::fmt::Display for DriveSpeed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({} KB/s)", self.label(), self.to_kbps())
write!(f, "{:?} ({} KB/s)", self, self.to_kbps())
}
}
/// Build SET CD SPEED CDB — MMC-6 §6.30
pub fn set_cd_speed_cdb(read_speed: DriveSpeed) -> [u8; 12] {
let kbps = read_speed.to_kbps();
[
0xBB, // SET CD SPEED opcode
0x00, // reserved
(kbps >> 8) as u8, // read speed MSB
kbps as u8, // read speed LSB
0xFF, // write speed MSB (0xFFFF = don't change)
0xFF, // write speed LSB
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // reserved
]
}