Remove SpeedTable, add probe_disc(), named constants, clean architecture
- Removed SpeedTable entirely — drive manages speeds after probe - Renamed read_speed_table() → probe_disc() - Named all SCSI constants: SUB_CMD_UNLOCK, SUB_CMD_INIT, SUB_CMD_PROBE, INIT_ADDR_BD, INIT_ADDR_UHD, PROBE_COARSE_END, PROBE_FINE_END, etc. - Auto-detect BD vs UHD from disc capacity for correct probe init address - Fixed NOMINAL_SPEED_B (was invalid CDB, removed — single max instead) - Added session.set_speed() for simple speed control - Error recovery: re-init on first error, BD2x on repeated errors - Batch size uses full kernel limit (was 80%, now 100%) - Clean variant_a/variant_b with named constants API: open() → wait_ready() → init() → probe_disc() → scan() → read
This commit is contained in:
@@ -29,8 +29,9 @@ use std::path::Path;
|
|||||||
|
|
||||||
// Open drive — profiles are bundled, auto-identified
|
// Open drive — profiles are bundled, auto-identified
|
||||||
let mut session = DriveSession::open(Path::new("/dev/sr0"))?;
|
let mut session = DriveSession::open(Path::new("/dev/sr0"))?;
|
||||||
session.wait_ready()?; // wait for disc
|
session.wait_ready()?; // wait for disc
|
||||||
session.init()?; // optional: unlock + calibrate for full speed
|
session.init()?; // unlock + firmware upload
|
||||||
|
session.probe_disc()?; // probe disc surface for optimal speeds
|
||||||
|
|
||||||
// Scan disc — UDF, playlists, streams, AACS (all automatic)
|
// Scan disc — UDF, playlists, streams, AACS (all automatic)
|
||||||
let disc = Disc::scan(&mut session, &ScanOptions::default())?;
|
let disc = Disc::scan(&mut session, &ScanOptions::default())?;
|
||||||
|
|||||||
+21
-27
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::drive::DriveSession;
|
use crate::drive::DriveSession;
|
||||||
|
use crate::speed::DriveSpeed;
|
||||||
use crate::udf;
|
use crate::udf;
|
||||||
use crate::mpls;
|
use crate::mpls;
|
||||||
use crate::clpi;
|
use crate::clpi;
|
||||||
@@ -853,10 +854,8 @@ fn detect_max_batch_sectors(device_path: &str) -> u16 {
|
|||||||
if let Ok(kb) = content.trim().parse::<u32>() {
|
if let Ok(kb) = content.trim().parse::<u32>() {
|
||||||
// Convert KB to sectors (1 sector = 2 KB = 2048 bytes)
|
// Convert KB to sectors (1 sector = 2 KB = 2048 bytes)
|
||||||
let sectors = (kb / 2) as u16;
|
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)
|
// Align down to 3 (one aligned unit)
|
||||||
let aligned = (safe / 3) * 3;
|
let aligned = (sectors / 3) * 3;
|
||||||
if aligned >= MIN_BATCH_SECTORS {
|
if aligned >= MIN_BATCH_SECTORS {
|
||||||
return aligned.min(MAX_BATCH_SECTORS);
|
return aligned.min(MAX_BATCH_SECTORS);
|
||||||
}
|
}
|
||||||
@@ -869,7 +868,7 @@ fn detect_max_batch_sectors(device_path: &str) -> u16 {
|
|||||||
|
|
||||||
/// Read strategy constants
|
/// Read strategy constants
|
||||||
const MAX_BATCH_SECTORS: u16 = 510; // absolute max (170 aligned units ≈ 1MB)
|
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 MIN_BATCH_SECTORS: u16 = 3; // 1 aligned unit = 6KB (error recovery)
|
||||||
const RAMP_BATCH_AFTER: u32 = 5; // successes before doubling batch size
|
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 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.
|
/// Read a batch of sectors into the internal buffer.
|
||||||
///
|
///
|
||||||
/// Speed management: speed table checked before each read.
|
/// Error handling:
|
||||||
/// On error: reduce speed, halve batch. On recovery: resume from table.
|
/// - 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<bool> {
|
fn fill_buffer(&mut self) -> Result<bool> {
|
||||||
loop {
|
loop {
|
||||||
if self.current_extent >= self.extents.len() {
|
if self.current_extent >= self.extents.len() {
|
||||||
@@ -987,15 +989,6 @@ impl<'a> ContentReader<'a> {
|
|||||||
let byte_count = sectors_to_read as usize * 2048;
|
let byte_count = sectors_to_read as usize * 2048;
|
||||||
self.read_buf.resize(byte_count, 0);
|
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) {
|
match self.read_sectors(lba, sectors_to_read) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.buf_len = sectors_to_read as usize / 3;
|
self.buf_len = sectors_to_read as usize / 3;
|
||||||
@@ -1015,9 +1008,10 @@ impl<'a> ContentReader<'a> {
|
|||||||
self.ok_streak = 0;
|
self.ok_streak = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resume table-driven speed after sustained success
|
// Restore max speed after sustained success at full batch
|
||||||
if self.error_streak == 0 && self.ok_streak >= RAMP_SPEED_AFTER {
|
if self.batch_sectors == self.max_batch_sectors && self.ok_streak >= RAMP_SPEED_AFTER {
|
||||||
self.session.speed_table.resume(lba);
|
self.session.set_speed(0xFFFF);
|
||||||
|
self.ok_streak = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
@@ -1027,19 +1021,19 @@ impl<'a> ContentReader<'a> {
|
|||||||
self.error_streak += 1;
|
self.error_streak += 1;
|
||||||
self.ok_streak = 0;
|
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 {
|
if self.error_streak >= SLOW_SPEED_AFTER {
|
||||||
let speed = self.session.speed_table.reduce();
|
self.session.set_speed(DriveSpeed::BD2x.to_kbps());
|
||||||
let cdb = crate::scsi::build_set_cd_speed(speed);
|
self.error_streak = 0;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.batch_sectors > MIN_BATCH_SECTORS {
|
if self.batch_sectors > MIN_BATCH_SECTORS {
|
||||||
// Shrink batch and retry
|
|
||||||
self.batch_sectors = (self.batch_sectors / 2).max(MIN_BATCH_SECTORS);
|
self.batch_sectors = (self.batch_sectors / 2).max(MIN_BATCH_SECTORS);
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+15
-10
@@ -3,21 +3,20 @@
|
|||||||
//! Three-step open:
|
//! Three-step open:
|
||||||
//! 1. `open()` — open device, identify drive. Always OEM.
|
//! 1. `open()` — open device, identify drive. Always OEM.
|
||||||
//! 2. `wait_ready()` — wait for disc to spin up. Call before reading.
|
//! 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 std::path::Path;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::scsi::ScsiTransport;
|
use crate::scsi::ScsiTransport;
|
||||||
use crate::identity::DriveId;
|
use crate::identity::DriveId;
|
||||||
use crate::profile::{self, DriveProfile, ProfileMatch};
|
use crate::profile::{self, DriveProfile};
|
||||||
use crate::platform::PlatformDriver;
|
use crate::platform::PlatformDriver;
|
||||||
use crate::platform::mt1959::Mt1959;
|
use crate::platform::mt1959::Mt1959;
|
||||||
use crate::speed::SpeedTable;
|
|
||||||
|
|
||||||
pub struct DriveSession {
|
pub struct DriveSession {
|
||||||
scsi: Box<dyn ScsiTransport>,
|
scsi: Box<dyn ScsiTransport>,
|
||||||
driver: Box<dyn PlatformDriver>,
|
driver: Box<dyn PlatformDriver>,
|
||||||
pub speed_table: SpeedTable,
|
|
||||||
pub profile: DriveProfile,
|
pub profile: DriveProfile,
|
||||||
pub platform: profile::Platform,
|
pub platform: profile::Platform,
|
||||||
pub drive_id: DriveId,
|
pub drive_id: DriveId,
|
||||||
@@ -42,7 +41,6 @@ impl DriveSession {
|
|||||||
Ok(DriveSession {
|
Ok(DriveSession {
|
||||||
scsi: transport,
|
scsi: transport,
|
||||||
driver,
|
driver,
|
||||||
speed_table: SpeedTable::new(),
|
|
||||||
platform: m.platform,
|
platform: m.platform,
|
||||||
profile: m.profile,
|
profile: m.profile,
|
||||||
drive_id,
|
drive_id,
|
||||||
@@ -74,15 +72,16 @@ impl DriveSession {
|
|||||||
&self.device_path
|
&self.device_path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize drive — unlock + firmware upload.
|
/// Initialize drive — unlock + firmware upload. Removes riplock.
|
||||||
pub fn init(&mut self) -> Result<()> {
|
pub fn init(&mut self) -> Result<()> {
|
||||||
self.driver.init(self.scsi.as_mut())
|
self.driver.init(self.scsi.as_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read speed zones from disc into speed table.
|
/// Probe disc surface so the drive firmware learns optimal read speeds
|
||||||
/// Requires init() first. Optional — without this, drive manages speed itself.
|
/// per region. After this the host reads at max speed and the drive
|
||||||
pub fn read_speed_table(&mut self) -> Result<()> {
|
/// manages zones internally.
|
||||||
self.driver.read_speed_table(self.scsi.as_mut(), &mut self.speed_table)
|
pub fn probe_disc(&mut self) -> Result<()> {
|
||||||
|
self.driver.probe_disc(self.scsi.as_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_ready(&self) -> bool {
|
pub fn is_ready(&self) -> bool {
|
||||||
@@ -111,6 +110,12 @@ impl DriveSession {
|
|||||||
Ok(result.bytes_transferred)
|
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<()> {
|
pub fn eject(&mut self) -> Result<()> {
|
||||||
let allow_cdb = [0x1Eu8, 0, 0, 0, 0x00, 0];
|
let allow_cdb = [0x1Eu8, 0, 0, 0, 0x00, 0];
|
||||||
let mut buf = [0u8; 0];
|
let mut buf = [0u8; 0];
|
||||||
|
|||||||
+6
-4
@@ -1,17 +1,19 @@
|
|||||||
//! Platform-specific drive initialization and calibration.
|
//! Platform-specific drive initialization and disc probing.
|
||||||
|
|
||||||
pub mod mt1959;
|
pub mod mt1959;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::scsi::ScsiTransport;
|
use crate::scsi::ScsiTransport;
|
||||||
use crate::speed::SpeedTable;
|
|
||||||
|
|
||||||
pub(crate) trait PlatformDriver {
|
pub(crate) trait PlatformDriver {
|
||||||
/// Unlock drive + upload firmware if needed.
|
/// Unlock drive + upload firmware if needed.
|
||||||
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>;
|
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>;
|
||||||
|
|
||||||
/// Read speed zones from disc surface, fill speed table.
|
/// Calibrate drive for this disc. Probes disc surface so the drive's
|
||||||
fn read_speed_table(&mut self, scsi: &mut dyn ScsiTransport, speed_table: &mut SpeedTable) -> Result<()>;
|
/// 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().
|
/// True after successful init().
|
||||||
fn is_ready(&self) -> bool;
|
fn is_ready(&self) -> bool;
|
||||||
|
|||||||
+74
-82
@@ -6,26 +6,48 @@ mod variant_b;
|
|||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::profile::DriveProfile;
|
use crate::profile::DriveProfile;
|
||||||
use crate::scsi::{self, DataDirection, ScsiTransport};
|
use crate::scsi::{self, DataDirection, ScsiTransport};
|
||||||
use crate::speed::SpeedTable;
|
|
||||||
use super::PlatformDriver;
|
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_A: u8 = 0x01;
|
||||||
const MODE_B: u8 = 0x02;
|
const MODE_B: u8 = 0x02;
|
||||||
const BUFFER_ID_A: u8 = 0x44;
|
const BUFFER_ID_A: u8 = 0x44;
|
||||||
const BUFFER_ID_B: u8 = 0x77;
|
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 struct Mt1959 {
|
||||||
pub(crate) profile: DriveProfile,
|
pub(crate) profile: DriveProfile,
|
||||||
pub(crate) mode: u8,
|
pub(crate) mode: u8,
|
||||||
pub(crate) buffer_id: u8,
|
pub(crate) buffer_id: u8,
|
||||||
pub(crate) unlocked: bool,
|
pub(crate) unlocked: bool,
|
||||||
speed_table: [u16; 64],
|
probed: bool,
|
||||||
disc_sectors: u32,
|
|
||||||
calibrated: bool,
|
|
||||||
calibration_config: [u8; 4],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mt1959 {
|
impl Mt1959 {
|
||||||
@@ -38,10 +60,7 @@ impl Mt1959 {
|
|||||||
Mt1959 {
|
Mt1959 {
|
||||||
profile, mode, buffer_id,
|
profile, mode, buffer_id,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
speed_table: [0u16; 64],
|
probed: false,
|
||||||
disc_sectors: 0,
|
|
||||||
calibrated: false,
|
|
||||||
calibration_config: [0u8; 4],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +68,7 @@ impl Mt1959 {
|
|||||||
|
|
||||||
pub(crate) fn read_buffer_sub(&self, sub_cmd: u8, address: u16, length: u8) -> [u8; 10] {
|
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,
|
(address >> 8) as u8, address as u8,
|
||||||
0x00, 0x00, length, 0x00,
|
0x00, 0x00, length, 0x00,
|
||||||
]
|
]
|
||||||
@@ -62,7 +81,7 @@ impl Mt1959 {
|
|||||||
let cdb = self.read_buffer_sub(sub_cmd, address, expected as u8);
|
let cdb = self.read_buffer_sub(sub_cmd, address, expected as u8);
|
||||||
let result = scsi.execute(&cdb, DataDirection::FromDevice, buf, 5_000)?;
|
let result = scsi.execute(&cdb, DataDirection::FromDevice, buf, 5_000)?;
|
||||||
if result.bytes_transferred != expected {
|
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)
|
Ok(result.bytes_transferred)
|
||||||
}
|
}
|
||||||
@@ -79,7 +98,7 @@ impl Mt1959 {
|
|||||||
pub(crate) 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 = [
|
let cdb = [
|
||||||
0x3C, self.mode, self.buffer_id,
|
0x3C, self.mode, self.buffer_id,
|
||||||
0x00, 0x00, 0x00,
|
SUB_CMD_UNLOCK, 0x00, 0x00,
|
||||||
0x00, 0x00, UNLOCK_RESPONSE_SIZE, 0x00,
|
0x00, 0x00, UNLOCK_RESPONSE_SIZE, 0x00,
|
||||||
];
|
];
|
||||||
let mut response = vec![0u8; UNLOCK_RESPONSE_SIZE as usize];
|
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 {
|
return Err(Error::UnlockFailed {
|
||||||
detail: format!(
|
detail: format!(
|
||||||
"mode not active: {:02x}{:02x}{:02x}{:02x}",
|
"mode not active: {:02x}{:02x}{:02x}{:02x}",
|
||||||
@@ -109,15 +129,15 @@ impl Mt1959 {
|
|||||||
for _attempt in 0..5 {
|
for _attempt in 0..5 {
|
||||||
let cdb = [
|
let cdb = [
|
||||||
0x3C, self.mode, self.buffer_id,
|
0x3C, self.mode, self.buffer_id,
|
||||||
0x00, 0x00, 0x00,
|
SUB_CMD_UNLOCK, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x04, 0x00,
|
0x00, 0x00, VALIDATE_RESPONSE_SIZE, 0x00,
|
||||||
];
|
];
|
||||||
let mut resp = [0u8; 4];
|
let mut resp = [0u8; 4];
|
||||||
if scsi.execute(&cdb, DataDirection::FromDevice, &mut resp, 5_000).is_ok() {
|
if scsi.execute(&cdb, DataDirection::FromDevice, &mut resp, 5_000).is_ok() {
|
||||||
return 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) ───────────────────────────────────────
|
// ── Init (unlock + firmware) ───────────────────────────────────────
|
||||||
@@ -148,70 +168,55 @@ impl Mt1959 {
|
|||||||
Ok(())
|
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)?; }
|
if !self.unlocked { self.do_unlock(scsi)?; }
|
||||||
|
|
||||||
let cap_cdb = [0x25u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
// Detect disc type from capacity to select probe mode.
|
||||||
let mut cap_buf = [0u8; 8];
|
// BD: 3C 01 44 12 01 00 00 00 04 00 (init_addr = 0x0100)
|
||||||
if scsi.execute(&cap_cdb, DataDirection::FromDevice, &mut cap_buf, 5_000).is_ok() {
|
// UHD: 3C 01 44 12 02 00 00 00 04 00 (init_addr = 0x0200)
|
||||||
self.disc_sectors = u32::from_be_bytes([cap_buf[0], cap_buf[1], cap_buf[2], cap_buf[3]]) + 1;
|
// 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 init_addr: u16 = 0x0100;
|
let disc_sectors = if scsi.execute(&cap_cdb, DataDirection::FromDevice, &mut cap_buf, 5_000).is_ok() {
|
||||||
let mut init_resp = [0u8; 4];
|
u32::from_be_bytes([cap_buf[0], cap_buf[1], cap_buf[2], cap_buf[3]]) + 1
|
||||||
let _ = self.read_buffer_probe(scsi, 0x12, init_addr, &mut init_resp, 4);
|
} 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.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 addr: u16 = 0;
|
||||||
let mut prev_speed = initial_speed;
|
while addr < PROBE_COARSE_END {
|
||||||
while addr < 0x5800 {
|
let mut resp = [0u8; PROBE_RESPONSE_SIZE as usize];
|
||||||
let mut resp = [0u8; 4];
|
if self.read_buffer_probe(scsi, SUB_CMD_PROBE, addr, &mut resp, PROBE_RESPONSE_SIZE as usize).is_err() {
|
||||||
if self.read_buffer_probe(scsi, 0x14, addr, &mut resp, 4).is_err() {
|
return Err(Error::ScsiError { opcode: SCSI_READ_BUFFER, status: 0xFF, sense_key: 0 });
|
||||||
self.speed_table = [0u16; 64];
|
|
||||||
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]; }
|
addr = addr.wrapping_add(PROBE_STEP);
|
||||||
addr = addr.wrapping_add(0x100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass 2: fine scan
|
||||||
let mut addr: u32 = 0;
|
let mut addr: u32 = 0;
|
||||||
let mut prev_speed: u8 = 0;
|
while addr < PROBE_FINE_END {
|
||||||
while addr < 0x10000 {
|
let mut resp = [0u8; PROBE_RESPONSE_SIZE as usize];
|
||||||
let mut resp = [0u8; 4];
|
if self.read_buffer_probe(scsi, SUB_CMD_PROBE, addr as u16, &mut resp, PROBE_RESPONSE_SIZE as usize).is_err() {
|
||||||
if self.read_buffer_probe(scsi, 0x14, addr as u16, &mut resp, 4).is_err() {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let speed = resp[0];
|
addr += PROBE_STEP as u32;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
self.calibration_config[3] = prev_speed;
|
|
||||||
|
|
||||||
let _ = self.set_cd_speed_max(scsi);
|
// Set max speed — drive manages zones from here
|
||||||
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);
|
|
||||||
let _ = self.set_cd_speed_max(scsi);
|
let _ = self.set_cd_speed_max(scsi);
|
||||||
|
|
||||||
self.calibrated = true;
|
self.probed = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,23 +229,10 @@ impl PlatformDriver for Mt1959 {
|
|||||||
self.run_init(scsi)
|
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.unlocked { self.run_init(scsi)?; }
|
||||||
if self.calibrated { return Ok(()); }
|
if self.probed { return Ok(()); }
|
||||||
self.run_calibrate(scsi)?;
|
self.run_probe(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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ready(&self) -> bool {
|
fn is_ready(&self) -> bool {
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
//! MT1959 variant A firmware upload.
|
//! MT1959 variant A firmware upload.
|
||||||
|
//!
|
||||||
|
//! WRITE_BUFFER (0x3B) → verify READ_BUFFER (0x45) → unlock × 2
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::scsi::{DataDirection, ScsiTransport};
|
use crate::scsi::{DataDirection, ScsiTransport};
|
||||||
use super::Mt1959;
|
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<()> {
|
pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||||
let firmware = &mt.profile.firmware;
|
let firmware = &mt.profile.firmware;
|
||||||
if firmware.is_empty() {
|
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 len = firmware.len();
|
||||||
let cdb = [
|
let cdb = [
|
||||||
0x3B, 0x06, 0x00,
|
SCSI_WRITE_BUFFER, 0x06, 0x00,
|
||||||
0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00,
|
||||||
(len >> 16) as u8, (len >> 8) as u8, len as u8,
|
(len >> 16) as u8, (len >> 8) as u8, len as u8,
|
||||||
0x00,
|
0x00,
|
||||||
@@ -22,11 +28,12 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re
|
|||||||
let mut data = firmware.clone();
|
let mut data = firmware.clone();
|
||||||
scsi.execute(&cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
scsi.execute(&cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||||||
|
|
||||||
// Verify (may fail, non-fatal)
|
// Verify firmware loaded (non-fatal — different buffer_id 0x45)
|
||||||
let verify_cdb = [0x3C, 0x01, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00];
|
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; 4];
|
let mut verify_resp = [0u8; super::VALIDATE_RESPONSE_SIZE as usize];
|
||||||
let _ = scsi.execute(&verify_cdb, DataDirection::FromDevice, &mut verify_resp, 5_000);
|
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)?;
|
||||||
mt.do_unlock(scsi)?;
|
mt.do_unlock(scsi)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
//! MT1959 variant B firmware upload.
|
//! MT1959 variant B firmware upload.
|
||||||
|
//!
|
||||||
|
//! MODE SELECT (0x55) → read metadata → WRITE_BUFFER → vendor verify (0xF1) → unlock × 5+1
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::scsi::{DataDirection, ScsiTransport};
|
use crate::scsi::{DataDirection, ScsiTransport};
|
||||||
use super::Mt1959;
|
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 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<()> {
|
pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||||
let firmware = &mt.profile.firmware;
|
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
|
// Step 1: Upload firmware via MODE SELECT
|
||||||
let write_len = 0x9C0usize.min(firmware.len());
|
let write_len = FIRMWARE_MAX_SIZE.min(firmware.len());
|
||||||
let mode_select_cdb = [
|
let mode_select_cdb = [
|
||||||
0x55, 0x10, 0x00,
|
SCSI_MODE_SELECT, 0x10, 0x00,
|
||||||
0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00,
|
||||||
(write_len >> 16) as u8, (write_len >> 8) as u8, write_len as u8,
|
(write_len >> 16) as u8, (write_len >> 8) as u8, write_len as u8,
|
||||||
0x00,
|
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();
|
let mut data = firmware[..write_len].to_vec();
|
||||||
scsi.execute(&mode_select_cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
scsi.execute(&mode_select_cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||||||
|
|
||||||
// Step 2: Read firmware metadata
|
// Step 2: Read firmware metadata (READ_BUFFER mode 6, offset 0x3000)
|
||||||
let read_meta_cdb = [0x3C, 0x06, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00];
|
let read_meta_cdb = [SCSI_READ_BUFFER, 0x06, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||||
let mut meta_resp = [0u8; 16];
|
let mut meta_resp = [0u8; 16];
|
||||||
let _ = scsi.execute(&read_meta_cdb, DataDirection::FromDevice, &mut meta_resp, 5_000);
|
let _ = scsi.execute(&read_meta_cdb, DataDirection::FromDevice, &mut meta_resp, 5_000);
|
||||||
|
|
||||||
// Step 3: Write extra firmware data
|
// Step 3: Write extra firmware data (all zeros)
|
||||||
let write2_cdb = [0x3B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
|
let write_extra_cdb = [SCSI_WRITE_BUFFER, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||||
let mut data2 = FIRMWARE_EXTRA.to_vec();
|
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 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 {
|
for _attempt in 0..5 {
|
||||||
if mt.do_unlock(scsi).is_ok() {
|
if mt.do_unlock(scsi).is_ok() {
|
||||||
let _ = mt.do_unlock(scsi);
|
let _ = mt.do_unlock(scsi);
|
||||||
|
|||||||
+2
-129
@@ -1,133 +1,6 @@
|
|||||||
//! Drive speed management — zone-based speed table.
|
//! Drive speed constants.
|
||||||
//!
|
|
||||||
//! 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.
|
|
||||||
|
|
||||||
/// Speed table — maps disc positions to optimal read speeds.
|
/// Common optical drive speeds with KB/s values for SET_CD_SPEED.
|
||||||
#[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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum DriveSpeed {
|
pub enum DriveSpeed {
|
||||||
BD1x, BD2x, BD4x, BD6x, BD8x, BD10x, BD12x,
|
BD1x, BD2x, BD4x, BD6x, BD8x, BD10x, BD12x,
|
||||||
|
|||||||
Reference in New Issue
Block a user