blob: 649fac93ce6f08c4d513019d8119629f151897cc [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use log::error;
use log::info;
use crate::context::Context;
use crate::cr50::cr50_get_name;
use crate::cr50::gsctool_cmd_successful;
use crate::cr50::run_gsctool_cmd;
use crate::cr50::GSCTOOL_CMD_NAME;
use crate::error::HwsecError;
const MAX_RETRIES: u32 = 3;
const SLEEP_DURATION: u64 = 70;
// TODO (b/246863926)
pub fn cr50_update(ctx: &mut impl Context) -> Result<(), HwsecError> {
info!("Starting");
let options: Vec<&str>;
// determine the best way to communicate with the Cr50.
if gsctool_cmd_successful(ctx, vec!["--fwver", "--systemdev"]) {
info!("Will use /dev/tpm0");
options = vec!["--systemdev"];
} else if gsctool_cmd_successful(ctx, vec!["--fwver", "--trunks_send"]) {
info!("Will use trunks_send");
options = vec!["--trunks_send"];
} else {
error!("Could not communicate with Cr50");
return Err(HwsecError::GsctoolError(1));
}
let cr50_image = cr50_get_name(ctx, &options[..])?;
if !ctx.path_exists(cr50_image.as_str()) {
info!("{} not found, quitting.", cr50_image);
return Err(HwsecError::GsctoolError(1));
}
let mut exit_status: i32 = 0;
for retries in 0..MAX_RETRIES {
if retries != 0 {
// Need to sleep for at least a minute to get around cr50 update throttling:
// it rejects repeat update attempts happening sooner than 60 seconds after
// the previous one.
ctx.sleep(SLEEP_DURATION);
}
let exe_result =
run_gsctool_cmd(ctx, [&options[..], &["--upstart", &cr50_image]].concat())?;
exit_status = exe_result.status.code().unwrap();
// Exit status values 2 or below indicate successful update, nonzero
// values mean that reboot is required for the new version to kick in.
if exit_status <= 2 {
// Callers of this script do not care about the details and consider any
// non-zero value an error.
info!("success ({})", exit_status);
exit_status = 0;
break;
}
info!(
"{} (options: {:?}) attempt {} error {}",
GSCTOOL_CMD_NAME,
options,
retries + 1,
exit_status
);
let output = format!(
"{}{}",
std::str::from_utf8(&exe_result.stdout)
.map_err(|_| HwsecError::GsctoolResponseBadFormatError)?,
std::str::from_utf8(&exe_result.stderr)
.map_err(|_| HwsecError::GsctoolResponseBadFormatError)?
);
// Log output text one line at a time, otherwise they are all concatenated
// into a single long entry with messed up line breaks.
for line in output.lines() {
info!("{}", line);
}
}
if exit_status == 0 {
Ok(())
} else {
Err(HwsecError::GsctoolError(exit_status))
}
}
#[cfg(test)]
mod tests {
use crate::context::mock::MockContext;
use crate::context::Context;
use crate::cr50::cr50_update;
use crate::cr50::update::MAX_RETRIES;
use crate::cr50::GSC_IMAGE_BASE_NAME;
use crate::error::HwsecError;
#[test]
fn test_cr50_update_could_not_communicate_with_cr50() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 1, "", "");
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--trunks_send"], 1, "", "");
let result = cr50_update(&mut mock_ctx);
assert_eq!(result, Err(HwsecError::GsctoolError(1)));
}
#[test]
fn test_cr50_update_cr50_image_not_exist() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 0, "", "");
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--board_id"],
0,
"Board ID space: 43425559:bcbdaaa6:00007f10",
"",
);
let result = cr50_update(&mut mock_ctx);
assert_eq!(result, Err(HwsecError::GsctoolError(1)));
}
#[test]
fn test_cr50_update_ok() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 0, "", "");
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--board_id"],
0,
"Board ID space: 43425559:bcbdaaa6:00007f10",
"",
);
let path = String::from(GSC_IMAGE_BASE_NAME) + ".prepvt";
mock_ctx.create_path(path.as_str());
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--upstart", path.as_str()],
0,
"",
"",
);
let result = cr50_update(&mut mock_ctx);
assert_eq!(result, Ok(()));
}
#[test]
fn test_cr50_update_failed_exceed_max_retries() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 0, "", "");
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--board_id"],
0,
"Board ID space: 43425559:bcbdaaa6:00007f10",
"",
);
let path = String::from(GSC_IMAGE_BASE_NAME) + ".prepvt";
mock_ctx.create_path(path.as_str());
for _ in 0..MAX_RETRIES {
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--upstart", path.as_str()],
3,
"",
"",
);
}
let result = cr50_update(&mut mock_ctx);
assert_eq!(result, Err(HwsecError::GsctoolError(3)));
}
}