New crate freemkv-unlock-ld — the LibreDrive Unlocker implementation, split out of libfreemkv so the library stays firmware-clean on crates.io. Owns everything about HOW MediaTek MT1959 drives are firmware-unlocked: - profiles.json (the bundled drive-profile database + DriveProfile parse) - src/platform/mt1959/* (variant-A/B firmware upload + unlock handshake) - WRITE_BUFFER / MODE SELECT upload, unlock CDBs, disc-speed calibration Exposes LibreDrive::new() implementing libfreemkv::Unlocker (name/matches/ unlock). Plug it in with one line at process start: libfreemkv::register_unlocker(Box::new(freemkv_unlock_ld::LibreDrive::new())); Depends on libfreemkv (path-patched via gitignored .cargo/config.toml in dev) for the trait, ScsiTransport, DriveId, and Result. README notes it's the LibreDrive unlocker (attribution to be added by owner).
75 lines
2.2 KiB
Rust
75 lines
2.2 KiB
Rust
//! MT1959 variant A firmware upload.
|
||
//!
|
||
//! WRITE_BUFFER (0x3B) → verify READ_BUFFER (0x45) → unlock × 2
|
||
|
||
use super::Mt1959;
|
||
use crate::error::Result;
|
||
use crate::scsi::{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::error::Error::UnlockFailed);
|
||
}
|
||
|
||
// Upload firmware via WRITE_BUFFER. The CDB's length is a 24-bit field;
|
||
// if the blob exceeds that, the encoded length would silently disagree
|
||
// with the bytes actually sent (`data`). Reject rather than upload a
|
||
// length-mismatched command.
|
||
let len = firmware.len();
|
||
if len > WRITE_BUFFER_MAX_LEN {
|
||
return Err(crate::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();
|
||
scsi.execute(&cdb, DataDirection::ToDevice, &mut data, 30_000)?;
|
||
|
||
// Verify firmware loaded (non-fatal — different buffer_id 0x45)
|
||
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];
|
||
let _ = scsi.execute(
|
||
&verify_cdb,
|
||
DataDirection::FromDevice,
|
||
&mut verify_resp,
|
||
5_000,
|
||
);
|
||
|
||
// Double unlock after firmware upload. The first establishes the
|
||
// unlock and is fatal on failure; the second is a confirmation pass and
|
||
// is best-effort (matching variant B), so a benign hiccup on the
|
||
// redundant call doesn't fail an already-successful unlock.
|
||
mt.do_unlock(scsi)?;
|
||
let _ = mt.do_unlock(scsi);
|
||
Ok(())
|
||
}
|