1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-04-28 07:23:43 +02:00
roccochen@chromium.com 04deed8c8f flashrom_tester: Fix partial_lock_test on libflashrom
partial_lock_test (Lock_top_quad, Lock_bottom_quad, Lock_bottom_half,
and Lock_top_half) tries to:
1. Disable HWWP
2. Lock partial
3. Enable HWWP
4. Try to write the locked partial and expect a failure
...

The 4th step only works on flashrom binary since the binary will set
FLASHROM_FLAG_VERIFY_AFTER_WRITE=1 by default and it will error out
while verifying.

But libflashrom does not set any flag beforehand, so it has
FLASHROM_FLAG_VERIFY_AFTER_WRITE=0 and thus it will think the write
command works normally and raise no error. This causes the issue that
flashrom_tester with libflashrom has been failed until today.

To solve this issue, there are two solutions:
1. Take care of the default flags in libflashrom
2. Always pass --noverify to flashrom binary and verify it afterwards.

To make both methods more consistent, I fix it with approach 1.

BUG=b:304439294
BRANCH=none
TEST=flashrom_tester internal --libflashrom Lock_top_quad

Change-Id: I7a8ac0c0984fef3cd9e73ed8d8097ddf429e54b2
Signed-off-by: roccochen@chromium.com <roccochen@chromium.org>
Reviewed-on: https://review.coreboot.org/c/flashrom/+/79304
Reviewed-by: Anastasia Klimchuk <aklm@chromium.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
2024-02-03 07:43:35 +00:00

572 lines
18 KiB
Rust

