//! MT1959 variant B firmware upload. //! //! MODE SELECT (0x55) → read metadata → WRITE_BUFFER → vendor verify (0xF1) → unlock × 5+1 use super::{Mt1959, SCSI_READ_BUFFER, SCSI_WRITE_BUFFER}; use crate::ld::error::Result; use crate::scsi::{CriticalGuard, DataDirection, ScsiTransport}; const SCSI_MODE_SELECT: u8 = 0x55; const FIRMWARE_EXTRA: [u8; 16] = [0; 16]; /// Fallback F1 vendor-verify for legacy profiles that predate the per-drive /// `fw_verify_cdb` capture. It carries ONE drive's token, so it only works for /// that drive — real profiles must supply their own (see `DriveProfile`). const VENDOR_VERIFY: [u8; 10] = [0xF1, 0x01, 0x02, 0x00, 0x0D, 0x30, 0x01, 0xF3, 0xAD, 0x23]; // Swallows everything except a transport fault; these steps are advisory, // the unlock retries below are the real gate. fn trace_step(phase: &'static str, r: crate::scsi::Result) -> Result<()> { match r { Ok(res) if res.status == 0 => { tracing::debug!( target: "freemkv::disc", phase, bytes_transferred = res.bytes_transferred, "firmware upload step ok" ); Ok(()) } Ok(res) => { tracing::warn!( target: "freemkv::disc", phase, status = res.status, "firmware upload step returned a drive sense" ); Ok(()) } Err(e) => { let err = crate::ld::error::Error::from(e); tracing::warn!( target: "freemkv::disc", phase, transport_failure = err.is_transport_failure(), "firmware upload step failed" ); if err.is_transport_failure() { return Err(err); } Ok(()) } } } 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::ld::error::Error::UnlockFailed); } // Step 1: Upload the firmware via MODE SELECT. // MODE SELECT(10)'s parameter-list length is 16-bit, so reject only a // blob that can't be expressed in the CDB. let write_len = firmware.len(); if write_len > u16::MAX as usize { return Err(crate::ld::error::Error::UnlockFailed); } let mode_select_cdb = [ SCSI_MODE_SELECT, 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(); // stop-design-v5 §2.3: "MODE SELECT at 30 s …, READ BUFFER, WRITE BUFFER and F1 run // inside a `CriticalGuard`. C_max = 45 s" — a Stop mid-upload waits for all four. let mut span = CriticalGuard::enter(&mut *scsi)?; span.execute(&mode_select_cdb, DataDirection::ToDevice, &mut data, 30_000)?; // 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]; trace_step( "mt1959b_fw_meta", span.execute( &read_meta_cdb, DataDirection::FromDevice, &mut meta_resp, 5_000, ), )?; // 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(); trace_step( "mt1959b_fw_extra", span.execute(&write_extra_cdb, DataDirection::ToDevice, &mut data2, 5_000), )?; // Step 4: Vendor verify (0xF1 — B-only, not standard SCSI), per-drive. let verify_cdb = mt.profile.fw_verify_cdb.unwrap_or(VENDOR_VERIFY); let mut dummy = [0u8; 0]; trace_step( "mt1959b_fw_verify", span.execute(&verify_cdb, DataDirection::None, &mut dummy, 5_000), )?; drop(span); // the span ends here; the unlock retries below stay cancellable // Step 5: Unlock retries (up to 5, then a final fatal attempt). The // confirmation pass after success is intentionally best-effort. for _attempt in 0..5 { match mt.do_unlock(scsi) { Ok(_) => { let _ = mt.do_unlock(scsi); return Ok(()); } // A dead bus will not recover across five more unlock attempts. Err(e) if e.is_transport_failure() => return Err(e), Err(_) => {} } } mt.do_unlock(scsi)?; Ok(()) }