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:
+131
-92
@@ -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
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user