| // 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(())); | |
| } | |
| } |