From 6456e24bb56850a7fbe376f427442b7240fec1aa Mon Sep 17 00:00:00 2001 From: MattJackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:41:57 -0700 Subject: [PATCH] Lock down Platform trait: pub(crate), only init/set_read_speed/is_ready MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Platform trait is no longer publicly exported. External code uses DriveSession only — cannot call unlock, load_firmware, calibrate directly. - Platform trait: pub(crate) with 3 methods only - All handlers are private methods on Mt1959 - DriveStatus moved to mt1959 internal struct - init() has guard: no re-init if already ready - set_read_speed() has guard: no-op if not calibrated - Removed open_unlocked() — open() is the only entry - Removed Platform and DriveStatus from public exports Prevents: out-of-sequence SCSI commands, double-init, wrong firmware writes. --- src/drive.rs | 8 ++-- src/lib.rs | 2 +- src/platform/mod.rs | 92 +++++++++++------------------------------- src/platform/mt1959.rs | 46 +++++++++++++++------ 4 files changed, 61 insertions(+), 87 deletions(-) diff --git a/src/drive.rs b/src/drive.rs index 241a9b1..eaefb5f 100644 --- a/src/drive.rs +++ b/src/drive.rs @@ -14,7 +14,7 @@ use crate::error::{Error, Result}; use crate::scsi::ScsiTransport; use crate::identity::DriveId; use crate::profile::{self, DriveProfile, Chipset}; -use crate::platform::{Platform, DriveStatus}; +use crate::platform::Platform; use crate::platform::mt1959::Mt1959; /// A drive session with identification, platform, and SCSI transport. @@ -116,9 +116,9 @@ impl DriveSession { self.platform.init(self.scsi.as_mut()) } - /// Check if raw disc access mode is active. - pub fn is_unlocked(&self) -> bool { - self.platform.is_unlocked() + /// Check if drive is initialized and ready for reads. + pub fn is_ready(&self) -> bool { + self.platform.is_ready() } /// Called per zone change during content reads. diff --git a/src/lib.rs b/src/lib.rs index d97a1d0..2563533 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,7 @@ pub use error::{Error, Result}; pub use drive::{DriveSession, find_drive, find_drives, resolve_device}; pub use identity::DriveId; pub use profile::{DriveProfile, Chipset}; -pub use platform::{Platform, DriveStatus}; +// Platform trait is pub(crate) — callers use DriveSession, not Platform directly pub use scsi::ScsiTransport; pub use speed::DriveSpeed; pub use disc::{Disc, DiscFormat, Title, Clip, Stream, VideoStream, AudioStream, SubtitleStream, diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 13eb92b..ac5efb9 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,81 +1,35 @@ -//! Platform-specific implementations of raw disc access commands. +//! Platform-specific drive initialization and speed management. //! -//! Each chipset family (MT1959, Pioneer) implements the Platform trait. -//! The trait methods correspond to the 10 command handlers in the per-drive +//! The Platform trait is minimal by design. Callers cannot access +//! individual handlers (unlock, firmware upload, calibrate) directly. +//! This prevents out-of-sequence operations that could damage drives. //! -//! x86 dispatch order (proven from code + hardware traces): -//! 1. unlock() — try activate raw mode -//! 2. load_firmware() — if unlock fails, write ld_microcode then retry -//! 3. calibrate() — probe disc zones, build speed table, triple SET_CD_SPEED -//! 5. read_register() — read hardware registers A and B (mid-rip, retried) -//! 6. status() — query feature flags -//! 7. probe() — parameterized register read -//! 8. set_read_speed()— per-zone SET_CD_SPEED during content reads -//! -//! The full init sequence is: -//! unlock → [load_firmware if fail] × 6 → calibrate × 6 → -//! drive_info → registers × 5 → status × 6 → probe +//! Pipeline: DriveSession::open() calls init() once. After that, +//! only set_read_speed() is available during reads. pub mod mt1959; use crate::error::Result; use crate::scsi::ScsiTransport; -pub trait Platform { - /// - /// Sends READ_BUFFER with drive-specific mode/buf_id. - /// Checks response against drive_signature and mode_active_magic ("MMkv"). - /// Returns Ok if mode already active (warm), Err if firmware needed (cold). - fn unlock(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; - - /// - /// Sends WRITE_BUFFER mode=6 with ld_microcode (1888 bytes). - /// Verifies with READ_BUFFER buf=0x45 (expects response == 2). - /// Then calls unlock() twice to activate mode. - /// Only called when unlock() fails (cold boot, firmware not in drive RAM). - fn load_firmware(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; - - /// - /// Sends pre-built hardware_register_a_cdb. Returns 16 bytes from - /// the 36-byte response at offset [4:20]. - fn read_register_a(&mut self, scsi: &mut dyn ScsiTransport) -> Result<[u8; 16]>; - - /// - /// Sends pre-built hardware_register_b_cdb. Returns 16 bytes from - /// the 36-byte response at offset [4:20]. - fn read_register_b(&mut self, scsi: &mut dyn ScsiTransport) -> Result<[u8; 16]>; - - /// - /// Probes disc zones via READ_BUFFER sub_cmd=0x14, builds speed table, - /// then commits with triple SET_CD_SPEED (max → nominal → max). - fn calibrate(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; - - fn keepalive(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; - - /// - /// Returns 16 bytes of feature data via READ_BUFFER sub_cmd=0x13. - fn status(&mut self, scsi: &mut dyn ScsiTransport) -> Result; - - fn probe(&mut self, scsi: &mut dyn ScsiTransport, sub_cmd: u8, address: u32, length: u32) -> Result>; - - /// - /// Looks up LBA in speed_zone_table, sends SET_CD_SPEED. - /// Called by x86 before each zone change during content reads. - fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()>; - - fn timing(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; - - /// Full init sequence — matches x86 dispatch order. - /// - /// unlock → [load_firmware if fail] × 6 → calibrate × 6 +/// Platform trait — locked-down interface. +/// +/// Only three operations exposed: +/// init() — called once by DriveSession::open() +/// set_read_speed() — called per zone during reads +/// is_ready() — state check +/// +/// All internal handlers (unlock, firmware, calibrate, registers) +/// are private to the implementation. Cannot be called externally. +pub(crate) trait Platform { + /// One-time initialization. Called by DriveSession::open() only. + /// Internally: unlock → [firmware if needed] → calibrate → registers. + /// Safe to call on any drive state (warm, cold, OEM). fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>; - /// Check if raw disc access mode is currently active. - fn is_unlocked(&self) -> bool; -} + /// Set read speed for a disc zone. Called during content reads. + fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()>; -#[derive(Debug, Clone)] -pub struct DriveStatus { - pub unlocked: bool, - pub features: [u8; 16], + /// True after successful init(). + fn is_ready(&self) -> bool; } diff --git a/src/platform/mt1959.rs b/src/platform/mt1959.rs index 711ea13..ad7d03c 100644 --- a/src/platform/mt1959.rs +++ b/src/platform/mt1959.rs @@ -10,7 +10,14 @@ use crate::error::{Error, Result}; use crate::profile::DriveProfile; use crate::scsi::{self, DataDirection, ScsiTransport}; -use super::{Platform, DriveStatus}; +use super::Platform; + +/// Internal drive status — not exposed to callers. +#[derive(Debug)] +struct DriveStatus { + unlocked: bool, + features: [u8; 16], +} /// MT1959 driver state. pub struct Mt1959 { @@ -151,7 +158,29 @@ impl Mt1959 { } impl Platform for Mt1959 { - /// Thin wrapper → do_unlock(). Returns Ok if mode active, Err if not. + fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> { + // Guard: don't re-init an already-ready drive + if self.unlocked && self.calibrated { + return Ok(()); + } + self.run_init(scsi) + } + + fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()> { + if !self.calibrated { + return Ok(()); + } + self.run_set_read_speed(scsi, lba) + } + + fn is_ready(&self) -> bool { + self.unlocked && self.calibrated + } +} + +// ── All handlers are PRIVATE — only callable through init() ──────────── + +impl Mt1959 { fn unlock(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> { self.do_unlock(scsi)?; Ok(()) @@ -399,7 +428,7 @@ impl Platform for Mt1959 { /// d. Build custom SET_CD_SPEED: BB 00 [r6>>8] [r6&FF] FF FF 00... /// e. Send custom SET_CD_SPEED /// f. Return next speed_table entry to host - fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()> { + fn run_set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()> { if !self.calibrated { return Ok(()); } @@ -455,13 +484,7 @@ impl Platform for Mt1959 { } /// Full init sequence — matches x86 dispatch exactly. - /// - /// Phase 1: cmd 0 → [cmd 1 if fail] × 6 retries (unlock + fw upload) - /// Phase 2: cmd 4 × 6 retries (calibrate) - /// Phase 3: cmd 7 → [cmd 5 fallback] (drive info) - /// Phase 4: cmd 2 + cmd 3 × 5 retries (registers) - /// Phase 5: cmd 9 × 6 retries (status) - fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> { + fn run_init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> { // Phase 1: Unlock + firmware upload (6 retries) // // Three unlock outcomes: @@ -515,9 +538,6 @@ impl Platform for Mt1959 { Ok(()) } - fn is_unlocked(&self) -> bool { - self.unlocked - } } // ── Private firmware upload variants ───────────────────────────────────