mirror of
https://github.com/freemkv/freemkv-unlock.git
synced 2026-10-07 18:13:11 +00:00
ScsiTransport gains Stop methods; AGIDs are handed off by AgidGuard, CSS is defused on Stop and MT1959 critical spans are guarded, and the post-upload readiness poll waits via pause so a Stop ends it. A test guards the op paths against bare sleeps.
111 lines
3.7 KiB
Rust
111 lines
3.7 KiB
Rust
//! MT1959 variant A firmware upload.
|
||
//!
|
||
//! WRITE_BUFFER (0x3B) → verify READ_BUFFER (0x45) → unlock × 2
|
||
|
||
use super::Mt1959;
|
||
use crate::ld::error::Result;
|
||
use crate::scsi::{CriticalGuard, DataDirection, ScsiTransport};
|
||
|
||
use super::SCSI_WRITE_BUFFER;
|
||
|
||
const VERIFY_BUFFER_ID: u8 = 0x45;
|
||
|
||
/// WRITE_BUFFER carries a 24-bit transfer length, so a firmware blob
|
||
/// larger than this cannot be uploaded in one command.
|
||
const WRITE_BUFFER_MAX_LEN: usize = 0x00FF_FFFF;
|
||
|
||
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);
|
||
}
|
||
|
||
// Upload firmware via WRITE_BUFFER. The CDB's length is a 24-bit field;
|
||
// reject blobs that would exceed it rather than send a length-mismatched
|
||
// command.
|
||
let len = firmware.len();
|
||
if len > WRITE_BUFFER_MAX_LEN {
|
||
return Err(crate::ld::error::Error::UnlockFailed);
|
||
}
|
||
let cdb = [
|
||
SCSI_WRITE_BUFFER,
|
||
0x06,
|
||
0x00,
|
||
0x00,
|
||
0x00,
|
||
0x00,
|
||
(len >> 16) as u8,
|
||
(len >> 8) as u8,
|
||
len as u8,
|
||
0x00,
|
||
];
|
||
let mut data = firmware.clone();
|
||
// D8 (stop-design-v5 §2.3): "the WRITE BUFFER at 30 s … and its verify run inside
|
||
// a `CriticalGuard`. C_max = 35 s" — a Stop mid-upload waits for both CDBs.
|
||
let mut span = CriticalGuard::enter(&mut *scsi)?;
|
||
span.execute(&cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||
|
||
// Verify the firmware loaded (non-fatal — different buffer_id 0x45). No
|
||
// documented expected payload exists, so the outcome is traced rather
|
||
// than asserted, instead of silently discarded before the unlock retries.
|
||
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];
|
||
match span.execute(
|
||
&verify_cdb,
|
||
DataDirection::FromDevice,
|
||
&mut verify_resp,
|
||
5_000,
|
||
) {
|
||
Ok(r) if r.status == 0 && r.bytes_transferred == verify_resp.len() => {
|
||
tracing::debug!(
|
||
target: "freemkv::disc",
|
||
phase = "mt1959a_fw_verify_ok",
|
||
"variant-A firmware verify read returned a full response"
|
||
);
|
||
}
|
||
Ok(r) => {
|
||
tracing::warn!(
|
||
target: "freemkv::disc",
|
||
phase = "mt1959a_fw_verify_bad",
|
||
status = r.status,
|
||
bytes_transferred = r.bytes_transferred,
|
||
"variant-A firmware verify read did not return a full good response"
|
||
);
|
||
}
|
||
// A dead bus mid-upload must abort, not be swallowed into the unlock
|
||
// retries below.
|
||
Err(e) => {
|
||
let err = crate::ld::error::Error::from(e);
|
||
tracing::warn!(
|
||
target: "freemkv::disc",
|
||
phase = "mt1959a_fw_verify_failed",
|
||
transport_failure = err.is_transport_failure(),
|
||
"variant-A firmware verify read failed"
|
||
);
|
||
if err.is_transport_failure() {
|
||
return Err(err);
|
||
}
|
||
}
|
||
}
|
||
|
||
drop(span); // the span ends here; the unlock below stays cancellable
|
||
|
||
// Double unlock after firmware upload: first is fatal on failure, second
|
||
// is a best-effort confirmation pass (matching variant B) so a benign
|
||
// hiccup there doesn't fail an already-successful unlock.
|
||
mt.do_unlock(scsi)?;
|
||
let _ = mt.do_unlock(scsi);
|
||
Ok(())
|
||
}
|