//
// Copyright 2019, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Alternatively, this software may be distributed under the terms of the
// GNU General Public License ("GPL") version 2 as published by the Free
// Software Foundation.
//
use super::rand_util;
use super::types;
use super::utils::{self, LayoutSizes};
use flashrom::FlashromError;
use flashrom::{FlashChip, Flashrom};
use libflashrom::FlashromFlags;
use serde_json::json;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
// type-signature comes from the return type of lib.rs workers.
type TestError = Box<dyn std::error::Error>;
pub type TestResult = Result<(), TestError>;
pub struct TestEnv<'a> {
chip_type: FlashChip,
/// Flashrom instantiation information.
///
/// Where possible, prefer to use methods on the TestEnv rather than delegating
/// to the raw flashrom functions.
pub cmd: &'a dyn Flashrom,
layout: LayoutSizes,
pub wp: WriteProtectState<'a>,
/// The path to a file containing the flash contents at test start.
original_flash_contents: PathBuf,
/// The path to a file containing flash-sized random data
random_data: PathBuf,
/// The path to a file containing layout data.
pub layout_file: PathBuf,
}
impl<'a> TestEnv<'a> {
pub fn create(
chip_type: FlashChip,
cmd: &'a dyn Flashrom,
print_layout: bool,
) -> Result<Self, FlashromError> {
let rom_sz = cmd.get_size()?;
let out = TestEnv {
chip_type,
cmd,
layout: utils::get_layout_sizes(rom_sz)?,
wp: WriteProtectState::from_hardware(cmd, chip_type)?,
original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(),
random_data: "/tmp/random_content.bin".into(),
layout_file: create_layout_file(rom_sz, Path::new("/tmp/"), print_layout),
};
let flags = FlashromFlags::default();
info!("Set flags: {}", flags);
out.cmd.set_flags(&flags);
info!("Stashing golden image for verification/recovery on completion");
out.cmd.read_into_file(&out.original_flash_contents)?;
out.cmd.verify_from_file(&out.original_flash_contents)?;
info!("Generating random flash-sized data");
rand_util::gen_rand_testdata(&out.random_data, rom_sz as usize)
.map_err(|io_err| format!("I/O error writing random data file: {:#}", io_err))?;
Ok(out)
}
pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult {
let name = test.get_name();
info!("Beginning test: {}", name);
let out = test.run(self);
info!("Completed test: {}; result {:?}", name, out);
out
}
pub fn chip_type(&self) -> FlashChip {
// This field is not public because it should be immutable to tests,
// so this getter enforces that it is copied.
self.chip_type
}
/// Return the path to a file that contains random data and is the same size
/// as the flash chip.
pub fn random_data_file(&self) -> &Path {
&self.random_data
}
pub fn layout(&self) -> &LayoutSizes {
&self.layout
}
/// Return true if the current Flash contents are the same as the golden image
/// that was present at the start of testing.
pub fn is_golden(&self) -> bool {
self.cmd
.verify_from_file(&self.original_flash_contents)
.is_ok()
}
/// Do whatever is necessary to make the current Flash contents the same as they
/// were at the start of testing.
pub fn ensure_golden(&mut self) -> Result<(), FlashromError> {
self.wp.set_hw(false)?.set_sw(false)?;
self.cmd.write_from_file(&self.original_flash_contents)?;
Ok(())
}
/// Attempt to erase the flash.
pub fn erase(&self) -> Result<(), FlashromError> {
self.cmd.erase()?;
Ok(())
}
/// Verify that the current Flash contents are the same as the file at the given
/// path.
///
/// Returns Err if they are not the same.
pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> {
self.cmd.verify_from_file(contents_path)?;
Ok(())
}
}
impl<'a> Drop for TestEnv<'a> {
fn drop(&mut self) {
info!("Verifying flash remains unmodified");
if !self.is_golden() {
warn!("ROM seems to be in a different state at finish; restoring original");
if let Err(e) = self.ensure_golden() {
error!("Failed to write back golden image: {:?}", e);
}
}
}
}
struct WriteProtect {
hw: bool,
sw: bool,
}
/// RAII handle for setting write protect in either hardware or software.
///
/// Given an instance, the state of either write protect can be modified by calling
/// `set`. When it goes out of scope, the write protects will be returned
/// to the state they had then it was created.
pub struct WriteProtectState<'a> {
current: WriteProtect,
initial: WriteProtect,
cmd: &'a dyn Flashrom,
fc: FlashChip,
}
impl<'a> WriteProtectState<'a> {
/// Initialize a state from the current state of the hardware.
pub fn from_hardware(cmd: &'a dyn Flashrom, fc: FlashChip) -> Result<Self, FlashromError> {
let hw = Self::get_hw(cmd)?;
let sw = Self::get_sw(cmd)?;
info!("Initial write protect state: HW={} SW={}", hw, sw);
Ok(WriteProtectState {
current: WriteProtect { hw, sw },
initial: WriteProtect { hw, sw },
cmd,
fc,
})
}
/// Get the actual hardware write protect state.
fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> {
if cmd.can_control_hw_wp() {
super::utils::get_hardware_wp()
} else {
Ok(false)
}
}
/// Get the actual software write protect state.
fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> {
let b = cmd.wp_status(true)?;
Ok(b)
}
/// Return true if the current programmer supports setting the hardware
/// write protect.
///
/// If false, calls to set_hw() will do nothing.
pub fn can_control_hw_wp(&self) -> bool {
self.cmd.can_control_hw_wp()
}
/// Set the software write protect and check that the state is as expected.
pub fn set_sw(&mut self, enable: bool) -> Result<&mut Self, String> {
info!("set_sw request={}, current={}", enable, self.current.sw);
if self.current.sw != enable {
self.cmd
.wp_toggle(/* en= */ enable)
.map_err(|e| e.to_string())?;
}
if Self::get_sw(self.cmd).map_err(|e| e.to_string())? != enable {
Err(format!(
"Software write protect did not change state to {} when requested",
enable
))
} else {
self.current.sw = enable;
Ok(self)
}
}
// Set software write protect with a custom range
pub fn set_range(&mut self, range: (i64, i64), enable: bool) -> Result<&mut Self, String> {
info!("set_range request={}, current={}", enable, self.current.sw);
self.cmd
.wp_range(range, enable)
.map_err(|e| e.to_string())?;
let actual_state = Self::get_sw(self.cmd).map_err(|e| e.to_string())?;
if actual_state != enable {
Err(format!(
"set_range request={}, real={}",
enable, actual_state
))
} else {
self.current.sw = enable;
Ok(self)
}
}
/// Set the hardware write protect if supported and check that the state is as expected.
pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> {
info!("set_hw request={}, current={}", enable, self.current.hw);
if self.can_control_hw_wp() {
if self.current.hw != enable {
super::utils::toggle_hw_wp(/* dis= */ !enable)?;
}
// toggle_hw_wp does check this, but we might not have called toggle_hw_wp so check again.
if Self::get_hw(self.cmd)? != enable {
return Err(format!(
"Hardware write protect did not change state to {} when requested",
enable
));
}
} else {
info!(
"Ignoring attempt to set hardware WP with {:?} programmer",
self.fc
);
}
self.current.hw = enable;
Ok(self)
}
/// Reset the hardware to what it was when this state was created, reporting errors.
///
/// This behaves exactly like allowing a state to go out of scope, but it can return
/// errors from that process rather than panicking.
pub fn close(mut self) -> Result<(), String> {
let out = self.drop_internal();
// We just ran drop, don't do it again
std::mem::forget(self);
out
}
/// Sets both write protects to the state they had when this state was created.
fn drop_internal(&mut self) -> Result<(), String> {
// Toggle both protects back to their initial states.
// Software first because we can't change it once hardware is enabled.
if self.set_sw(self.initial.sw).is_err() {
self.set_hw(false)?;
self.set_sw(self.initial.sw)?;
}
self.set_hw(self.initial.hw)?;
Ok(())
}
}
impl<'a> Drop for WriteProtectState<'a> {
fn drop(&mut self) {
self.drop_internal()
.expect("Error while dropping WriteProtectState")
}
}
pub trait TestCase {
fn get_name(&self) -> &str;
fn expected_result(&self) -> TestConclusion;
fn run(&self, env: &mut TestEnv) -> TestResult;
}
impl<S: AsRef<str>, F: Fn(&mut TestEnv) -> TestResult> TestCase for (S, F) {
fn get_name(&self) -> &str {
self.0.as_ref()
}
fn expected_result(&self) -> TestConclusion {
TestConclusion::Pass
}
fn run(&self, env: &mut TestEnv) -> TestResult {
(self.1)(env)
}
}
impl<T: TestCase + ?Sized> TestCase for &T {
fn get_name(&self) -> &str {
(*self).get_name()
}
fn expected_result(&self) -> TestConclusion {
(*self).expected_result()
}
fn run(&self, env: &mut TestEnv) -> TestResult {
(*self).run(env)
}
}
#[allow(dead_code)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum TestConclusion {
Pass,
Fail,
UnexpectedPass,
UnexpectedFail,
}
pub struct ReportMetaData {
pub chip_name: String,
pub os_release: String,
pub cros_release: String,
pub system_info: String,
pub bios_info: String,
}
fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, Option<TestError>) {
use TestConclusion::*;
match (res, con) {
(Ok(_), Fail) => (UnexpectedPass, None),
(Err(e), Pass) => (UnexpectedFail, Some(e)),
_ => (Pass, None),
}
}
fn create_layout_file(rom_sz: i64, tmp_dir: &Path, print_layout: bool) -> PathBuf {
info!("Calculate ROM partition sizes & Create the layout file.");
let layout_sizes = utils::get_layout_sizes(rom_sz).expect("Could not partition rom");
let layout_file = tmp_dir.join("layout.file");
let mut f = File::create(&layout_file).expect("Could not create layout file");
let mut buf: Vec<u8> = vec![];
utils::construct_layout_file(&mut buf, &layout_sizes).expect("Could not construct layout file");
f.write_all(&buf).expect("Writing layout file failed");
if print_layout {
info!(
"Dumping layout file as requested:\n{}",
String::from_utf8_lossy(&buf)
);
}
layout_file
}
pub fn run_all_tests<T, TS>(
chip: FlashChip,
cmd: &dyn Flashrom,
ts: TS,
terminate_flag: Option<&AtomicBool>,
print_layout: bool,
) -> Vec<(String, (TestConclusion, Option<TestError>))>
where
T: TestCase + Copy,
TS: IntoIterator<Item = T>,
{
let mut env =
TestEnv::create(chip, cmd, print_layout).expect("Failed to set up test environment");
let mut results = Vec::new();
for t in ts {
if terminate_flag
.map(|b| b.load(Ordering::Acquire))
.unwrap_or(false)
{
break;
}
let result = decode_test_result(env.run_test(t), t.expected_result());
results.push((t.get_name().into(), result));
}
results
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OutputFormat {
Pretty,
Json,
}
impl std::str::FromStr for OutputFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
use OutputFormat::*;
if s.eq_ignore_ascii_case("pretty") {
Ok(Pretty)
} else if s.eq_ignore_ascii_case("json") {
Ok(Json)
} else {
Err(())
}
}
}
pub fn collate_all_test_runs(
truns: &[(String, (TestConclusion, Option<TestError>))],
meta_data: ReportMetaData,
format: OutputFormat,
) {
match format {
OutputFormat::Pretty => {
let color = if atty::is(atty::Stream::Stdout) {
types::COLOR
} else {
types::NOCOLOR
};
println!();
println!(" =============================");
println!(" ===== AVL qual RESULTS ====");
println!(" =============================");
println!();
println!(" %---------------------------%");
println!(" os release: {}", meta_data.os_release);
println!(" cros release: {}", meta_data.cros_release);
println!(" chip name: {}", meta_data.chip_name);
println!(" system info: \n{}", meta_data.system_info);
println!(" bios info: \n{}", meta_data.bios_info);
println!(" %---------------------------%");
println!();
for trun in truns.iter() {
let (name, (result, error)) = trun;
if *result != TestConclusion::Pass {
println!(
" {} {}",
style!(format!(" <+> {} test:", name), color.bold, color),
style_dbg!(result, color.red, color)
);
match error {
None => {}
Some(e) => info!(" - {} failure details:\n{}", name, e.to_string()),
};
} else {
println!(
" {} {}",
style!(format!(" <+> {} test:", name), color.bold, color),
style_dbg!(result, color.green, color)
);
}
}
println!();
}
OutputFormat::Json => {
use serde_json::{Map, Value};
let mut all_pass = true;
let mut tests = Map::<String, Value>::new();
for (name, (result, error)) in truns {
let passed = *result == TestConclusion::Pass;
all_pass &= passed;
let error = match error {
Some(e) => Value::String(format!("{:#?}", e)),
None => Value::Null,
};
assert!(
!tests.contains_key(name),
"Found multiple tests named {:?}",
name
);
tests.insert(
name.into(),
json!({
"pass": passed,
"error": error,
}),
);
}
let json = json!({
"pass": all_pass,
"metadata": {
"os_release": meta_data.os_release,
"chip_name": meta_data.chip_name,
"system_info": meta_data.system_info,
"bios_info": meta_data.bios_info,
},
"tests": tests,
});
println!("{:#}", json);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn decode_test_result() {
use super::decode_test_result;
use super::TestConclusion::*;
let (result, err) = decode_test_result(Ok(()), Pass);
assert_eq!(result, Pass);
assert!(err.is_none());
let (result, err) = decode_test_result(Ok(()), Fail);
assert_eq!(result, UnexpectedPass);
assert!(err.is_none());
let (result, err) = decode_test_result(Err("broken".into()), Pass);
assert_eq!(result, UnexpectedFail);
assert!(err.is_some());
let (result, err) = decode_test_result(Err("broken".into()), Fail);
assert_eq!(result, Pass);
assert!(err.is_none());
}
#[test]
fn output_format_round_trip() {
use super::OutputFormat::{self, *};
assert_eq!(format!("{:?}", Pretty).parse::<OutputFormat>(), Ok(Pretty));
assert_eq!(format!("{:?}", Json).parse::<OutputFormat>(), Ok(Json));
}
}