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:
+25
-46
@@ -774,9 +774,7 @@ pub struct ContentReader<'a> {
|
||||
/// Consecutive errors at current position
|
||||
error_streak: u32,
|
||||
/// Current speed tier index (0 = max, higher = slower)
|
||||
speed_tier: usize,
|
||||
/// Last time maintain_speed was called
|
||||
last_speed_maintain: std::time::Instant,
|
||||
/// Total read errors encountered
|
||||
pub errors: u32,
|
||||
}
|
||||
@@ -823,8 +821,6 @@ impl Disc {
|
||||
max_batch_sectors: max_batch,
|
||||
ok_streak: 0,
|
||||
error_streak: 0,
|
||||
speed_tier: 0,
|
||||
last_speed_maintain: std::time::Instant::now(),
|
||||
errors: 0,
|
||||
})
|
||||
}
|
||||
@@ -879,15 +875,6 @@ const RAMP_BATCH_AFTER: u32 = 5; // successes before doubling batch size
|
||||
const RAMP_SPEED_AFTER: u32 = 50; // successes at max batch before restoring speed
|
||||
const SLOW_SPEED_AFTER: u32 = 3; // consecutive errors before reducing disc speed
|
||||
|
||||
/// Disc speed tiers (KB/s for SET CD SPEED).
|
||||
/// Blu-ray: 1x=4500, 2x=9000, 4x=18000, 8x=36000, 12x=54000
|
||||
const SPEED_TIERS: &[u16] = &[
|
||||
0xFFFF, // tier 0: max (drive decides, typically 8-12x)
|
||||
36000, // tier 1: 8x BD (~36 MB/s)
|
||||
18000, // tier 2: 4x BD (~18 MB/s)
|
||||
9000, // tier 3: 2x BD (~9 MB/s)
|
||||
4500, // tier 4: 1x BD (~4.5 MB/s) — last resort
|
||||
];
|
||||
|
||||
impl<'a> ContentReader<'a> {
|
||||
/// Total bytes across all extents (for progress display).
|
||||
@@ -973,29 +960,10 @@ impl<'a> ContentReader<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set disc spin speed via SCSI SET CD SPEED.
|
||||
fn set_speed(&mut self, tier: usize) {
|
||||
let tier = tier.min(SPEED_TIERS.len() - 1);
|
||||
if tier != self.speed_tier {
|
||||
self.speed_tier = tier;
|
||||
let speed_kbs = SPEED_TIERS[tier];
|
||||
let cdb = crate::scsi::build_set_cd_speed(speed_kbs);
|
||||
let mut dummy = [0u8; 0];
|
||||
let _ = self.session.scsi_execute(
|
||||
&cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a batch of sectors into the internal buffer.
|
||||
///
|
||||
/// Adaptive strategy:
|
||||
/// 1. Read at current batch size
|
||||
/// 2. On success: ramp batch up (double after 5 successes),
|
||||
/// then restore disc speed (after 50 at max batch)
|
||||
/// 3. On error: halve batch, pause. After 3 consecutive errors,
|
||||
/// also reduce disc spin speed (scratched/damaged region).
|
||||
/// 4. At min batch + still failing: retry once, then skip + zero-fill.
|
||||
/// Speed management: speed table checked before each read.
|
||||
/// On error: reduce speed, halve batch. On recovery: resume from table.
|
||||
fn fill_buffer(&mut self) -> Result<bool> {
|
||||
loop {
|
||||
if self.current_extent >= self.extents.len() {
|
||||
@@ -1019,6 +987,15 @@ impl<'a> ContentReader<'a> {
|
||||
let byte_count = sectors_to_read as usize * 2048;
|
||||
self.read_buf.resize(byte_count, 0);
|
||||
|
||||
// Check speed table — send SET_CD_SPEED if zone changed
|
||||
if let Some(speed_kbs) = self.session.speed_table.speed_for(lba) {
|
||||
let cdb = crate::scsi::build_set_cd_speed(speed_kbs);
|
||||
let mut dummy = [0u8; 0];
|
||||
let _ = self.session.scsi_execute(
|
||||
&cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000,
|
||||
);
|
||||
}
|
||||
|
||||
match self.read_sectors(lba, sectors_to_read) {
|
||||
Ok(_) => {
|
||||
self.buf_len = sectors_to_read as usize / 3;
|
||||
@@ -1031,17 +1008,16 @@ impl<'a> ContentReader<'a> {
|
||||
self.current_offset = 0;
|
||||
}
|
||||
|
||||
// Ramp up: batch size first, then disc speed
|
||||
// Ramp up batch size after consecutive successes
|
||||
self.ok_streak += 1;
|
||||
if self.batch_sectors < self.max_batch_sectors {
|
||||
if self.ok_streak >= RAMP_BATCH_AFTER {
|
||||
if self.batch_sectors < self.max_batch_sectors && self.ok_streak >= RAMP_BATCH_AFTER {
|
||||
self.batch_sectors = (self.batch_sectors * 2).min(self.max_batch_sectors);
|
||||
self.ok_streak = 0;
|
||||
}
|
||||
} else if self.speed_tier > 0 && self.ok_streak >= RAMP_SPEED_AFTER {
|
||||
// At max batch for a while — try faster disc speed
|
||||
self.set_speed(self.speed_tier - 1);
|
||||
self.ok_streak = 0;
|
||||
|
||||
// Resume table-driven speed after sustained success
|
||||
if self.error_streak == 0 && self.ok_streak >= RAMP_SPEED_AFTER {
|
||||
self.session.speed_table.resume(lba);
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
@@ -1051,11 +1027,14 @@ impl<'a> ContentReader<'a> {
|
||||
self.error_streak += 1;
|
||||
self.ok_streak = 0;
|
||||
|
||||
// Reduce disc speed after repeated errors (physical problem)
|
||||
if self.error_streak >= SLOW_SPEED_AFTER
|
||||
&& self.speed_tier < SPEED_TIERS.len() - 1
|
||||
{
|
||||
self.set_speed(self.speed_tier + 1);
|
||||
// Reduce speed after repeated errors
|
||||
if self.error_streak >= SLOW_SPEED_AFTER {
|
||||
let speed = self.session.speed_table.reduce();
|
||||
let cdb = crate::scsi::build_set_cd_speed(speed);
|
||||
let mut dummy = [0u8; 0];
|
||||
let _ = self.session.scsi_execute(
|
||||
&cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000,
|
||||
);
|
||||
self.error_streak = 0; // reset — give new speed a chance
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -12,10 +12,12 @@ use crate::identity::DriveId;
|
||||
use crate::profile::{self, DriveProfile, ProfileMatch};
|
||||
use crate::platform::PlatformDriver;
|
||||
use crate::platform::mt1959::Mt1959;
|
||||
use crate::speed::SpeedTable;
|
||||
|
||||
pub struct DriveSession {
|
||||
scsi: Box<dyn ScsiTransport>,
|
||||
driver: Box<dyn PlatformDriver>,
|
||||
pub speed_table: SpeedTable,
|
||||
pub profile: DriveProfile,
|
||||
pub platform: profile::Platform,
|
||||
pub drive_id: DriveId,
|
||||
@@ -40,6 +42,7 @@ impl DriveSession {
|
||||
Ok(DriveSession {
|
||||
scsi: transport,
|
||||
driver,
|
||||
speed_table: SpeedTable::new(),
|
||||
platform: m.platform,
|
||||
profile: m.profile,
|
||||
drive_id,
|
||||
@@ -71,16 +74,19 @@ impl DriveSession {
|
||||
&self.device_path
|
||||
}
|
||||
|
||||
/// Initialize drive — unlock + firmware upload.
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
self.driver.init(self.scsi.as_mut())
|
||||
}
|
||||
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.driver.is_ready()
|
||||
/// Read speed zones from disc into speed table.
|
||||
/// Requires init() first. Optional — without this, drive manages speed itself.
|
||||
pub fn read_speed_table(&mut self) -> Result<()> {
|
||||
self.driver.read_speed_table(self.scsi.as_mut(), &mut self.speed_table)
|
||||
}
|
||||
|
||||
pub fn set_read_speed(&mut self, lba: u32) -> Result<()> {
|
||||
self.driver.set_read_speed(self.scsi.as_mut(), lba)
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.driver.is_ready()
|
||||
}
|
||||
|
||||
pub fn read_disc(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
|
||||
|
||||
+8
-12
@@ -1,22 +1,18 @@
|
||||
//! Platform-specific drive initialization and speed management.
|
||||
//!
|
||||
//! The Platform trait is minimal by design. Callers use init() once,
|
||||
//! then set_read_speed() during reads. Internal operations cannot be
|
||||
//! called directly — this prevents out-of-sequence operations.
|
||||
//! Platform-specific drive initialization and calibration.
|
||||
|
||||
pub mod mt1959;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::scsi::ScsiTransport;
|
||||
use crate::speed::SpeedTable;
|
||||
|
||||
/// Platform trait — locked-down interface.
|
||||
///
|
||||
/// Only three operations exposed:
|
||||
/// init() — one-time initialization
|
||||
/// set_read_speed() — per-zone speed during reads
|
||||
/// is_ready() — state check
|
||||
pub(crate) trait PlatformDriver {
|
||||
/// Unlock drive + upload firmware if needed.
|
||||
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>;
|
||||
fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()>;
|
||||
|
||||
/// Read speed zones from disc surface, fill speed table.
|
||||
fn read_speed_table(&mut self, scsi: &mut dyn ScsiTransport, speed_table: &mut SpeedTable) -> Result<()>;
|
||||
|
||||
/// True after successful init().
|
||||
fn is_ready(&self) -> bool;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
//! MT1959 platform — unlock, firmware upload, calibration, speed management.
|
||||
//! MT1959 platform — shared logic for both variants.
|
||||
|
||||
mod variant_a;
|
||||
mod variant_b;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::profile::DriveProfile;
|
||||
use crate::scsi::{self, DataDirection, ScsiTransport};
|
||||
use crate::speed::SpeedTable;
|
||||
use super::PlatformDriver;
|
||||
|
||||
const UNLOCK_RESPONSE_SIZE: u8 = 64;
|
||||
|
||||
// Variant constants
|
||||
const MODE_A: u8 = 0x01;
|
||||
const MODE_B: u8 = 0x02;
|
||||
const BUFFER_ID_A: u8 = 0x44;
|
||||
const BUFFER_ID_B: u8 = 0x77;
|
||||
const NOMINAL_SPEED_A: [u8; 12] = [0xBB, 0x00, 0x23, 0x28, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
const NOMINAL_SPEED_B: [u8; 12] = [0x00, 0x00, 0xBB, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00];
|
||||
const FIRMWARE_EXTRA_B: [u8; 16] = [0; 16];
|
||||
const VERIFY_COMMAND_B: [u8; 10] = [0xF1, 0x01, 0x02, 0x00, 0x0D, 0x30, 0x01, 0xF3, 0xAD, 0x23];
|
||||
|
||||
pub struct Mt1959 {
|
||||
profile: DriveProfile,
|
||||
mode: u8,
|
||||
buffer_id: u8,
|
||||
unlocked: bool,
|
||||
pub(crate) profile: DriveProfile,
|
||||
pub(crate) mode: u8,
|
||||
pub(crate) buffer_id: u8,
|
||||
pub(crate) unlocked: bool,
|
||||
speed_table: [u16; 64],
|
||||
disc_sectors: u32,
|
||||
calibrated: bool,
|
||||
@@ -36,9 +36,7 @@ impl Mt1959 {
|
||||
(MODE_A, BUFFER_ID_A)
|
||||
};
|
||||
Mt1959 {
|
||||
profile,
|
||||
mode,
|
||||
buffer_id,
|
||||
profile, mode, buffer_id,
|
||||
unlocked: false,
|
||||
speed_table: [0u16; 64],
|
||||
disc_sectors: 0,
|
||||
@@ -47,7 +45,9 @@ impl Mt1959 {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_buffer_sub(&self, sub_cmd: u8, address: u16, length: u8) -> [u8; 10] {
|
||||
// ── SCSI helpers (shared by both variants) ─────────────────────────
|
||||
|
||||
pub(crate) fn read_buffer_sub(&self, sub_cmd: u8, address: u16, length: u8) -> [u8; 10] {
|
||||
[
|
||||
0x3C, self.mode, self.buffer_id, sub_cmd,
|
||||
(address >> 8) as u8, address as u8,
|
||||
@@ -55,7 +55,7 @@ impl Mt1959 {
|
||||
]
|
||||
}
|
||||
|
||||
fn read_buffer_probe(
|
||||
pub(crate) fn read_buffer_probe(
|
||||
&self, scsi: &mut dyn ScsiTransport,
|
||||
sub_cmd: u8, address: u16, buf: &mut [u8], expected: usize,
|
||||
) -> Result<usize> {
|
||||
@@ -67,21 +67,16 @@ impl Mt1959 {
|
||||
Ok(result.bytes_transferred)
|
||||
}
|
||||
|
||||
fn set_cd_speed_max(&self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
pub(crate) fn set_cd_speed_max(&self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let cdb = scsi::build_set_cd_speed(0xFFFF);
|
||||
let mut dummy = [0u8; 0];
|
||||
scsi.execute(&cdb, DataDirection::None, &mut dummy, 5_000)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_cd_speed(&self, scsi: &mut dyn ScsiTransport, speed: u16) -> Result<()> {
|
||||
let cdb = scsi::build_set_cd_speed(speed);
|
||||
let mut dummy = [0u8; 0];
|
||||
scsi.execute(&cdb, DataDirection::None, &mut dummy, 5_000)?;
|
||||
Ok(())
|
||||
}
|
||||
// ── Unlock (shared) ────────────────────────────────────────────────
|
||||
|
||||
fn do_unlock(&mut self, scsi: &mut dyn ScsiTransport) -> Result<Vec<u8>> {
|
||||
pub(crate) fn do_unlock(&mut self, scsi: &mut dyn ScsiTransport) -> Result<Vec<u8>> {
|
||||
let cdb = [
|
||||
0x3C, self.mode, self.buffer_id,
|
||||
0x00, 0x00, 0x00,
|
||||
@@ -124,49 +119,38 @@ impl Mt1959 {
|
||||
}
|
||||
Err(Error::ScsiError { opcode: 0x3C, status: 0xFF, sense_key: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDriver for Mt1959 {
|
||||
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
if self.unlocked && self.calibrated {
|
||||
return Ok(());
|
||||
}
|
||||
self.run_init(scsi)
|
||||
}
|
||||
// ── Init (unlock + firmware) ───────────────────────────────────────
|
||||
|
||||
fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()> {
|
||||
if !self.calibrated {
|
||||
return Ok(());
|
||||
fn run_init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let mut unlocked = false;
|
||||
for _attempt in 0..6 {
|
||||
match self.do_unlock(scsi) {
|
||||
Ok(_) => { unlocked = true; break; }
|
||||
Err(Error::SignatureMismatch { .. }) => {
|
||||
return Err(Error::UnlockFailed {
|
||||
detail: "signature mismatch — wrong profile for this drive".into(),
|
||||
});
|
||||
}
|
||||
self.run_set_read_speed(scsi, lba)
|
||||
Err(_) => {
|
||||
let ok = if self.mode == MODE_A {
|
||||
variant_a::load_firmware(self, scsi).is_ok()
|
||||
} else {
|
||||
variant_b::load_firmware(self, scsi).is_ok()
|
||||
};
|
||||
if ok { unlocked = true; break; }
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.unlocked && self.calibrated
|
||||
}
|
||||
}
|
||||
|
||||
impl Mt1959 {
|
||||
fn unlock(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
self.do_unlock(scsi)?;
|
||||
}
|
||||
if !unlocked {
|
||||
return Err(Error::UnlockFailed { detail: "failed after 6 attempts".into() });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_firmware(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
if self.profile.firmware.is_empty() {
|
||||
return Err(Error::UnlockFailed {
|
||||
detail: "no firmware in profile".into(),
|
||||
});
|
||||
}
|
||||
// ── Calibrate (disc surface probes) ────────────────────────────────
|
||||
|
||||
if self.mode == MODE_A {
|
||||
self.load_firmware_a(scsi)
|
||||
} else {
|
||||
self.load_firmware_b(scsi)
|
||||
}
|
||||
}
|
||||
|
||||
fn calibrate(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
fn run_calibrate(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
if !self.unlocked { self.do_unlock(scsi)?; }
|
||||
|
||||
let cap_cdb = [0x25u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
@@ -175,7 +159,7 @@ impl Mt1959 {
|
||||
self.disc_sectors = u32::from_be_bytes([cap_buf[0], cap_buf[1], cap_buf[2], cap_buf[3]]) + 1;
|
||||
}
|
||||
|
||||
let init_addr: u16 = 0x0100; // TODO: detect disc type (0x0200 for UHD)
|
||||
let init_addr: u16 = 0x0100;
|
||||
let mut init_resp = [0u8; 4];
|
||||
let _ = self.read_buffer_probe(scsi, 0x12, init_addr, &mut init_resp, 4);
|
||||
|
||||
@@ -198,9 +182,7 @@ impl Mt1959 {
|
||||
self.calibration_config = [0u8; 4];
|
||||
return Err(Error::ScsiError { opcode: 0x3C, status: 0xFF, sense_key: 0 });
|
||||
}
|
||||
if resp[0] != prev_speed {
|
||||
prev_speed = resp[0];
|
||||
}
|
||||
if resp[0] != prev_speed { prev_speed = resp[0]; }
|
||||
addr = addr.wrapping_add(0x100);
|
||||
}
|
||||
|
||||
@@ -232,135 +214,36 @@ impl Mt1959 {
|
||||
self.calibrated = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()> {
|
||||
if !self.calibrated {
|
||||
return Ok(());
|
||||
// ── PlatformDriver trait ───────────────────────────────────────────────
|
||||
|
||||
impl PlatformDriver for Mt1959 {
|
||||
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
if self.unlocked { return Ok(()); }
|
||||
self.run_init(scsi)
|
||||
}
|
||||
|
||||
let mut best_idx: usize = 0;
|
||||
let mut best_diff: u32 = 0x10000000;
|
||||
let mut found = false;
|
||||
fn read_speed_table(&mut self, scsi: &mut dyn ScsiTransport, speed_table: &mut SpeedTable) -> Result<()> {
|
||||
if !self.unlocked { self.run_init(scsi)?; }
|
||||
if self.calibrated { return Ok(()); }
|
||||
self.run_calibrate(scsi)?;
|
||||
|
||||
let mut probes: Vec<(u16, u8)> = Vec::new();
|
||||
for i in 0..64 {
|
||||
let entry = self.speed_table[i] as u32;
|
||||
if entry == 0 { continue; }
|
||||
let diff = if lba > entry { lba - entry } else { entry - lba };
|
||||
if diff < best_diff {
|
||||
best_diff = diff;
|
||||
best_idx = i;
|
||||
found = true;
|
||||
let addr = self.speed_table[i];
|
||||
if addr == 0 { continue; }
|
||||
let speed_idx = ((i + 1) << 1) as u8;
|
||||
probes.push((addr, speed_idx));
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let speed_val = self.speed_table[best_idx];
|
||||
let probe_addr = 0x0100 | (speed_val.swap_bytes() as u16);
|
||||
let mut probe_resp = [0u8; 4];
|
||||
let _ = self.read_buffer_probe(scsi, 0x14, probe_addr, &mut probe_resp, 4);
|
||||
|
||||
let _ = self.set_cd_speed_max(scsi);
|
||||
let _ = self.set_cd_speed(scsi, speed_val);
|
||||
const PROBE_RANGE: u32 = 0x10000;
|
||||
const BD_1X_KBS: u16 = 4500;
|
||||
speed_table.load_calibration(self.disc_sectors, &probes, PROBE_RANGE, BD_1X_KBS);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let mut unlocked = false;
|
||||
for _attempt in 0..6 {
|
||||
match self.unlock(scsi) {
|
||||
Ok(_) => { unlocked = true; break; }
|
||||
Err(Error::SignatureMismatch { .. }) => {
|
||||
return Err(Error::UnlockFailed {
|
||||
detail: "signature mismatch — wrong profile for this drive".into(),
|
||||
});
|
||||
}
|
||||
Err(_) => {
|
||||
if self.load_firmware(scsi).is_ok() {
|
||||
unlocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !unlocked {
|
||||
return Err(Error::UnlockFailed {
|
||||
detail: "failed after 6 attempts".into(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut calibrated = false;
|
||||
for _attempt in 0..6 {
|
||||
if self.calibrate(scsi).is_ok() {
|
||||
calibrated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !calibrated {
|
||||
return Err(Error::ScsiError { opcode: 0x3C, status: 0xFF, sense_key: 0 });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mt1959 {
|
||||
fn load_firmware_a(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let firmware = &self.profile.firmware;
|
||||
let len = firmware.len();
|
||||
|
||||
let cdb = [
|
||||
0x3B, 0x06, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
(len >> 16) as u8, (len >> 8) as u8, len as u8,
|
||||
0x00,
|
||||
];
|
||||
let mut data = firmware.clone();
|
||||
scsi.execute(&cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||||
|
||||
let verify_cdb = [0x3C, 0x01, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00];
|
||||
let mut verify_resp = [0u8; 4];
|
||||
let _ = scsi.execute(&verify_cdb, DataDirection::FromDevice, &mut verify_resp, 5_000);
|
||||
|
||||
self.do_unlock(scsi)?;
|
||||
self.do_unlock(scsi)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_firmware_b(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let firmware = &self.profile.firmware;
|
||||
|
||||
let write_len = 0x9C0usize.min(firmware.len());
|
||||
let mode_select_cdb = [
|
||||
0x55, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
(write_len >> 16) as u8, (write_len >> 8) as u8, write_len as u8,
|
||||
0x00,
|
||||
];
|
||||
let mut data = firmware[..write_len].to_vec();
|
||||
scsi.execute(&mode_select_cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||||
|
||||
let read_meta_cdb = [0x3C, 0x06, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||
let mut meta_resp = [0u8; 16];
|
||||
let _ = scsi.execute(&read_meta_cdb, DataDirection::FromDevice, &mut meta_resp, 5_000);
|
||||
|
||||
let write2_cdb = [0x3B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||
let mut data2 = FIRMWARE_EXTRA_B.to_vec();
|
||||
let _ = scsi.execute(&write2_cdb, DataDirection::ToDevice, &mut data2, 5_000);
|
||||
|
||||
let mut dummy = [0u8; 0];
|
||||
let _ = scsi.execute(&VERIFY_COMMAND_B, DataDirection::None, &mut dummy, 5_000);
|
||||
|
||||
for _attempt in 0..5 {
|
||||
if self.do_unlock(scsi).is_ok() {
|
||||
let _ = self.do_unlock(scsi);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.do_unlock(scsi)?;
|
||||
Ok(())
|
||||
fn is_ready(&self) -> bool {
|
||||
self.unlocked
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//! MT1959 variant A firmware upload.
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::scsi::{DataDirection, ScsiTransport};
|
||||
use super::Mt1959;
|
||||
|
||||
pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let firmware = &mt.profile.firmware;
|
||||
if firmware.is_empty() {
|
||||
return Err(crate::error::Error::UnlockFailed {
|
||||
detail: "no firmware in profile".into(),
|
||||
});
|
||||
}
|
||||
|
||||
let len = firmware.len();
|
||||
let cdb = [
|
||||
0x3B, 0x06, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
(len >> 16) as u8, (len >> 8) as u8, len as u8,
|
||||
0x00,
|
||||
];
|
||||
let mut data = firmware.clone();
|
||||
scsi.execute(&cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||||
|
||||
// Verify (may fail, non-fatal)
|
||||
let verify_cdb = [0x3C, 0x01, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00];
|
||||
let mut verify_resp = [0u8; 4];
|
||||
let _ = scsi.execute(&verify_cdb, DataDirection::FromDevice, &mut verify_resp, 5_000);
|
||||
|
||||
mt.do_unlock(scsi)?;
|
||||
mt.do_unlock(scsi)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! MT1959 variant B firmware upload.
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::scsi::{DataDirection, ScsiTransport};
|
||||
use super::Mt1959;
|
||||
|
||||
const FIRMWARE_EXTRA: [u8; 16] = [0; 16];
|
||||
const VERIFY_COMMAND: [u8; 10] = [0xF1, 0x01, 0x02, 0x00, 0x0D, 0x30, 0x01, 0xF3, 0xAD, 0x23];
|
||||
|
||||
pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
let firmware = &mt.profile.firmware;
|
||||
if firmware.is_empty() {
|
||||
return Err(crate::error::Error::UnlockFailed {
|
||||
detail: "no firmware in profile".into(),
|
||||
});
|
||||
}
|
||||
|
||||
// Step 1: MODE SELECT with firmware payload
|
||||
let write_len = 0x9C0usize.min(firmware.len());
|
||||
let mode_select_cdb = [
|
||||
0x55, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
(write_len >> 16) as u8, (write_len >> 8) as u8, write_len as u8,
|
||||
0x00,
|
||||
];
|
||||
let mut data = firmware[..write_len].to_vec();
|
||||
scsi.execute(&mode_select_cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||||
|
||||
// Step 2: Read firmware metadata
|
||||
let read_meta_cdb = [0x3C, 0x06, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||
let mut meta_resp = [0u8; 16];
|
||||
let _ = scsi.execute(&read_meta_cdb, DataDirection::FromDevice, &mut meta_resp, 5_000);
|
||||
|
||||
// Step 3: Write extra firmware data
|
||||
let write2_cdb = [0x3B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||
let mut data2 = FIRMWARE_EXTRA.to_vec();
|
||||
let _ = scsi.execute(&write2_cdb, DataDirection::ToDevice, &mut data2, 5_000);
|
||||
|
||||
// Step 4: Vendor verify
|
||||
let mut dummy = [0u8; 0];
|
||||
let _ = scsi.execute(&VERIFY_COMMAND, DataDirection::None, &mut dummy, 5_000);
|
||||
|
||||
// Step 5: Unlock retries
|
||||
for _attempt in 0..5 {
|
||||
if mt.do_unlock(scsi).is_ok() {
|
||||
let _ = mt.do_unlock(scsi);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
mt.do_unlock(scsi)?;
|
||||
Ok(())
|
||||
}
|
||||
+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