diff options
Diffstat (limited to 'util/flashrom_tester/src/tester.rs')
-rw-r--r-- | util/flashrom_tester/src/tester.rs | 396 |
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) ); } } |