mirror of
https://github.com/freemkv/freemkv-unlock.git
synced 2026-10-07 18:13:11 +00:00
Fixed - ld/profile: find_by_drive_id no longer mis-binds a drive whose reported product_id is real but uncatalogued to a sibling model's profile (and its wrong LibreDrive microcode); the product-id-blind loose match is gated on an empty id. Docs / CI - Third-party NOTICE for the LibreDrive microcode bundled in profiles.json; cargo-deny gate (#15) and audience-based comment-guard (#17).
153 lines
4.7 KiB
Rust
153 lines
4.7 KiB
Rust
//! 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::{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];
|
||
|
||
// See docs/variant-b-firmware-upload.md — trace_step rationale.
|
||
// 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<crate::scsi::ScsiResult>) -> 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 (see docs/variant-b-firmware-upload.md).
|
||
// 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();
|
||
scsi.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",
|
||
scsi.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",
|
||
scsi.execute(&write_extra_cdb, DataDirection::ToDevice, &mut data2, 5_000),
|
||
)?;
|
||
|
||
// Step 4: Vendor verify (0xF1 — B-only, not standard SCSI), per-drive.
|
||
// See docs/variant-b-firmware-upload.md for why the outcome is traced
|
||
// rather than discarded.
|
||
let verify_cdb = mt.profile.fw_verify_cdb.unwrap_or(VENDOR_VERIFY);
|
||
let mut dummy = [0u8; 0];
|
||
trace_step(
|
||
"mt1959b_fw_verify",
|
||
scsi.execute(&verify_cdb, DataDirection::None, &mut dummy, 5_000),
|
||
)?;
|
||
|
||
// Step 5: Unlock retries (up to 5, then a final fatal attempt). The
|
||
// confirmation pass after success is intentionally best-effort — see
|
||
// docs/variant-b-firmware-upload.md.
|
||
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(())
|
||
}
|