blob: a055823929f25d3387515e6e3038bbeca2a18068 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::path::Path;
use crate::context::Context;
use crate::error::HwsecError;
use crate::gsc::get_value_from_gsctool_output;
use crate::gsc::parse_version;
use crate::gsc::run_gsctool_cmd;
use crate::gsc::Version;
/// Retrieve Cr50 version running on the DUT. Reported in 'gsctool -afM' output
/// as RW_FW_VER=<epoch>.<major>.<minor>
fn retrieve_dut_firmware_rw_version(ctx: &mut impl Context) -> Result<Version, HwsecError> {
let gsctool_output = run_gsctool_cmd(ctx, vec!["--fwver", "--machine"]).map_err(|e| {
eprintln!("Failed to run gsctool.");
e
})?;
if !gsctool_output.status.success() {
eprintln!("Failed to get running Cr50 firmware versions.");
return Err(HwsecError::GsctoolError(
gsctool_output.status.code().unwrap(),
));
}
let output = std::str::from_utf8(&gsctool_output.stdout).map_err(|_| {
eprintln!("Internal error occurred.");
HwsecError::GsctoolResponseBadFormatError
})?;
let rw_version_str = get_value_from_gsctool_output(output, "RW_FW_VER")?;
match parse_version(rw_version_str) {
Some(version) => Ok(version),
None => {
eprintln!("Failed to parse {} into version", rw_version_str);
Err(HwsecError::InternalError)
}
}
}
/// Retrieve RW Cr50 version of the supplied image file. Reported in 'gsctool
/// -Mb' output as
///
/// IMAGE_RO_FW_VER=<version>
/// IMAGE_RW_FW_VER=<version>
/// IMAGE_BID_STRING=<hex>
/// IMAGE_BID_MASK=<hex>
/// IMAGE_BID_FLAGS=<hex>
///
/// RW_A and RW_B versions are expected to match for the purposes of this
/// script.
fn retrieve_image_rw_version(
ctx: &mut impl Context,
image_path: &Path,
) -> Result<Version, HwsecError> {
let gsctool_output = run_gsctool_cmd(
ctx,
vec!["--machine", "--binvers", image_path.to_str().unwrap()],
)
.map_err(|e| {
eprintln!("ERROR: Failed to run gsctool.");
e
})?;
if !gsctool_output.status.success() {
eprintln!("Failed to get Cr50 image's RW and RO headers.");
return Err(HwsecError::GsctoolError(
gsctool_output.status.code().unwrap(),
));
}
let output = std::str::from_utf8(&gsctool_output.stdout).map_err(|_| {
eprintln!("Internal error occurred.");
HwsecError::GsctoolResponseBadFormatError
})?;
let image_version_str = get_value_from_gsctool_output(output, "IMAGE_RW_FW_VER")?;
match parse_version(image_version_str) {
Some(version) => Ok(version),
None => {
eprintln!("Failed to parse {} into version", image_version_str);
Err(HwsecError::InternalError)
}
}
}
/// Retrieve board ID and flags values from the H1 on the DUT.
///
/// Output of 'gsctool -i' contains a line of the following format:
///
/// BID_TYPE=<hex board ID>
/// BID_TYPE_INV=~<hex board id>
/// BID_FLAGS=<hex flags>
/// BID_RLZ=<ascii rlz>
///
/// The <hex board ID> value is included twice, straight and inverted.
///
/// This function verifies that board ID value is valid and returns the
/// board ID and flags as u32.
fn retrieve_dut_board_id_values(ctx: &mut impl Context) -> Result<(u32, u32), HwsecError> {
let gsctool_output = run_gsctool_cmd(ctx, vec!["--board_id", "--machine"]).map_err(|e| {
eprintln!("ERROR: Failed to run gsctool.");
e
})?;
if !gsctool_output.status.success() {
eprintln!("Failed to get running Cr50 firmware versions.");
return Err(HwsecError::GsctoolError(
gsctool_output.status.code().unwrap(),
));
}
let output = std::str::from_utf8(&gsctool_output.stdout).map_err(|_| {
eprintln!("Internal error occurred.");
HwsecError::GsctoolResponseBadFormatError
})?;
let board_id_str = get_value_from_gsctool_output(output, "BID_TYPE")?;
let board_id_invert_str = get_value_from_gsctool_output(output, "BID_TYPE_INV")?;
let board_id_flags_str = get_value_from_gsctool_output(output, "BID_FLAGS")?;
// let board_id_rlz = get_value_from_gsctool_output(output, "BID_RLZ=")?;
let board_id = u32::from_str_radix(board_id_str, 16).map_err(|_| HwsecError::InternalError)?;
let board_id_invert =
u32::from_str_radix(board_id_invert_str, 16).map_err(|_| HwsecError::InternalError)?;
if !board_id != board_id_invert {
eprintln!(
"Invalid chip board ID value bid {}, ~bid {}",
board_id_str, board_id_invert_str
);
return Err(HwsecError::InternalError);
}
let board_id_flags =
u32::from_str_radix(board_id_flags_str, 16).map_err(|_| HwsecError::InternalError)?;
Ok((board_id, board_id_flags))
}
fn retrieve_dut_board_rlz(ctx: &mut impl Context) -> Result<String, HwsecError> {
let gsctool_output = run_gsctool_cmd(ctx, vec!["--board_id", "--machine"]).map_err(|e| {
eprintln!("ERROR: Failed to run gsctool.");
e
})?;
if !gsctool_output.status.success() {
eprintln!("Failed to get running Cr50 firmware versions.");
return Err(HwsecError::GsctoolError(
gsctool_output.status.code().unwrap(),
));
}
let output = std::str::from_utf8(&gsctool_output.stdout).map_err(|_| {
eprintln!("Internal error occurred.");
HwsecError::GsctoolResponseBadFormatError
})?;
let board_rlz = get_value_from_gsctool_output(output, "BID_RLZ")?;
Ok(String::from(board_rlz))
}
/// Retrieve Cr50 binary's board ID and flags values.
///
/// Output of 'gsctool -b cr50.bin' contains a line of the following format:
///
/// ...
/// IMAGE_BID_STRING=<board ID>
/// IMAGE_BID_MASK=<hex board ID mask>
/// IMAGE_BID_FLAGS=<hex flags>
/// ...
///
/// This function returns the board ID, board ID
/// mask and flags as u32.
fn retrieve_image_board_id_values(
ctx: &mut impl Context,
image_path: &Path,
) -> Result<(u32, u32, u32), HwsecError> {
let gsctool_output = run_gsctool_cmd(
ctx,
vec!["--machine", "--binvers", image_path.to_str().unwrap()],
)
.map_err(|e| {
eprintln!("ERROR: Failed to run gsctool.");
e
})?;
if !gsctool_output.status.success() {
eprintln!("Failed to get Cr50 image's RW and RO headers.");
return Err(HwsecError::GsctoolError(
gsctool_output.status.code().unwrap(),
));
}
let output = std::str::from_utf8(&gsctool_output.stdout).map_err(|_| {
eprintln!("Internal error occurred.");
HwsecError::GsctoolResponseBadFormatError
})?;
let image_board_id_str = get_value_from_gsctool_output(output, "IMAGE_BID_STRING")?;
let image_board_id_mask_str = get_value_from_gsctool_output(output, "IMAGE_BID_MASK")?;
let image_board_id_flags_str = get_value_from_gsctool_output(output, "IMAGE_BID_FLAGS")?;
let image_board_id =
u32::from_str_radix(image_board_id_str, 16).map_err(|_| HwsecError::InternalError)?;
let image_board_id_mask =
u32::from_str_radix(image_board_id_mask_str, 16).map_err(|_| HwsecError::InternalError)?;
let image_board_id_flags =
u32::from_str_radix(image_board_id_flags_str, 16).map_err(|_| HwsecError::InternalError)?;
Ok((image_board_id, image_board_id_mask, image_board_id_flags))
}
pub fn update_dut_if_needed(ctx: &mut impl Context, new_image: &Path) -> Result<(), HwsecError> {
let dut_rw_version = retrieve_dut_firmware_rw_version(ctx).map_err(|e| {
eprintln!(
"Failed to retrieve DUT Cr50 version. Is DUT connected? \
You may need to flip suzy-q cable if DUT is already attached."
);
e
})?;
let image_rw_version = retrieve_image_rw_version(ctx, new_image).map_err(|e| {
eprintln!("Failed to retrieve image firmware version.");
e
})?;
if dut_rw_version.to_ord() >= image_rw_version.to_ord() {
// Don't need to update.
return Ok(());
}
// Perform update if DUT version is lower than the supplied image version.
println!(
"Updating dut from {} to {}",
dut_rw_version, image_rw_version
);
update_dut(ctx, new_image).map_err(|e| {
eprintln!("Failed when trying to update DUT.");
e
})?;
println!("Waiting for the DUT to restart");
ctx.sleep(5); // Let it reboot.
println!("Verifying that update succeeded");
let dut_rw_version = retrieve_dut_firmware_rw_version(ctx)?;
if dut_rw_version.to_ord() < image_rw_version.to_ord() {
eprintln!("Failed to update DUT to version {}", image_rw_version);
Err(HwsecError::InternalError)
} else {
Ok(())
}
}
fn update_dut(ctx: &mut impl Context, image_path: &Path) -> Result<(), HwsecError> {
// Retrieve board ID header fields of the image file.
let (image_bid, image_mask, image_flags) = retrieve_image_board_id_values(ctx, image_path)?;
// Retrieve board ID fields of the H1 on the DUT.
let (chip_bid, chip_flags) = retrieve_dut_board_id_values(ctx)?;
// Verify that board ID of the image is suitable for the chip.
if (image_bid & image_mask) != (chip_bid & image_mask) {
eprintln!("Image board ID and mask incompatible with chip board ID");
return Err(HwsecError::InternalError);
}
// Verify that flags of the image are compatible with the chip.
if (image_flags & chip_flags) != (image_flags) {
eprintln!("Image flags incompatible with chip flags");
return Err(HwsecError::InternalError);
}
run_gsctool_cmd(ctx, vec![image_path.to_str().unwrap()])?;
Ok(())
}
pub fn gsc_verify_ro(ctx: &mut impl Context, ro_descriptions: &Path) -> Result<(), HwsecError> {
let rlz = retrieve_dut_board_rlz(ctx)?;
let desc_file_str = ro_descriptions.join(format!("verify_ro_{}.db", rlz));
let gsctool_output =
run_gsctool_cmd(ctx, vec!["--openbox_rma", desc_file_str.to_str().unwrap()]).map_err(
|e| {
eprintln!("ERROR: Failed to run gsctool.");
e
},
)?;
if !gsctool_output.status.success() {
eprintln!("Hash verification failed.");
return Err(HwsecError::GsctoolError(
gsctool_output.status.code().unwrap(),
));
}
eprintln!("Hash verification succeed.");
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::retrieve_dut_board_id_values;
use super::retrieve_dut_firmware_rw_version;
use super::update_dut;
use super::update_dut_if_needed;
use crate::context::mock::MockContext;
use crate::context::Context;
use crate::error::HwsecError;
use crate::gsc::Version;
#[test]
fn test_retrieve_dut_firmware_rw_version_ok() {
let mut mock_ctx = MockContext::new();
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--fwver", "--machine"],
0,
"RO_FW_VER=0.0.0\nRW_FW_VER=1.2.3",
"",
);
let result = retrieve_dut_firmware_rw_version(&mut mock_ctx);
assert_eq!(
result,
Ok(Version {
epoch: 1,
major: 2,
minor: 3,
})
);
}
#[test]
fn test_retrieve_dut_firmware_rw_version_gsctool_outout_wrong_format_1() {
let mut mock_ctx = MockContext::new();
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--fwver", "--machine"],
0,
"RO_FW_VER=1.2.3",
"",
);
let result = retrieve_dut_firmware_rw_version(&mut mock_ctx);
assert_eq!(result, Err(HwsecError::InternalError));
}
#[test]
fn test_retrieve_dut_firmware_rw_version_gsctool_outout_wrong_format_2() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--machine"], 0, "1.2.3", "");
let result = retrieve_dut_firmware_rw_version(&mut mock_ctx);
assert_eq!(result, Err(HwsecError::InternalError));
}
#[test]
fn test_retrieve_dut_board_id_values_ok() {
let mut mock_ctx = MockContext::new();
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--board_id", "--machine"],
0,
"BID_TYPE=5a5a4352\nBID_TYPE_INV=a5a5bcad\nBID_FLAGS=00007f7f\n",
"",
);
let result = retrieve_dut_board_id_values(&mut mock_ctx);
assert_eq!(
result,
Ok((
u32::from_str_radix("5a5a4352", 16).unwrap(),
u32::from_str_radix("00007f7f", 16).unwrap()
))
)
}
#[test]
fn test_retrieve_dut_board_id_values_invalid_bid_inv() {
let mut mock_ctx = MockContext::new();
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--board_id", "--machine"],
0,
"BID_TYPE=5a5a4352\nBID_TYPE_INV=00000000\nBID_FLAGS=00007f7f\n",
"",
);
let result = retrieve_dut_board_id_values(&mut mock_ctx);
assert_eq!(result, Err(HwsecError::InternalError))
}
#[test]
fn test_update_dut_ok() {
let mut mock_ctx = MockContext::new();
// mock interaction for retrieve_image_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--machine", "--binvers", "mock_image_path"],
0,
"IMAGE_BID_STRING=00000000\nIMAGE_BID_MASK=00000000\nIMAGE_BID_FLAGS=00000000\n",
"",
);
// mock interaction for retrieve_dut_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--board_id", "--machine"],
0,
"BID_TYPE=00000000\nBID_TYPE_INV=ffffffff\nBID_FLAGS=00000000\n",
"",
);
// mock interaction for updating firmware
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["mock_image_path"], 0, "", "");
let result = update_dut(&mut mock_ctx, Path::new("mock_image_path"));
assert_eq!(result, Ok(()));
}
#[test]
fn test_update_dut_bid_incompatible() {
// (image_bid & image_mask) != (chip_bid & image_mask)
let mut mock_ctx = MockContext::new();
// mock interaction for retrieve_image_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--machine", "--binvers", "mock_image_path"],
0,
"IMAGE_BID_STRING=00000000\nIMAGE_BID_MASK=00000000\nIMAGE_BID_FLAGS=00000000\n",
"",
);
// mock interaction for retrieve_dut_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--board_id", "--machine"],
0,
"BID_TYPE=00000001\nBID_TYPE_INV=ffffffff\nBID_FLAGS=00000000\n",
"",
);
let result = update_dut(&mut mock_ctx, Path::new("mock_image_path"));
assert_eq!(result, Err(HwsecError::InternalError));
}
#[test]
fn test_update_dut_image_flag_incompatible() {
// (image_flags & chip_flags) != (image_flags)
let mut mock_ctx = MockContext::new();
// mock interaction for retrieve_image_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--machine", "--binvers", "mock_image_path"],
0,
"IMAGE_BID_STRING=00000000\nIMAGE_BID_MASK=00000000\nIMAGE_BID_FLAGS=00000001\n",
"",
);
// mock interaction for retrieve_dut_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--board_id", "--machine"],
0,
"BID_TYPE=00000000\nBID_TYPE_INV=ffffffff\nBID_FLAGS=00000000\n",
"",
);
let result = update_dut(&mut mock_ctx, Path::new("mock_image_path"));
assert_eq!(result, Err(HwsecError::InternalError));
}
#[test]
fn test_update_dut_if_needed_update_not_needed() {
let mut mock_ctx = MockContext::new();
// mock interaction for retrieve_dut_firmware_rw_version
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--fwver", "--machine"],
0,
"RW_FW_VER=1.2.3",
"",
);
// mock interaction for retrieve_image_rw_version
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--machine", "--binvers", "mock_image_path"],
0,
"IMAGE_RW_FW_VER=1.2.3",
"",
);
let result = update_dut_if_needed(&mut mock_ctx, Path::new("mock_image_path"));
assert_eq!(result, Ok(()));
}
#[test]
fn test_update_dut_if_needed_successfully_update() {
let mut mock_ctx = MockContext::new();
// mock interaction for retrieve_dut_firmware_rw_version
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--fwver", "--machine"],
0,
"RW_FW_VER=1.2.3",
"",
);
// mock interaction for retrieve_image_rw_version
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--machine", "--binvers", "mock_image_path"],
0,
"IMAGE_RW_FW_VER=1.2.4",
"",
);
// mock interaction for retrieve_image_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--machine", "--binvers", "mock_image_path"],
0,
"IMAGE_BID_STRING=00000000\nIMAGE_BID_MASK=00000000\nIMAGE_BID_FLAGS=00000000\n",
"",
);
// mock interaction for retrieve_dut_board_id_values
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--board_id", "--machine"],
0,
"BID_TYPE=00000000\nBID_TYPE_INV=ffffffff\nBID_FLAGS=00000000\n",
"",
);
// mock interaction for updating firmware
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["mock_image_path"], 0, "", "");
// mock interaction for retrieve_dut_firmware_rw_version (after update)
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--fwver", "--machine"],
0,
"RW_FW_VER=1.2.4",
"",
);
let result = update_dut_if_needed(&mut mock_ctx, Path::new("mock_image_path"));
assert_eq!(result, Ok(()));
}
}