summaryrefslogtreecommitdiffstats
path: root/util/flashrom_tester/src/tester.rs
diff options
context:
space:
mode:
Diffstat (limited to 'util/flashrom_tester/src/tester.rs')
-rw-r--r--util/flashrom_tester/src/tester.rs396
1 files changed, 159 insertions, 237 deletions
diff --git a/util/flashrom_tester/src/tester.rs b/util/flashrom_tester/src/tester.rs
index 3150a4354..4629c2eb7 100644
--- a/util/flashrom_tester/src/tester.rs
+++ b/util/flashrom_tester/src/tester.rs
@@ -36,11 +36,14 @@
use super::rand_util;
use super::types;
use super::utils::{self, LayoutSizes};
-use flashrom::{FlashChip, Flashrom, FlashromCmd};
+use flashrom::FlashromError;
+use flashrom::{FlashChip, Flashrom};
use serde_json::json;
-use std::mem::MaybeUninit;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::Mutex;
// type-signature comes from the return type of lib.rs workers.
type TestError = Box<dyn std::error::Error>;
@@ -52,33 +55,38 @@ pub struct TestEnv<'a> {
///
/// Where possible, prefer to use methods on the TestEnv rather than delegating
/// to the raw flashrom functions.
- pub cmd: &'a FlashromCmd,
+ pub cmd: &'a dyn Flashrom,
layout: LayoutSizes,
- pub wp: WriteProtectState<'a, 'static>,
+ pub wp: WriteProtectState<'a>,
/// The path to a file containing the flash contents at test start.
- // TODO(pmarheine) migrate this to a PathBuf for clarity
- original_flash_contents: String,
+ original_flash_contents: PathBuf,
/// The path to a file containing flash-sized random data
- // TODO(pmarheine) make this a PathBuf too
- random_data: String,
+ 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 FlashromCmd) -> Result<Self, String> {
+ 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: chip_type,
- cmd: cmd,
+ chip_type,
+ cmd,
layout: utils::get_layout_sizes(rom_sz)?,
- wp: WriteProtectState::from_hardware(cmd)?,
+ 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),
};
info!("Stashing golden image for verification/recovery on completion");
- flashrom::read(&out.cmd, &out.original_flash_contents)?;
- flashrom::verify(&out.cmd, &out.original_flash_contents)?;
+ 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)
@@ -88,19 +96,10 @@ impl<'a> TestEnv<'a> {
}
pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult {
- let use_dut_control = self.chip_type == FlashChip::SERVO;
- if use_dut_control && flashrom::dut_ctrl_toggle_wp(false).is_err() {
- error!("failed to dispatch dut_ctrl_toggle_wp()!");
- }
-
let name = test.get_name();
info!("Beginning test: {}", name);
let out = test.run(self);
info!("Completed test: {}; result {:?}", name, out);
-
- if use_dut_control && flashrom::dut_ctrl_toggle_wp(true).is_err() {
- error!("failed to dispatch dut_ctrl_toggle_wp()!");
- }
out
}
@@ -112,7 +111,7 @@ impl<'a> TestEnv<'a> {
/// 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) -> &str {
+ pub fn random_data_file(&self) -> &Path {
&self.random_data
}
@@ -123,31 +122,36 @@ impl<'a> TestEnv<'a> {
/// 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 {
- flashrom::verify(&self.cmd, &self.original_flash_contents).is_ok()
+ 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<(), String> {
+ pub fn ensure_golden(&mut self) -> Result<(), FlashromError> {
self.wp.set_hw(false)?.set_sw(false)?;
- flashrom::write(&self.cmd, &self.original_flash_contents)
+ self.cmd.write_from_file(&self.original_flash_contents)?;
+ Ok(())
}
/// Attempt to erase the flash.
- pub fn erase(&self) -> Result<(), String> {
- flashrom::erase(self.cmd)
+ 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: &str) -> Result<(), String> {
- flashrom::verify(self.cmd, contents_path)
+ pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> {
+ self.cmd.verify_from_file(contents_path)?;
+ Ok(())
}
}
-impl Drop for TestEnv<'_> {
+impl<'a> Drop for TestEnv<'a> {
fn drop(&mut self) {
info!("Verifying flash remains unmodified");
if !self.is_golden() {
@@ -159,70 +163,41 @@ impl Drop for TestEnv<'_> {
}
}
+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` or `push`. When it goes out of scope, the write protects will be returned
+/// `set`. When it goes out of scope, the write protects will be returned
/// to the state they had then it was created.
-///
-/// The lifetime `'p` on this struct is the parent state it derives from; `'static`
-/// implies it is derived from hardware, while anything else is part of a stack
-/// created by `push`ing states. An initial state is always static, and the stack
-/// forms a lifetime chain `'static -> 'p -> 'p1 -> ... -> 'pn`.
-pub struct WriteProtectState<'a, 'p> {
- /// The parent state this derives from.
- ///
- /// If it's a root (gotten via `from_hardware`), then this is Hardware and the
- /// liveness flag will be reset on drop.
- initial: InitialState<'p>,
- // Tuples are (hardware, software)
- current: (bool, bool),
- cmd: &'a FlashromCmd,
-}
-
-enum InitialState<'p> {
- Hardware(bool, bool),
- Previous(&'p WriteProtectState<'p, 'p>),
+pub struct WriteProtectState<'a> {
+ current: WriteProtect,
+ initial: WriteProtect,
+ cmd: &'a dyn Flashrom,
+ fc: FlashChip,
}
-impl InitialState<'_> {
- fn get_target(&self) -> (bool, bool) {
- match self {
- InitialState::Hardware(hw, sw) => (*hw, *sw),
- InitialState::Previous(s) => s.current,
- }
- }
-}
-
-impl<'a> WriteProtectState<'a, 'static> {
+impl<'a> WriteProtectState<'a> {
/// Initialize a state from the current state of the hardware.
- ///
- /// Panics if there is already a live state derived from hardware. In such a situation the
- /// new state must be derived from the live one, or the live one must be dropped first.
- pub fn from_hardware(cmd: &'a FlashromCmd) -> Result<Self, String> {
- let mut lock = Self::get_liveness_lock()
- .lock()
- .expect("Somebody panicked during WriteProtectState init from hardware");
- if *lock {
- drop(lock); // Don't poison the lock
- panic!("Attempted to create a new WriteProtectState when one is already live");
- }
-
+ 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 hardware write protect: HW={} SW={}", hw, sw);
+ info!("Initial write protect state: HW={} SW={}", hw, sw);
- *lock = true;
Ok(WriteProtectState {
- initial: InitialState::Hardware(hw, sw),
- current: (hw, sw),
+ current: WriteProtect { hw, sw },
+ initial: WriteProtect { hw, sw },
cmd,
+ fc,
})
}
/// Get the actual hardware write protect state.
- fn get_hw(cmd: &FlashromCmd) -> Result<bool, String> {
- if cmd.fc.can_control_hw_wp() {
+ fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> {
+ if cmd.can_control_hw_wp() {
super::utils::get_hardware_wp()
} else {
Ok(false)
@@ -230,98 +205,78 @@ impl<'a> WriteProtectState<'a, 'static> {
}
/// Get the actual software write protect state.
- fn get_sw(cmd: &FlashromCmd) -> Result<bool, String> {
- flashrom::wp_status(cmd, true)
+ fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> {
+ let b = cmd.wp_status(true)?;
+ Ok(b)
}
-}
-impl<'a, 'p> WriteProtectState<'a, 'p> {
/// 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.fc.can_control_hw_wp()
+ self.cmd.can_control_hw_wp()
}
- /// Set the software write protect.
+ /// 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!("request={}, current={}", enable, self.current.1);
- if self.current.1 != enable {
- flashrom::wp_toggle(self.cmd, /* en= */ enable)?;
- self.current.1 = enable;
+ 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())?;
}
- Ok(self)
- }
-
- /// Set the hardware write protect.
- pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> {
- if self.current.0 != enable {
- if self.can_control_hw_wp() {
- super::utils::toggle_hw_wp(/* dis= */ !enable)?;
- self.current.0 = enable;
- } else if enable {
- info!(
- "Ignoring attempt to enable hardware WP with {:?} programmer",
- self.cmd.fc
- );
- }
+ 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)
}
- Ok(self)
}
- /// Stack a new write protect state on top of the current one.
- ///
- /// This is useful if you need to temporarily make a change to write protection:
- ///
- /// ```no_run
- /// # fn main() -> Result<(), String> {
- /// # let cmd: flashrom::FlashromCmd = unimplemented!();
- /// let wp = flashrom_tester::tester::WriteProtectState::from_hardware(&cmd)?;
- /// {
- /// let mut wp = wp.push();
- /// wp.set_sw(false)?;
- /// // Do something with software write protect disabled
- /// }
- /// // Now software write protect returns to its original state, even if
- /// // set_sw() failed.
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// This returns a new state which restores the original when it is dropped- the new state
- /// refers to the old, so the compiler enforces that states are disposed of in the reverse
- /// order of their creation and correctly restore the original state.
- pub fn push<'p1>(&'p1 self) -> WriteProtectState<'a, 'p1> {
- WriteProtectState {
- initial: InitialState::Previous(self),
- current: self.current,
- cmd: self.cmd,
+ // 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)
}
}
- fn get_liveness_lock() -> &'static Mutex<bool> {
- static INIT: std::sync::Once = std::sync::Once::new();
- /// Value becomes true when there is a live WriteProtectState derived `from_hardware`,
- /// blocking duplicate initialization.
- ///
- /// This is required because hardware access is not synchronized; it's possible to leave the
- /// hardware in an unintended state by creating a state handle from it, modifying the state,
- /// creating another handle from the hardware then dropping the first handle- then on drop
- /// of the second handle it will restore the state to the modified one rather than the initial.
- ///
- /// This flag ensures that a duplicate root state cannot be created.
- ///
- /// This is a Mutex<bool> rather than AtomicBool because acquiring the flag needs to perform
- /// several operations that may themselves fail- acquisitions must be fully synchronized.
- static mut LIVE_FROM_HARDWARE: MaybeUninit<Mutex<bool>> = MaybeUninit::uninit();
-
- unsafe {
- INIT.call_once(|| {
- LIVE_FROM_HARDWARE.as_mut_ptr().write(Mutex::new(false));
- });
- &*LIVE_FROM_HARDWARE.as_ptr()
+ /// 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.
@@ -329,91 +284,30 @@ impl<'a, 'p> WriteProtectState<'a, 'p> {
/// 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> {
- unsafe {
- let out = self.drop_internal();
- // We just ran drop, don't do it again
- std::mem::forget(self);
- out
- }
+ let out = self.drop_internal();
+ // We just ran drop, don't do it again
+ std::mem::forget(self);
+ out
}
- /// Internal Drop impl.
- ///
- /// This is unsafe because it effectively consumes self when clearing the
- /// liveness lock. Callers must be able to guarantee that self will be forgotten
- /// if the state was constructed from hardware in order to uphold the liveness
- /// invariant (that only a single state constructed from hardware exists at any
- /// time).
- unsafe fn drop_internal(&mut self) -> Result<(), String> {
- let lock = match self.initial {
- InitialState::Hardware(_, _) => Some(
- Self::get_liveness_lock()
- .lock()
- .expect("Somebody panicked during WriteProtectState drop from hardware"),
- ),
- _ => None,
- };
- let (hw, sw) = self.initial.get_target();
-
- fn enable_str(enable: bool) -> &'static str {
- if enable {
- "en"
- } else {
- "dis"
- }
- }
-
+ /// 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 sw != self.current.1 {
- // Is the hw wp currently enabled?
- if self.current.0 {
- super::utils::toggle_hw_wp(/* dis= */ true).map_err(|e| {
- format!(
- "Failed to {}able hardware write protect: {}",
- enable_str(false),
- e
- )
- })?;
- }
- flashrom::wp_toggle(self.cmd, /* en= */ sw).map_err(|e| {
- format!(
- "Failed to {}able software write protect: {}",
- enable_str(sw),
- e
- )
- })?;
- }
-
- assert!(
- self.cmd.fc.can_control_hw_wp() || (!self.current.0 && !hw),
- "HW WP must be disabled if it cannot be controlled"
- );
- if hw != self.current.0 {
- super::utils::toggle_hw_wp(/* dis= */ !hw).map_err(|e| {
- format!(
- "Failed to {}able hardware write protect: {}",
- enable_str(hw),
- e
- )
- })?;
+ 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)?;
- if let Some(mut lock) = lock {
- // Initial state was constructed via from_hardware, now we can clear the liveness
- // lock since reset is complete.
- *lock = false;
- }
Ok(())
}
}
-impl<'a, 'p> Drop for WriteProtectState<'a, 'p> {
- /// Sets both write protects to the state they had when this state was created.
- ///
- /// Panics on error because there is no mechanism to report errors in Drop.
+impl<'a> Drop for WriteProtectState<'a> {
fn drop(&mut self) {
- unsafe { self.drop_internal() }.expect("Error while dropping WriteProtectState")
+ self.drop_internal()
+ .expect("Error while dropping WriteProtectState")
}
}
@@ -452,7 +346,7 @@ impl<T: TestCase + ?Sized> TestCase for &T {
}
#[allow(dead_code)]
-#[derive(Copy, Clone, PartialEq, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum TestConclusion {
Pass,
Fail,
@@ -463,6 +357,7 @@ pub enum TestConclusion {
pub struct ReportMetaData {
pub chip_name: String,
pub os_release: String,
+ pub cros_release: String,
pub system_info: String,
pub bios_info: String,
}
@@ -477,17 +372,38 @@ fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion,
}
}
+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: &FlashromCmd,
+ 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).expect("Failed to set up test environment");
+ 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 {
@@ -504,7 +420,7 @@ where
results
}
-#[derive(Debug, PartialEq, Clone, Copy)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OutputFormat {
Pretty,
Json,
@@ -533,6 +449,11 @@ pub fn collate_all_test_runs(
) {
match format {
OutputFormat::Pretty => {
+ let color = if atty::is(atty::Stream::Stdout) {
+ types::COLOR
+ } else {
+ types::NOCOLOR
+ };
println!();
println!(" =============================");
println!(" ===== AVL qual RESULTS ====");
@@ -540,6 +461,7 @@ pub fn collate_all_test_runs(
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);
@@ -551,8 +473,8 @@ pub fn collate_all_test_runs(
if *result != TestConclusion::Pass {
println!(
" {} {}",
- style!(format!(" <+> {} test:", name), types::BOLD),
- style_dbg!(result, types::RED)
+ style!(format!(" <+> {} test:", name), color.bold, color),
+ style_dbg!(result, color.red, color)
);
match error {
None => {}
@@ -561,8 +483,8 @@ pub fn collate_all_test_runs(
} else {
println!(
" {} {}",
- style!(format!(" <+> {} test:", name), types::BOLD),
- style_dbg!(result, types::GREEN)
+ style!(format!(" <+> {} test:", name), color.bold, color),
+ style_dbg!(result, color.green, color)
);
}
}