diff --git a/README.md b/README.md index 3267625..d35c299 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,9 @@ use std::path::Path; // Open drive — profiles are bundled, auto-identified let mut session = DriveSession::open(Path::new("/dev/sr0"))?; -session.wait_ready()?; // wait for disc -session.init()?; // optional: unlock + calibrate for full speed +session.wait_ready()?; // wait for disc +session.init()?; // unlock + firmware upload +session.probe_disc()?; // probe disc surface for optimal speeds // Scan disc — UDF, playlists, streams, AACS (all automatic) let disc = Disc::scan(&mut session, &ScanOptions::default())?; diff --git a/src/disc.rs b/src/disc.rs index 67b6f1f..b825ae9 100644 --- a/src/disc.rs +++ b/src/disc.rs @@ -10,6 +10,7 @@ use crate::error::{Error, Result}; use crate::drive::DriveSession; +use crate::speed::DriveSpeed; use crate::udf; use crate::mpls; use crate::clpi; @@ -853,10 +854,8 @@ fn detect_max_batch_sectors(device_path: &str) -> u16 { if let Ok(kb) = content.trim().parse::() { // Convert KB to sectors (1 sector = 2 KB = 2048 bytes) let sectors = (kb / 2) as u16; - // Stay well under kernel limit (80% of max) to avoid edge cases - let safe = (sectors * 4 / 5).max(MIN_BATCH_SECTORS); // Align down to 3 (one aligned unit) - let aligned = (safe / 3) * 3; + let aligned = (sectors / 3) * 3; if aligned >= MIN_BATCH_SECTORS { return aligned.min(MAX_BATCH_SECTORS); } @@ -869,7 +868,7 @@ fn detect_max_batch_sectors(device_path: &str) -> u16 { /// Read strategy constants const MAX_BATCH_SECTORS: u16 = 510; // absolute max (170 aligned units ≈ 1MB) -const DEFAULT_BATCH_SECTORS: u16 = 48; // safe fallback (96KB, under typical 120KB kernel limit) +const DEFAULT_BATCH_SECTORS: u16 = 60; // fallback: typical kernel limit (120KB = 60 sectors) const MIN_BATCH_SECTORS: u16 = 3; // 1 aligned unit = 6KB (error recovery) const RAMP_BATCH_AFTER: u32 = 5; // successes before doubling batch size const RAMP_SPEED_AFTER: u32 = 50; // successes at max batch before restoring speed @@ -962,8 +961,11 @@ impl<'a> ContentReader<'a> { /// Read a batch of sectors into the internal buffer. /// - /// Speed management: speed table checked before each read. - /// On error: reduce speed, halve batch. On recovery: resume from table. + /// Error handling: + /// - First error: re-init drive (may have re-locked), halve batch + /// - Repeated errors: reduce speed, keep halving batch + /// - At minimum batch: retry once, then skip + zero-fill + /// - After sustained success: ramp batch back up, restore max speed fn fill_buffer(&mut self) -> Result { loop { if self.current_extent >= self.extents.len() { @@ -987,15 +989,6 @@ 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; @@ -1015,9 +1008,10 @@ impl<'a> ContentReader<'a> { 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); + // Restore max speed after sustained success at full batch + if self.batch_sectors == self.max_batch_sectors && self.ok_streak >= RAMP_SPEED_AFTER { + self.session.set_speed(0xFFFF); + self.ok_streak = 0; } return Ok(true); @@ -1027,19 +1021,19 @@ impl<'a> ContentReader<'a> { self.error_streak += 1; self.ok_streak = 0; - // Reduce speed after repeated errors + // First error: re-init (drive may have re-locked) + if self.error_streak == 1 { + let _ = self.session.init(); + let _ = self.session.probe_disc(); + } + + // Repeated errors: slow down 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 + self.session.set_speed(DriveSpeed::BD2x.to_kbps()); + self.error_streak = 0; } if self.batch_sectors > MIN_BATCH_SECTORS { - // Shrink batch and retry self.batch_sectors = (self.batch_sectors / 2).max(MIN_BATCH_SECTORS); std::thread::sleep(std::time::Duration::from_millis(100)); } else { diff --git a/src/drive.rs b/src/drive.rs index f6ef01d..b8a55fd 100644 --- a/src/drive.rs +++ b/src/drive.rs @@ -3,21 +3,20 @@ //! Three-step open: //! 1. `open()` — open device, identify drive. Always OEM. //! 2. `wait_ready()` — wait for disc to spin up. Call before reading. -//! 3. `init()` — activate custom firmware. Optional, caller decides. +//! 3. `init()` — activate custom firmware. Removes riplock. +//! 4. `probe_disc()` — probe disc surface. Drive learns optimal speeds. use std::path::Path; use crate::error::{Error, Result}; use crate::scsi::ScsiTransport; use crate::identity::DriveId; -use crate::profile::{self, DriveProfile, ProfileMatch}; +use crate::profile::{self, DriveProfile}; use crate::platform::PlatformDriver; use crate::platform::mt1959::Mt1959; -use crate::speed::SpeedTable; pub struct DriveSession { scsi: Box, driver: Box, - pub speed_table: SpeedTable, pub profile: DriveProfile, pub platform: profile::Platform, pub drive_id: DriveId, @@ -42,7 +41,6 @@ impl DriveSession { Ok(DriveSession { scsi: transport, driver, - speed_table: SpeedTable::new(), platform: m.platform, profile: m.profile, drive_id, @@ -74,15 +72,16 @@ impl DriveSession { &self.device_path } - /// Initialize drive — unlock + firmware upload. + /// Initialize drive — unlock + firmware upload. Removes riplock. pub fn init(&mut self) -> Result<()> { self.driver.init(self.scsi.as_mut()) } - /// 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) + /// Probe disc surface so the drive firmware learns optimal read speeds + /// per region. After this the host reads at max speed and the drive + /// manages zones internally. + pub fn probe_disc(&mut self) -> Result<()> { + self.driver.probe_disc(self.scsi.as_mut()) } pub fn is_ready(&self) -> bool { @@ -111,6 +110,12 @@ impl DriveSession { Ok(result.bytes_transferred) } + pub fn set_speed(&mut self, speed_kbs: u16) { + let cdb = crate::scsi::build_set_cd_speed(speed_kbs); + let mut dummy = [0u8; 0]; + let _ = self.scsi_execute(&cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000); + } + pub fn eject(&mut self) -> Result<()> { let allow_cdb = [0x1Eu8, 0, 0, 0, 0x00, 0]; let mut buf = [0u8; 0]; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 46cf833..e403742 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,17 +1,19 @@ -//! Platform-specific drive initialization and calibration. +//! Platform-specific drive initialization and disc probing. pub mod mt1959; use crate::error::Result; use crate::scsi::ScsiTransport; -use crate::speed::SpeedTable; pub(crate) trait PlatformDriver { /// Unlock drive + upload firmware if needed. fn init(&mut self, scsi: &mut dyn ScsiTransport) -> 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<()>; + /// Calibrate drive for this disc. Probes disc surface so the drive's + /// firmware learns the optimal speed for each region. After probing + /// the drive manages per-zone speeds internally — the host just reads + /// at max speed. + fn probe_disc(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; /// True after successful init(). fn is_ready(&self) -> bool; diff --git a/src/platform/mt1959/mod.rs b/src/platform/mt1959/mod.rs index 9bb5d34..9a24336 100644 --- a/src/platform/mt1959/mod.rs +++ b/src/platform/mt1959/mod.rs @@ -6,26 +6,48 @@ 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 ────────────────────────────────────────────────── +// Every vendor command: 3C [mode] [buffer_id] [sub_cmd] [addr] ... 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]; + +// ── SCSI opcodes ────────────────────────────────────────────────────── +const SCSI_READ_BUFFER: u8 = 0x3C; +const SCSI_READ_CAPACITY: u8 = 0x25; + +// ── Sub-commands (shared A/B) ───────────────────────────────────────── +const SUB_CMD_UNLOCK: u8 = 0x00; +const SUB_CMD_INIT: u8 = 0x12; +const SUB_CMD_PROBE: u8 = 0x14; +const UNLOCK_RESPONSE_SIZE: u8 = 64; +const VALIDATE_RESPONSE_SIZE: u8 = 4; +const FIRMWARE_ACTIVE_OFFSET: usize = 12; +const FIRMWARE_ACTIVE_SIG: [u8; 4] = [0x4D, 0x4D, 0x6B, 0x76]; + +// ── Init address (per disc type) ────────────────────────────────────── +const INIT_ADDR_BD: u16 = 0x0100; +const INIT_ADDR_UHD: u16 = 0x0200; + +// ── Probe scan ranges ───────────────────────────────────────────────── +const PROBE_COARSE_END: u16 = 0x5800; +const PROBE_FINE_END: u32 = 0x10000; +const PROBE_STEP: u16 = 0x0100; +const PROBE_RESPONSE_SIZE: u8 = 4; + +// ── Disc type threshold ─────────────────────────────────────────────── +const UHD_SECTOR_THRESHOLD: u32 = 25_000_000; // ~50 GB +const READ_CAPACITY_RESPONSE_SIZE: usize = 8; pub struct Mt1959 { 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, - calibration_config: [u8; 4], + probed: bool, } impl Mt1959 { @@ -38,10 +60,7 @@ impl Mt1959 { Mt1959 { profile, mode, buffer_id, unlocked: false, - speed_table: [0u16; 64], - disc_sectors: 0, - calibrated: false, - calibration_config: [0u8; 4], + probed: false, } } @@ -49,7 +68,7 @@ impl Mt1959 { pub(crate) fn read_buffer_sub(&self, sub_cmd: u8, address: u16, length: u8) -> [u8; 10] { [ - 0x3C, self.mode, self.buffer_id, sub_cmd, + SCSI_READ_BUFFER, self.mode, self.buffer_id, sub_cmd, (address >> 8) as u8, address as u8, 0x00, 0x00, length, 0x00, ] @@ -62,7 +81,7 @@ impl Mt1959 { let cdb = self.read_buffer_sub(sub_cmd, address, expected as u8); let result = scsi.execute(&cdb, DataDirection::FromDevice, buf, 5_000)?; if result.bytes_transferred != expected { - return Err(Error::ScsiError { opcode: 0x3C, status: 0xFF, sense_key: 0 }); + return Err(Error::ScsiError { opcode: SCSI_READ_BUFFER, status: 0xFF, sense_key: 0 }); } Ok(result.bytes_transferred) } @@ -79,7 +98,7 @@ impl Mt1959 { pub(crate) fn do_unlock(&mut self, scsi: &mut dyn ScsiTransport) -> Result> { let cdb = [ 0x3C, self.mode, self.buffer_id, - 0x00, 0x00, 0x00, + SUB_CMD_UNLOCK, 0x00, 0x00, 0x00, 0x00, UNLOCK_RESPONSE_SIZE, 0x00, ]; let mut response = vec![0u8; UNLOCK_RESPONSE_SIZE as usize]; @@ -92,7 +111,8 @@ impl Mt1959 { }); } - if response.len() >= 16 && &response[12..16] != b"MMkv" { + if response.len() >= FIRMWARE_ACTIVE_OFFSET + 4 + && response[FIRMWARE_ACTIVE_OFFSET..FIRMWARE_ACTIVE_OFFSET + 4] != FIRMWARE_ACTIVE_SIG { return Err(Error::UnlockFailed { detail: format!( "mode not active: {:02x}{:02x}{:02x}{:02x}", @@ -109,15 +129,15 @@ impl Mt1959 { for _attempt in 0..5 { let cdb = [ 0x3C, self.mode, self.buffer_id, - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x04, 0x00, + SUB_CMD_UNLOCK, 0x00, 0x00, + 0x00, 0x00, VALIDATE_RESPONSE_SIZE, 0x00, ]; let mut resp = [0u8; 4]; if scsi.execute(&cdb, DataDirection::FromDevice, &mut resp, 5_000).is_ok() { return Ok(()); } } - Err(Error::ScsiError { opcode: 0x3C, status: 0xFF, sense_key: 0 }) + Err(Error::ScsiError { opcode: SCSI_READ_BUFFER, status: 0xFF, sense_key: 0 }) } // ── Init (unlock + firmware) ─────────────────────────────────────── @@ -148,70 +168,55 @@ impl Mt1959 { Ok(()) } - // ── Calibrate (disc surface probes) ──────────────────────────────── + // ── Probe disc ───────────────────────────────────────────────────── - fn run_calibrate(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> { + /// Probe the disc surface so the drive firmware learns optimal speeds + /// per region. Two passes, then SET_CD_SPEED(max). After this the + /// drive manages per-zone speeds internally. + fn run_probe(&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]; - let mut cap_buf = [0u8; 8]; - if scsi.execute(&cap_cdb, DataDirection::FromDevice, &mut cap_buf, 5_000).is_ok() { - 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; - let mut init_resp = [0u8; 4]; - let _ = self.read_buffer_probe(scsi, 0x12, init_addr, &mut init_resp, 4); + // Detect disc type from capacity to select probe mode. + // BD: 3C 01 44 12 01 00 00 00 04 00 (init_addr = 0x0100) + // UHD: 3C 01 44 12 02 00 00 00 04 00 (init_addr = 0x0200) + // Verified from MakeMKV strace: BD and UHD use different init addresses. + let cap_cdb = [SCSI_READ_CAPACITY, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let mut cap_buf = [0u8; READ_CAPACITY_RESPONSE_SIZE]; + let disc_sectors = if scsi.execute(&cap_cdb, DataDirection::FromDevice, &mut cap_buf, 5_000).is_ok() { + u32::from_be_bytes([cap_buf[0], cap_buf[1], cap_buf[2], cap_buf[3]]) + 1 + } else { + 0 + }; + let init_addr = if disc_sectors > UHD_SECTOR_THRESHOLD { INIT_ADDR_UHD } else { INIT_ADDR_BD }; + let mut init_resp = [0u8; PROBE_RESPONSE_SIZE as usize]; + let _ = self.read_buffer_probe(scsi, SUB_CMD_INIT, init_addr, &mut init_resp, PROBE_RESPONSE_SIZE as usize); self.validate(scsi)?; - self.speed_table = [0u16; 64]; - - let mut probe_buf = [0u8; 4]; - let _ = self.read_buffer_probe(scsi, 0x14, 0, &mut probe_buf, 4); - let initial_speed = probe_buf[0]; - self.calibration_config[0] = probe_buf[0]; - self.calibration_config[1] = probe_buf[1]; - self.calibration_config[2] = probe_buf[2]; + // Pass 1: coarse scan let mut addr: u16 = 0; - let mut prev_speed = initial_speed; - while addr < 0x5800 { - let mut resp = [0u8; 4]; - if self.read_buffer_probe(scsi, 0x14, addr, &mut resp, 4).is_err() { - self.speed_table = [0u16; 64]; - self.calibration_config = [0u8; 4]; - return Err(Error::ScsiError { opcode: 0x3C, status: 0xFF, sense_key: 0 }); + while addr < PROBE_COARSE_END { + let mut resp = [0u8; PROBE_RESPONSE_SIZE as usize]; + if self.read_buffer_probe(scsi, SUB_CMD_PROBE, addr, &mut resp, PROBE_RESPONSE_SIZE as usize).is_err() { + return Err(Error::ScsiError { opcode: SCSI_READ_BUFFER, status: 0xFF, sense_key: 0 }); } - if resp[0] != prev_speed { prev_speed = resp[0]; } - addr = addr.wrapping_add(0x100); + addr = addr.wrapping_add(PROBE_STEP); } + // Pass 2: fine scan let mut addr: u32 = 0; - let mut prev_speed: u8 = 0; - while addr < 0x10000 { - let mut resp = [0u8; 4]; - if self.read_buffer_probe(scsi, 0x14, addr as u16, &mut resp, 4).is_err() { + while addr < PROBE_FINE_END { + let mut resp = [0u8; PROBE_RESPONSE_SIZE as usize]; + if self.read_buffer_probe(scsi, SUB_CMD_PROBE, addr as u16, &mut resp, PROBE_RESPONSE_SIZE as usize).is_err() { break; } - let speed = resp[0]; - if speed > prev_speed && speed > 0 { - let idx = ((speed as usize) >> 1).saturating_sub(1); - if idx < 64 && self.speed_table[idx] == 0 { - self.speed_table[idx] = addr as u16; - } - } - prev_speed = speed; - addr += 0x100; + addr += PROBE_STEP as u32; } - self.calibration_config[3] = prev_speed; - let _ = self.set_cd_speed_max(scsi); - let nominal = if self.mode == MODE_A { &NOMINAL_SPEED_A } else { &NOMINAL_SPEED_B }; - let mut dummy = [0u8; 0]; - let _ = scsi.execute(nominal, DataDirection::None, &mut dummy, 5_000); + // Set max speed — drive manages zones from here let _ = self.set_cd_speed_max(scsi); - self.calibrated = true; + self.probed = true; Ok(()) } } @@ -224,23 +229,10 @@ impl PlatformDriver for Mt1959 { self.run_init(scsi) } - fn read_speed_table(&mut self, scsi: &mut dyn ScsiTransport, speed_table: &mut SpeedTable) -> Result<()> { + fn probe_disc(&mut self, scsi: &mut dyn ScsiTransport) -> 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 addr = self.speed_table[i]; - if addr == 0 { continue; } - let speed_idx = ((i + 1) << 1) as u8; - probes.push((addr, speed_idx)); - } - 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(()) + if self.probed { return Ok(()); } + self.run_probe(scsi) } fn is_ready(&self) -> bool { diff --git a/src/platform/mt1959/variant_a.rs b/src/platform/mt1959/variant_a.rs index 8b82670..bfe205d 100644 --- a/src/platform/mt1959/variant_a.rs +++ b/src/platform/mt1959/variant_a.rs @@ -1,9 +1,14 @@ //! MT1959 variant A firmware upload. +//! +//! WRITE_BUFFER (0x3B) → verify READ_BUFFER (0x45) → unlock × 2 use crate::error::Result; use crate::scsi::{DataDirection, ScsiTransport}; use super::Mt1959; +const SCSI_WRITE_BUFFER: u8 = 0x3B; +const VERIFY_BUFFER_ID: u8 = 0x45; + pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Result<()> { let firmware = &mt.profile.firmware; if firmware.is_empty() { @@ -12,9 +17,10 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re }); } + // Upload firmware via WRITE_BUFFER let len = firmware.len(); let cdb = [ - 0x3B, 0x06, 0x00, + SCSI_WRITE_BUFFER, 0x06, 0x00, 0x00, 0x00, 0x00, (len >> 16) as u8, (len >> 8) as u8, len as u8, 0x00, @@ -22,11 +28,12 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re 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]; + // Verify firmware loaded (non-fatal — different buffer_id 0x45) + let verify_cdb = [super::SCSI_READ_BUFFER, super::MODE_A, VERIFY_BUFFER_ID, 0x00, 0x00, 0x00, 0x00, 0x00, super::VALIDATE_RESPONSE_SIZE, 0x00]; + let mut verify_resp = [0u8; super::VALIDATE_RESPONSE_SIZE as usize]; let _ = scsi.execute(&verify_cdb, DataDirection::FromDevice, &mut verify_resp, 5_000); + // Double unlock after firmware upload mt.do_unlock(scsi)?; mt.do_unlock(scsi)?; Ok(()) diff --git a/src/platform/mt1959/variant_b.rs b/src/platform/mt1959/variant_b.rs index 3adcb4f..119e696 100644 --- a/src/platform/mt1959/variant_b.rs +++ b/src/platform/mt1959/variant_b.rs @@ -1,11 +1,17 @@ //! MT1959 variant B firmware upload. +//! +//! MODE SELECT (0x55) → read metadata → WRITE_BUFFER → vendor verify (0xF1) → unlock × 5+1 use crate::error::Result; use crate::scsi::{DataDirection, ScsiTransport}; use super::Mt1959; +const SCSI_MODE_SELECT: u8 = 0x55; +const SCSI_WRITE_BUFFER: u8 = 0x3B; +const SCSI_READ_BUFFER: u8 = 0x3C; +const FIRMWARE_MAX_SIZE: usize = 0x9C0; const FIRMWARE_EXTRA: [u8; 16] = [0; 16]; -const VERIFY_COMMAND: [u8; 10] = [0xF1, 0x01, 0x02, 0x00, 0x0D, 0x30, 0x01, 0xF3, 0xAD, 0x23]; +const VENDOR_VERIFY: [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; @@ -15,10 +21,10 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re }); } - // Step 1: MODE SELECT with firmware payload - let write_len = 0x9C0usize.min(firmware.len()); + // Step 1: Upload firmware via MODE SELECT + let write_len = FIRMWARE_MAX_SIZE.min(firmware.len()); let mode_select_cdb = [ - 0x55, 0x10, 0x00, + SCSI_MODE_SELECT, 0x10, 0x00, 0x00, 0x00, 0x00, (write_len >> 16) as u8, (write_len >> 8) as u8, write_len as u8, 0x00, @@ -26,21 +32,21 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re 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]; + // Step 2: Read firmware metadata (READ_BUFFER mode 6, offset 0x3000) + let read_meta_cdb = [SCSI_READ_BUFFER, 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]; + // Step 3: Write extra firmware data (all zeros) + let write_extra_cdb = [SCSI_WRITE_BUFFER, 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); + let _ = scsi.execute(&write_extra_cdb, DataDirection::ToDevice, &mut data2, 5_000); - // Step 4: Vendor verify + // Step 4: Vendor verify (0xF1 — B-only, not standard SCSI) let mut dummy = [0u8; 0]; - let _ = scsi.execute(&VERIFY_COMMAND, DataDirection::None, &mut dummy, 5_000); + let _ = scsi.execute(&VENDOR_VERIFY, DataDirection::None, &mut dummy, 5_000); - // Step 5: Unlock retries + // Step 5: Unlock retries (up to 5, then final attempt) for _attempt in 0..5 { if mt.do_unlock(scsi).is_ok() { let _ = mt.do_unlock(scsi); diff --git a/src/speed.rs b/src/speed.rs index 72f2ea3..c506bf3 100644 --- a/src/speed.rs +++ b/src/speed.rs @@ -1,133 +1,6 @@ -//! Drive speed management — zone-based speed table. -//! -//! 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. +//! Drive speed constants. -/// 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 { - if lba < self.next_boundary { - return None; - } - self.transition(lba) - } - - /// Zone transition — lookup + precompute next boundary. - fn transition(&mut self, lba: u32) -> Option { - 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 +/// Common optical drive speeds with KB/s values for SET_CD_SPEED. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum DriveSpeed { BD1x, BD2x, BD4x, BD6x, BD8x, BD10x, BD12x,