mt1959_b: per-drive fw_verify_cdb + correct firmware length; v1.0.0-rc.4.3
The variant-B firmware load hardcoded one drive's F1 vendor-verify token and a fixed 0x9C0 firmware size. Across the 140 B drives the verify CDB has 39 distinct per-drive values, and the real firmware length is encoded in each drive's MODE SELECT CDB (2192..2528 bytes) — so the constants were wrong for ~139 of 140 drives and truncated 13. - DriveProfile gains fw_verify_cdb (the per-drive 0xF1 verify). - variant_b uploads firmware.len() (now the correct per-drive size from the regenerated profiles.json) and issues profile.fw_verify_cdb, falling back to the const only for legacy profiles. - profiles.json regenerated: 79 firmware fields corrected (13 truncated recovered to full length, 66 over-reads trimmed). - Adds a recording-transport regression test asserting the profile's F1 verify is issued (not the const) and MODE SELECT carries firmware.len().
This commit is contained in:
@@ -425,6 +425,64 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Records every CDB issued, so a test can assert which bytes hit the wire.
|
||||
struct RecordingTransport {
|
||||
cdbs: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ScsiTransport for RecordingTransport {
|
||||
fn execute(
|
||||
&mut self,
|
||||
cdb: &[u8],
|
||||
_dir: DataDirection,
|
||||
_data: &mut [u8],
|
||||
_timeout_ms: u32,
|
||||
) -> Result<ScsiResult> {
|
||||
self.cdbs.push(cdb.to_vec());
|
||||
// Empty response → do_unlock's signature check fails, so the unlock
|
||||
// loop exhausts — but the firmware-load CDBs (incl. the F1 verify)
|
||||
// are already recorded by then.
|
||||
Ok(ScsiResult {
|
||||
status: 0,
|
||||
bytes_transferred: 0,
|
||||
sense: [0u8; 32],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// variant_b must issue the PROFILE's per-drive `fw_verify_cdb`, not the
|
||||
/// hardcoded fallback const — the bug that broke ~139 of 140 B drives.
|
||||
#[test]
|
||||
fn variant_b_issues_profile_fw_verify_cdb_not_const() {
|
||||
let drive_f1 = [0xF1, 0x01, 0x02, 0x00, 0x0C, 0xF0, 0x01, 0xFB, 0xC9, 0x93];
|
||||
// variant_b's hardcoded fallback const (a different drive's token).
|
||||
let fallback_f1 = [0xF1, 0x01, 0x02, 0x00, 0x0D, 0x30, 0x01, 0xF3, 0xAD, 0x23];
|
||||
let mut profile = fixture_profile([0x9a, 0xa9, 0x3a, 0xe2]);
|
||||
profile.firmware = vec![0u8; 2208]; // a real per-drive length, != old 0x9C0
|
||||
profile.fw_verify_cdb = Some(drive_f1);
|
||||
|
||||
let mut mt = Mt1959::new(profile, true);
|
||||
let mut t = RecordingTransport { cdbs: Vec::new() };
|
||||
let _ = variant_b::load_firmware(&mut mt, &mut t);
|
||||
|
||||
assert!(
|
||||
t.cdbs.iter().any(|c| c.as_slice() == drive_f1),
|
||||
"must send the profile's F1 verify CDB"
|
||||
);
|
||||
assert!(
|
||||
!t.cdbs.iter().any(|c| c.as_slice() == fallback_f1),
|
||||
"must NOT send the hardcoded fallback const when the profile has its own"
|
||||
);
|
||||
// MODE SELECT must encode the real per-drive length (2208), not 0x9C0.
|
||||
let ms = t
|
||||
.cdbs
|
||||
.iter()
|
||||
.find(|c| c.first() == Some(&0x55))
|
||||
.expect("MODE SELECT issued");
|
||||
let len = ((ms[7] as usize) << 8) | ms[8] as usize;
|
||||
assert_eq!(len, 2208, "MODE SELECT length = firmware.len(), not 2496");
|
||||
}
|
||||
|
||||
fn fixture_profile(signature: [u8; 4]) -> DriveProfile {
|
||||
DriveProfile {
|
||||
identity: Identity {
|
||||
@@ -446,6 +504,7 @@ mod tests {
|
||||
read_buffer_verify_cdb: None,
|
||||
write_buffer_cdb: None,
|
||||
read_buffer_unlock_cdb: None,
|
||||
fw_verify_cdb: None,
|
||||
speed_zone_table: None,
|
||||
speed_calc_table: None,
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ use crate::error::Result;
|
||||
use crate::scsi::{DataDirection, ScsiTransport};
|
||||
|
||||
const SCSI_MODE_SELECT: u8 = 0x55;
|
||||
const FIRMWARE_MAX_SIZE: usize = 0x9C0;
|
||||
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];
|
||||
|
||||
pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||
@@ -17,14 +19,16 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re
|
||||
return Err(crate::error::Error::UnlockFailed);
|
||||
}
|
||||
|
||||
// Step 1: Upload firmware via MODE SELECT. Variant-B firmware blobs are
|
||||
// exactly FIRMWARE_MAX_SIZE; a larger blob means a corrupt/wrong profile,
|
||||
// and silently truncating it would upload a partial image that can't
|
||||
// unlock. Reject it explicitly instead.
|
||||
if firmware.len() > FIRMWARE_MAX_SIZE {
|
||||
// Step 1: Upload the firmware via MODE SELECT. The profile's `firmware` is
|
||||
// the exact per-drive image — extracted at the drive's own load-CDB length
|
||||
// (2192..2528 bytes; the old fixed 0x9C0 truncated some drives and over-read
|
||||
// others into blob strings). Upload all of it. 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::error::Error::UnlockFailed);
|
||||
}
|
||||
let write_len = FIRMWARE_MAX_SIZE.min(firmware.len());
|
||||
let mode_select_cdb = [
|
||||
SCSI_MODE_SELECT,
|
||||
0x10,
|
||||
@@ -77,9 +81,12 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re
|
||||
let mut data2 = FIRMWARE_EXTRA.to_vec();
|
||||
let _ = scsi.execute(&write_extra_cdb, DataDirection::ToDevice, &mut data2, 5_000);
|
||||
|
||||
// Step 4: Vendor verify (0xF1 — B-only, not standard SCSI)
|
||||
// Step 4: Vendor verify (0xF1 — B-only, not standard SCSI). PER-DRIVE: take
|
||||
// it from the profile (39 distinct values across the 140 B drives). The
|
||||
// const is only a legacy fallback — it carries one drive's token.
|
||||
let verify_cdb = mt.profile.fw_verify_cdb.unwrap_or(VENDOR_VERIFY);
|
||||
let mut dummy = [0u8; 0];
|
||||
let _ = scsi.execute(&VENDOR_VERIFY, DataDirection::None, &mut dummy, 5_000);
|
||||
let _ = scsi.execute(&verify_cdb, DataDirection::None, &mut dummy, 5_000);
|
||||
|
||||
// Step 5: Unlock retries (up to 5, then a final fatal attempt). On a
|
||||
// successful unlock we issue one confirmation pass; its result is
|
||||
|
||||
@@ -71,6 +71,11 @@ pub struct DriveProfile {
|
||||
pub write_buffer_cdb: Option<[u8; 10]>,
|
||||
#[serde(default, deserialize_with = "deserialize_opt_hex_bytes_10")]
|
||||
pub read_buffer_unlock_cdb: Option<[u8; 10]>,
|
||||
/// Variant-B vendor verify (0xF1) CDB. PER-DRIVE: 39 distinct values across
|
||||
/// the 140 B drives, so it CANNOT be a hardcoded constant. `variant_b`'s old
|
||||
/// `VENDOR_VERIFY` const was one drive's token, wrong for the other ~139.
|
||||
#[serde(default, deserialize_with = "deserialize_opt_hex_bytes_10")]
|
||||
pub fw_verify_cdb: Option<[u8; 10]>,
|
||||
|
||||
// Per-drive identifier tables — variable-length hex strings.
|
||||
#[serde(default, deserialize_with = "deserialize_opt_hex_bytes")]
|
||||
|
||||
Reference in New Issue
Block a user