From 25a7b131b48841dcda5176eb7e4be47f77616226 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:54:51 -0700 Subject: [PATCH] =?UTF-8?q?drive:=20Drive::spin=5Fcycle()=20=E2=80=94=20so?= =?UTF-8?q?ft=20un-wedge=20via=20START=20STOP=20UNIT=20(no=20eject)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spin the disc down (START=0, LOEJ=0) then back up (START=1) to clear the BU40N/Initio fast-fail wedge state a run of HARDWARE_ERROR reads leaves the drive in — the non-eject power-cycle our notes say the wedge needs. The disc stays loaded; we never eject (slot-loading — a human eject is a product failure for an unattended service). Validated live 2026-07-01: took the drive from failing-every-read back to reading at MB/s. --- src/drive/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/drive/mod.rs b/src/drive/mod.rs index c86425e..3d3041b 100644 --- a/src/drive/mod.rs +++ b/src/drive/mod.rs @@ -51,6 +51,13 @@ pub enum DriveStatus { // SCSI opcodes used in drive control const SCSI_TEST_UNIT_READY: u8 = 0x00; const SCSI_START_STOP_UNIT: u8 = 0x1B; +/// Idle time the disc sits spun-down during [`Drive::spin_cycle`] before it's +/// spun back up — long enough for the mechanism's fast-fail wedge state to +/// clear. Validated at 5–6 s live. +const SPIN_DOWN_IDLE_SECS: u64 = 5; +/// Settle time after spin-up in [`Drive::spin_cycle`] before the caller reads +/// again, so the first post-cycle read doesn't hit a transient NOT_READY. +const SPIN_UP_SETTLE_SECS: u64 = 10; const SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E; const SCSI_GET_EVENT_STATUS: u8 = 0x4A; const SCSI_MODE_SENSE: u8 = 0x5A; @@ -895,6 +902,29 @@ impl Drive { Ok(()) } + /// Soft power-cycle the drive mechanism WITHOUT ejecting: spin the disc + /// down (`START STOP UNIT`, START=0, **LOEJ=0**) then back up (START=1). + /// This clears the BU40N/Initio fast-fail *wedge* state that a run of + /// `HARDWARE_ERROR` reads leaves the drive in — the non-eject equivalent of + /// the power-cycle our notes say the wedge needs. The disc stays loaded (the + /// BU40N is slot-loading; we NEVER eject to recover — a hands-on eject is a + /// failure for an unattended service). Validated live 2026-07-01: took the + /// drive from failing-every-read back to reading at MB/s. + pub fn spin_cycle(&mut self) -> Result<()> { + let stop = [SCSI_START_STOP_UNIT, 0, 0, 0, 0x00, 0]; // START=0, LOEJ=0 → spin down + let start = [SCSI_START_STOP_UNIT, 0, 0, 0, 0x01, 0]; // START=1, LOEJ=0 → spin up + let mut buf = [0u8; 0]; + self.scsi + .as_mut() + .execute(&stop, crate::scsi::DataDirection::None, &mut buf, 30_000)?; + std::thread::sleep(std::time::Duration::from_secs(SPIN_DOWN_IDLE_SECS)); + self.scsi + .as_mut() + .execute(&start, crate::scsi::DataDirection::None, &mut buf, 30_000)?; + std::thread::sleep(std::time::Duration::from_secs(SPIN_UP_SETTLE_SECS)); + Ok(()) + } + pub fn scsi_execute( &mut self, cdb: &[u8],