summaryrefslogtreecommitdiffstats
path: root/util/flashrom_tester
diff options
context:
space:
mode:
Diffstat (limited to 'util/flashrom_tester')
-rw-r--r--util/flashrom_tester/.cargo/config.toml2
-rw-r--r--util/flashrom_tester/Cargo.toml13
-rw-r--r--util/flashrom_tester/flashrom/Cargo.toml5
-rw-r--r--util/flashrom_tester/flashrom/src/cmd.rs489
-rw-r--r--util/flashrom_tester/flashrom/src/flashromlib.rs184
-rw-r--r--util/flashrom_tester/flashrom/src/lib.rs372
-rw-r--r--util/flashrom_tester/src/cros_sysinfo.rs24
-rw-r--r--util/flashrom_tester/src/logger.rs93
-rw-r--r--util/flashrom_tester/src/main.rs106
-rw-r--r--util/flashrom_tester/src/rand_util.rs11
-rw-r--r--util/flashrom_tester/src/tester.rs405
-rw-r--r--util/flashrom_tester/src/tests.rs185
-rw-r--r--util/flashrom_tester/src/types.rs39
-rw-r--r--util/flashrom_tester/src/utils.rs155
14 files changed, 1119 insertions, 964 deletions
diff --git a/util/flashrom_tester/.cargo/config.toml b/util/flashrom_tester/.cargo/config.toml
new file mode 100644
index 000000000..8af59dd8c
--- /dev/null
+++ b/util/flashrom_tester/.cargo/config.toml
@@ -0,0 +1,2 @@
+[env]
+RUST_TEST_THREADS = "1"
diff --git a/util/flashrom_tester/Cargo.toml b/util/flashrom_tester/Cargo.toml
index 50f2c4a7d..e6ed9c044 100644
--- a/util/flashrom_tester/Cargo.toml
+++ b/util/flashrom_tester/Cargo.toml
@@ -3,6 +3,8 @@ name = "flashrom_tester"
version = "1.6.0"
authors = ["Edward O'Callaghan <quasisec@chromium.org>",
"Peter Marheine <pmarheine@chromium.org>"]
+description = "A tool to verify flashrom and flash chip behaviour."
+license = "GPL-2.0-only"
edition = "2018"
build = "build.rs"
@@ -14,17 +16,22 @@ name = "flashrom_tester"
required-features = ["cli"]
[dependencies]
-built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] }
+atty = "0.2"
+built = { version = "0.5", features = ["chrono"] }
chrono = { version = "0.4", optional = true }
clap = { version = "2.33", default-features = false, optional = true }
flashrom = { path = "flashrom/" }
+libc = "0.2"
log = { version = "0.4", features = ["std"] }
rand = "0.6.4"
serde_json = "1"
-sys-info = "0.5.7"
+sys-info = "0.9"
[build-dependencies]
-built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] }
+built = { version = "0.5", features = ["chrono"] }
+
+[dev-dependencies]
+gag = "1"
[features]
# Features required to build the CLI binary but not the library
diff --git a/util/flashrom_tester/flashrom/Cargo.toml b/util/flashrom_tester/flashrom/Cargo.toml
index 27216cbde..4d4fc2fea 100644
--- a/util/flashrom_tester/flashrom/Cargo.toml
+++ b/util/flashrom_tester/flashrom/Cargo.toml
@@ -3,7 +3,10 @@ name = "flashrom"
version = "1.0.0"
authors = ["Edward O'Callaghan <quasisec@chromium.org>",
"Peter Marheine <pmarheine@chromium.org>"]
+description = "Flashrom abstraction for the flashrom_tester tool."
+license = "GPL-2.0-only"
edition = "2018"
[dependencies]
-log = "0.4" \ No newline at end of file
+log = "0.4"
+libflashrom = { path = "../../../bindings/rust/libflashrom" }
diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs
index 3fd2ac04d..1f13a8ede 100644
--- a/util/flashrom_tester/flashrom/src/cmd.rs
+++ b/util/flashrom_tester/flashrom/src/cmd.rs
@@ -33,11 +33,57 @@
// Software Foundation.
//
-use crate::{FlashChip, FlashromError, FlashromOpt};
+use crate::{FlashChip, FlashromError};
-use std::process::Command;
+use std::{
+ ffi::{OsStr, OsString},
+ path::Path,
+ process::Command,
+};
-#[derive(PartialEq, Debug)]
+#[derive(Default)]
+pub struct FlashromOpt<'a> {
+ pub wp_opt: WPOpt,
+ pub io_opt: Option<IOOpt<'a>>,
+
+ pub flash_name: bool, // --flash-name
+ pub verbose: bool, // -V
+}
+
+#[derive(Default)]
+pub struct WPOpt {
+ pub range: Option<(i64, i64)>, // --wp-range x0 x1
+ pub status: bool, // --wp-status
+ pub list: bool, // --wp-list
+ pub enable: bool, // --wp-enable
+ pub disable: bool, // --wp-disable
+}
+
+pub enum OperationArgs<'a> {
+ /// The file is the whole chip.
+ EntireChip(&'a Path),
+ /// File is the size of the full chip, limited to a single named region.
+ ///
+ /// The required path is the file to use, and the optional path is a layout file
+ /// specifying how to locate regions (if unspecified, flashrom will attempt
+ /// to discover the layout itself).
+ FullFileRegion(&'a str, &'a Path, Option<&'a Path>),
+ /// File is the size of the single named region only.
+ ///
+ /// The required path is the file to use, and the optional path is a layout file
+ /// specifying how to locate regions (if unspecified, flashrom will attempt
+ /// to discover the layout itself).
+ RegionFileRegion(&'a str, &'a Path, Option<&'a Path>), // The file contains only the region
+}
+
+pub enum IOOpt<'a> {
+ Read(OperationArgs<'a>), // -r <file>
+ Write(OperationArgs<'a>), // -w <file>
+ Verify(OperationArgs<'a>), // -v <file>
+ Erase, // -E
+}
+
+#[derive(PartialEq, Eq, Debug)]
pub struct FlashromCmd {
pub path: String,
pub fc: FlashChip,
@@ -54,33 +100,204 @@ fn flashrom_extract_size(stdout: &str) -> Result<i64, FlashromError> {
.last()
.map(str::parse::<i64>)
{
- None => return Err("Found no purely-numeric lines in flashrom output".into()),
+ None => Err("Found no purely-numeric lines in flashrom output".into()),
Some(Err(e)) => {
- return Err(format!(
- "Failed to parse flashrom size output as integer: {}",
- e
- ))
+ Err(format!("Failed to parse flashrom size output as integer: {}", e).into())
}
Some(Ok(sz)) => Ok(sz),
}
}
+impl FlashromCmd {
+ fn dispatch(
+ &self,
+ fropt: FlashromOpt,
+ debug_name: &str,
+ ) -> Result<(String, String), FlashromError> {
+ let params = flashrom_decode_opts(fropt);
+ flashrom_dispatch(self.path.as_str(), &params, self.fc, debug_name)
+ }
+}
+
impl crate::Flashrom for FlashromCmd {
fn get_size(&self) -> Result<i64, FlashromError> {
- let (stdout, _) = flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc)?;
- let sz = String::from_utf8_lossy(&stdout);
+ let (stdout, _) =
+ flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc, "get_size")?;
+ flashrom_extract_size(&stdout)
+ }
+
+ fn name(&self) -> Result<(String, String), FlashromError> {
+ let opts = FlashromOpt {
+ flash_name: true,
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "name")?;
+ match extract_flash_name(&stdout) {
+ None => Err("Didn't find chip vendor/name in flashrom output".into()),
+ Some((vendor, name)) => Ok((vendor.into(), name.into())),
+ }
+ }
- flashrom_extract_size(&sz)
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Write(OperationArgs::FullFileRegion(
+ region,
+ path,
+ Some(layout),
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "write_file_with_layout")?;
+ Ok(true)
}
- fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let params = flashrom_decode_opts(fropt);
- flashrom_dispatch(self.path.as_str(), &params, self.fc)
+ fn wp_range(&self, range: (i64, i64), en: bool) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ enable: en,
+ disable: !en,
+ range: Some(range),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "wp_range")?;
+ Ok(true)
+ }
+
+ fn wp_list(&self) -> Result<String, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ list: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "wp_list")?;
+ if stdout.is_empty() {
+ return Err(
+ "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list"
+ .into(),
+ );
+ }
+ Ok(stdout)
+ }
+
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError> {
+ let status = if en { "en" } else { "dis" };
+ info!("See if chip write protect is {}abled", status);
+
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ status: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "wp_status")?;
+ let s = std::format!("write protect is {}abled", status);
+ Ok(stdout.contains(&s))
+ }
+
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> {
+ let range = if en {
+ let rom_sz: i64 = self.get_size()?;
+ (0, rom_sz) // (start, len)
+ } else {
+ (0, 0)
+ };
+ self.wp_range(range, en)?;
+ let status = if en { "en" } else { "dis" };
+ match self.wp_status(true) {
+ Ok(_ret) => {
+ info!("Successfully {}abled write-protect", status);
+ Ok(true)
+ }
+ Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e).into()),
+ }
+ }
+
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Read(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "read_into_file")?;
+ Ok(())
+ }
+
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Read(OperationArgs::RegionFileRegion(
+ region, path, None,
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "read_region_into_file")?;
+ Ok(())
+ }
+
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Write(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "write_from_file")?;
+ Ok(())
+ }
+
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Verify(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "verify_from_file")?;
+ Ok(())
+ }
+
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Verify(OperationArgs::RegionFileRegion(
+ region, path, None,
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "verify_region_from_file")?;
+ Ok(())
+ }
+
+ fn erase(&self) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Erase),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "erase")?;
+ Ok(())
+ }
+
+ fn can_control_hw_wp(&self) -> bool {
+ self.fc.can_control_hw_wp()
}
}
-fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> {
- let mut params = Vec::<String>::new();
+fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<OsString> {
+ let mut params = Vec::<OsString>::new();
// ------------ WARNING !!! ------------
// each param must NOT contain spaces!
@@ -89,134 +306,144 @@ fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> {
// wp_opt
if opts.wp_opt.range.is_some() {
let (x0, x1) = opts.wp_opt.range.unwrap();
- params.push("--wp-range".to_string());
- params.push(hex_string(x0));
- params.push(hex_string(x1));
+ params.push("--wp-range".into());
+ params.push(hex_range_string(x0, x1).into());
}
if opts.wp_opt.status {
- params.push("--wp-status".to_string());
+ params.push("--wp-status".into());
} else if opts.wp_opt.list {
- params.push("--wp-list".to_string());
+ params.push("--wp-list".into());
} else if opts.wp_opt.enable {
- params.push("--wp-enable".to_string());
+ params.push("--wp-enable".into());
} else if opts.wp_opt.disable {
- params.push("--wp-disable".to_string());
+ params.push("--wp-disable".into());
}
// io_opt
- if opts.io_opt.read.is_some() {
- params.push("-r".to_string());
- params.push(opts.io_opt.read.unwrap().to_string());
- } else if opts.io_opt.write.is_some() {
- params.push("-w".to_string());
- params.push(opts.io_opt.write.unwrap().to_string());
- } else if opts.io_opt.verify.is_some() {
- params.push("-v".to_string());
- params.push(opts.io_opt.verify.unwrap().to_string());
- } else if opts.io_opt.erase {
- params.push("-E".to_string());
- }
-
- // misc_opt
- if opts.layout.is_some() {
- params.push("-l".to_string());
- params.push(opts.layout.unwrap().to_string());
+ fn add_operation_args(opts: OperationArgs, params: &mut Vec<OsString>) {
+ let (file, region, layout) = match opts {
+ OperationArgs::EntireChip(file) => (Some(file), None, None),
+ OperationArgs::FullFileRegion(region, file, layout) => {
+ (Some(file), Some(region.to_string()), layout)
+ }
+ OperationArgs::RegionFileRegion(region, file, layout) => (
+ None,
+ Some(format!("{region}:{}", file.to_string_lossy())),
+ layout,
+ ),
+ };
+ if let Some(file) = file {
+ params.push(file.into())
+ }
+ if let Some(region) = region {
+ params.push("--include".into());
+ params.push(region.into())
+ }
+ if let Some(layout) = layout {
+ params.push("--layout".into());
+ params.push(layout.into())
+ }
}
- if opts.image.is_some() {
- params.push("-i".to_string());
- params.push(opts.image.unwrap().to_string());
+ if let Some(io) = opts.io_opt {
+ match io {
+ IOOpt::Read(args) => {
+ params.push("-r".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Write(args) => {
+ params.push("-w".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Verify(args) => {
+ params.push("-v".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Erase => params.push("-E".into()),
+ }
}
+ // misc_opt
if opts.flash_name {
- params.push("--flash-name".to_string());
- }
- if opts.ignore_fmap {
- params.push("--ignore-fmap".to_string());
+ params.push("--flash-name".into());
}
if opts.verbose {
- params.push("-V".to_string());
+ params.push("-V".into());
}
params
}
-fn flashrom_dispatch<S: AsRef<str>>(
+fn flashrom_dispatch<S: AsRef<OsStr>>(
path: &str,
params: &[S],
fc: FlashChip,
-) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ debug_name: &str,
+) -> Result<(String, String), FlashromError> {
// from man page:
// ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] '
- let mut args: Vec<&str> = vec!["-p", FlashChip::to(fc)];
+ let mut args: Vec<&OsStr> = vec![OsStr::new("-p"), OsStr::new(FlashChip::to(fc))];
args.extend(params.iter().map(S::as_ref));
info!("flashrom_dispatch() running: {} {:?}", path, args);
let output = match Command::new(path).args(&args).output() {
Ok(x) => x,
- Err(e) => return Err(format!("Failed to run flashrom: {}", e)),
+ Err(e) => return Err(format!("Failed to run flashrom: {}", e).into()),
};
+
+ let stdout = String::from_utf8_lossy(output.stdout.as_slice());
+ let stderr = String::from_utf8_lossy(output.stderr.as_slice());
+ debug!("{}()'stdout: {}.", debug_name, stdout);
+ debug!("{}()'stderr: {}.", debug_name, stderr);
+
if !output.status.success() {
// There is two cases on failure;
// i. ) A bad exit code,
// ii.) A SIG killed us.
match output.status.code() {
Some(code) => {
- return Err(format!(
- "{}\nExited with error code: {}",
- String::from_utf8_lossy(&output.stderr),
- code
- ));
+ return Err(format!("{}\nExited with error code: {}", stderr, code).into());
}
None => return Err("Process terminated by a signal".into()),
}
}
- Ok((output.stdout, output.stderr))
-}
-
-pub fn dut_ctrl_toggle_wp(en: bool) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let args = if en {
- ["fw_wp_en:off", "fw_wp:on"]
- } else {
- ["fw_wp_en:on", "fw_wp:off"]
- };
- dut_ctrl(&args)
+ Ok((stdout.into(), stderr.into()))
}
-pub fn dut_ctrl_servo_type() -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let args = ["servo_type"];
- dut_ctrl(&args)
+fn hex_range_string(s: i64, l: i64) -> String {
+ format!("{:#08X},{:#08X}", s, l)
}
-fn dut_ctrl(args: &[&str]) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let output = match Command::new("dut-control").args(args).output() {
- Ok(x) => x,
- Err(e) => return Err(format!("Failed to run dut-control: {}", e)),
- };
- if !output.status.success() {
- // There is two cases on failure;
- // i. ) A bad exit code,
- // ii.) A SIG killed us.
- match output.status.code() {
- Some(code) => {
- return Err(format!("Exited with error code: {}", code).into());
- }
- None => return Err("Process terminated by a signal".into()),
+/// Get a flash vendor and name from the first matching line of flashrom output.
+///
+/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name.
+/// This is usually the last line of output.
+fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> {
+ for line in stdout.lines() {
+ if !line.starts_with("vendor=\"") {
+ continue;
}
- }
- Ok((output.stdout, output.stderr))
-}
+ let tail = line.trim_start_matches("vendor=\"");
+ let mut split = tail.splitn(2, "\" name=\"");
+ let vendor = split.next();
+ let name = split.next().map(|s| s.trim_end_matches('"'));
-fn hex_string(v: i64) -> String {
- format!("{:#08X}", v).to_string()
+ match (vendor, name) {
+ (Some(v), Some(n)) => return Some((v, n)),
+ _ => continue,
+ }
+ }
+ None
}
#[cfg(test)]
mod tests {
+ use std::path::Path;
+
use super::flashrom_decode_opts;
- use crate::{FlashromOpt, IOOpt, WPOpt};
+ use super::{FlashromOpt, IOOpt, WPOpt};
#[test]
fn decode_wp_opt() {
@@ -237,7 +464,7 @@ mod tests {
status: true,
..Default::default()
},
- &["--wp-range", "0x000000", "0x0004D2", "--wp-status"],
+ &["--wp-range", "0x000000,0x0004D2", "--wp-status"],
);
test_wp_opt(
WPOpt {
@@ -267,7 +494,7 @@ mod tests {
fn test_io_opt(opts: IOOpt, expected: &[&str]) {
assert_eq!(
flashrom_decode_opts(FlashromOpt {
- io_opt: opts,
+ io_opt: Some(opts),
..Default::default()
}),
expected
@@ -275,62 +502,48 @@ mod tests {
}
test_io_opt(
- IOOpt {
- read: Some("foo.bin"),
- ..Default::default()
- },
+ IOOpt::Read(crate::cmd::OperationArgs::EntireChip(Path::new("foo.bin"))),
&["-r", "foo.bin"],
);
test_io_opt(
- IOOpt {
- write: Some("bar.bin"),
- ..Default::default()
- },
+ IOOpt::Write(crate::cmd::OperationArgs::EntireChip(Path::new("bar.bin"))),
&["-w", "bar.bin"],
);
test_io_opt(
- IOOpt {
- verify: Some("/tmp/baz.bin"),
- ..Default::default()
- },
- &["-v", "/tmp/baz.bin"],
+ IOOpt::Verify(crate::cmd::OperationArgs::EntireChip(Path::new("baz.bin"))),
+ &["-v", "baz.bin"],
);
+ test_io_opt(IOOpt::Erase, &["-E"]);
test_io_opt(
- IOOpt {
- erase: true,
- ..Default::default()
- },
- &["-E"],
+ IOOpt::Read(crate::cmd::OperationArgs::FullFileRegion(
+ "RO",
+ Path::new("foo.bin"),
+ Some(Path::new("baz.bin")),
+ )),
+ &["-r", "foo.bin", "--include", "RO", "--layout", "baz.bin"],
);
+
+ test_io_opt(
+ IOOpt::Read(crate::cmd::OperationArgs::RegionFileRegion(
+ "foo",
+ Path::new("bar.bin"),
+ None,
+ )),
+ &["-r", "--include", "foo:bar.bin"],
+ )
}
#[test]
fn decode_misc() {
//use Default::default;
- assert_eq!(
- flashrom_decode_opts(FlashromOpt {
- layout: Some("TestLayout"),
- ..Default::default()
- }),
- &["-l", "TestLayout"]
- );
-
- assert_eq!(
- flashrom_decode_opts(FlashromOpt {
- image: Some("TestImage"),
- ..Default::default()
- }),
- &["-i", "TestImage"]
- );
assert_eq!(
flashrom_decode_opts(FlashromOpt {
flash_name: true,
- ignore_fmap: true,
verbose: true,
..Default::default()
}),
- &["--flash-name", "--ignore-fmap", "-V"]
+ &["--flash-name", "-V"]
);
}
@@ -352,4 +565,26 @@ mod tests {
Err("Found no purely-numeric lines in flashrom output".into())
);
}
+
+ #[test]
+ fn extract_flash_name() {
+ use super::extract_flash_name;
+
+ assert_eq!(
+ extract_flash_name(
+ "coreboot table found at 0x7cc13000\n\
+ Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\
+ vendor=\"Winbond\" name=\"W25Q64DW\"\n"
+ ),
+ Some(("Winbond", "W25Q64DW"))
+ );
+
+ assert_eq!(
+ extract_flash_name(
+ "vendor name is TEST\n\
+ Something failed!"
+ ),
+ None
+ )
+ }
}
diff --git a/util/flashrom_tester/flashrom/src/flashromlib.rs b/util/flashrom_tester/flashrom/src/flashromlib.rs
new file mode 100644
index 000000000..5e1747b1b
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/flashromlib.rs
@@ -0,0 +1,184 @@
+// Copyright 2022, 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 libflashrom::{Chip, Programmer};
+
+use std::{cell::RefCell, convert::TryFrom, fs, path::Path};
+
+use crate::{FlashChip, FlashromError};
+
+#[derive(Debug)]
+pub struct FlashromLib {
+ // RefCell required here to keep Flashrom trait immutable.
+ // Cant make Flashrom methods mut because WriteProtectState
+ // and TestEnv both keep a reference.
+ pub flashrom: RefCell<Chip>,
+ pub fc: FlashChip,
+}
+
+impl FlashromLib {
+ pub fn new(fc: FlashChip, log_level: libflashrom::flashrom_log_level) -> FlashromLib {
+ libflashrom::set_log_level(Some(log_level));
+ let (programmer, options) = FlashChip::to_split(fc);
+ let flashrom = Chip::new(Programmer::new(programmer, options).unwrap(), None).unwrap();
+ FlashromLib {
+ flashrom: RefCell::new(flashrom),
+ fc,
+ }
+ }
+}
+
+impl crate::Flashrom for FlashromLib {
+ fn get_size(&self) -> Result<i64, FlashromError> {
+ Ok(self.flashrom.borrow().get_size() as i64)
+ }
+
+ fn name(&self) -> Result<(String, String), FlashromError> {
+ Ok(("not".to_string(), "implemented".to_string()))
+ }
+
+ fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError> {
+ let mut cfg = libflashrom::WriteProtectCfg::new()?;
+ let start = usize::try_from(range.0).unwrap();
+ let len = usize::try_from(range.1).unwrap();
+ cfg.set_range::<std::ops::Range<usize>>(start..(start + len));
+ cfg.set_mode(if wp_enable {
+ libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_HARDWARE
+ } else {
+ libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED
+ });
+ self.flashrom.borrow_mut().set_wp(&cfg)?;
+ Ok(true)
+ }
+
+ fn wp_list(&self) -> Result<String, FlashromError> {
+ let ranges = self.flashrom.borrow_mut().get_wp_ranges()?;
+ Ok(format!("{:?}", ranges))
+ }
+
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError> {
+ let ret = self
+ .flashrom
+ .borrow_mut()
+ .get_wp()
+ .map_err(|e| format!("{:?}", e))?
+ .get_mode();
+ if en {
+ Ok(ret != libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED)
+ } else {
+ Ok(ret == libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED)
+ }
+ }
+
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> {
+ let range = if en { (0, self.get_size()?) } else { (0, 0) };
+ self.wp_range(range, en)
+ }
+
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let buf = self.flashrom.borrow_mut().image_read(None)?;
+ fs::write(path, buf).map_err(|error| error.to_string())?;
+ Ok(())
+ }
+
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?;
+ layout.include_region(region)?;
+ let range = layout.get_region_range(region)?;
+ let buf = self.flashrom.borrow_mut().image_read(None)?;
+ fs::write(path, &buf[range]).map_err(|error| error.to_string())?;
+ Ok(())
+ }
+
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let mut buf = fs::read(path).map_err(|error| error.to_string())?;
+ self.flashrom.borrow_mut().image_write(&mut buf, None)?;
+ Ok(())
+ }
+
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError> {
+ let buf = fs::read(layout).map_err(|error| error.to_string())?;
+ let buf = String::from_utf8(buf).unwrap();
+ let mut layout: libflashrom::Layout = buf
+ .parse()
+ .map_err(|e: Box<dyn std::error::Error>| e.to_string())?;
+ layout.include_region(region)?;
+ let mut buf = fs::read(path).map_err(|error| error.to_string())?;
+ self.flashrom
+ .borrow_mut()
+ .image_write(&mut buf, Some(layout))?;
+ Ok(true)
+ }
+
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let buf = fs::read(path).map_err(|error| error.to_string())?;
+ self.flashrom.borrow_mut().image_verify(&buf, None)?;
+ Ok(())
+ }
+
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?;
+ layout.include_region(region)?;
+ let range = layout.get_region_range(region)?;
+ let region_data = fs::read(path).map_err(|error| error.to_string())?;
+ if region_data.len() != range.len() {
+ return Err(format!(
+ "verify region range ({}) does not match provided file size ({})",
+ range.len(),
+ region_data.len()
+ )
+ .into());
+ }
+ let mut buf = vec![0; self.get_size()? as usize];
+ buf[range].copy_from_slice(&region_data);
+ self.flashrom
+ .borrow_mut()
+ .image_verify(&buf, Some(layout))?;
+ Ok(())
+ }
+
+ fn erase(&self) -> Result<(), FlashromError> {
+ self.flashrom.borrow_mut().erase()?;
+ Ok(())
+ }
+
+ fn can_control_hw_wp(&self) -> bool {
+ self.fc.can_control_hw_wp()
+ }
+}
diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs
index 734e3ff4a..41393e841 100644
--- a/util/flashrom_tester/flashrom/src/lib.rs
+++ b/util/flashrom_tester/flashrom/src/lib.rs
@@ -37,36 +37,41 @@
extern crate log;
mod cmd;
+mod flashromlib;
-pub use cmd::{dut_ctrl_toggle_wp, FlashromCmd};
+use std::{error, fmt, path::Path};
-#[derive(Copy, Clone, PartialEq, Debug)]
+pub use cmd::FlashromCmd;
+pub use flashromlib::FlashromLib;
+
+pub use libflashrom::{
+ flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR,
+ FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN,
+};
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum FlashChip {
- EC,
HOST,
- SERVO,
- DEDIPROG,
}
impl FlashChip {
pub fn from(s: &str) -> Result<FlashChip, &str> {
- let r = match s {
- "ec" => Ok(FlashChip::EC),
+ match s {
"host" => Ok(FlashChip::HOST),
- "servo" => Ok(FlashChip::SERVO),
- "dediprog" => Ok(FlashChip::DEDIPROG),
_ => Err("cannot convert str to enum"),
- };
- return r;
+ }
}
pub fn to(fc: FlashChip) -> &'static str {
- let r = match fc {
- FlashChip::EC => "ec",
+ match fc {
FlashChip::HOST => "host",
- FlashChip::SERVO => "ft2231_spi:type=servo-v2",
- FlashChip::DEDIPROG => "dediprog",
- };
- return r;
+ }
+ }
+
+ /// Return the programmer string and optional programmer options
+ pub fn to_split(fc: FlashChip) -> (&'static str, Option<&'static str>) {
+ let programmer = FlashChip::to(fc);
+ let mut bits = programmer.splitn(2, ':');
+ (bits.next().unwrap(), bits.next())
}
/// Return whether the hardware write protect signal can be controlled.
@@ -75,307 +80,86 @@ impl FlashChip {
/// disabled.
pub fn can_control_hw_wp(&self) -> bool {
match self {
- FlashChip::HOST | FlashChip::EC => true,
- FlashChip::SERVO | FlashChip::DEDIPROG => false,
+ FlashChip::HOST => true,
}
}
}
-pub type FlashromError = String;
-
-#[derive(Default)]
-pub struct FlashromOpt<'a> {
- pub wp_opt: WPOpt,
- pub io_opt: IOOpt<'a>,
-
- pub layout: Option<&'a str>, // -l <file>
- pub image: Option<&'a str>, // -i <name>
-
- pub flash_name: bool, // --flash-name
- pub ignore_fmap: bool, // --ignore-fmap
- pub verbose: bool, // -V
-}
-
-#[derive(Default)]
-pub struct WPOpt {
- pub range: Option<(i64, i64)>, // --wp-range x0 x1
- pub status: bool, // --wp-status
- pub list: bool, // --wp-list
- pub enable: bool, // --wp-enable
- pub disable: bool, // --wp-disable
+#[derive(Debug, PartialEq, Eq)]
+pub struct FlashromError {
+ msg: String,
}
-#[derive(Default)]
-pub struct IOOpt<'a> {
- pub read: Option<&'a str>, // -r <file>
- pub write: Option<&'a str>, // -w <file>
- pub verify: Option<&'a str>, // -v <file>
- pub erase: bool, // -E
-}
-
-pub trait Flashrom {
- fn get_size(&self) -> Result<i64, FlashromError>;
- fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError>;
-}
-
-pub fn name(cmd: &cmd::FlashromCmd) -> Result<(String, String), FlashromError> {
- let opts = FlashromOpt {
- io_opt: IOOpt {
- ..Default::default()
- },
-
- flash_name: true,
-
- ..Default::default()
- };
-
- let (stdout, stderr) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- let eoutput = String::from_utf8_lossy(stderr.as_slice());
- debug!("name()'stdout: {:#?}.", output);
- debug!("name()'stderr: {:#?}.", eoutput);
-
- match extract_flash_name(&output) {
- None => Err("Didn't find chip vendor/name in flashrom output".into()),
- Some((vendor, name)) => Ok((vendor.into(), name.into())),
- }
-}
-
-/// Get a flash vendor and name from the first matching line of flashrom output.
-///
-/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name.
-/// This is usually the last line of output.
-fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> {
- for line in stdout.lines() {
- if !line.starts_with("vendor=\"") {
- continue;
- }
-
- let tail = line.trim_start_matches("vendor=\"");
- let mut split = tail.splitn(2, "\" name=\"");
- let vendor = split.next();
- let name = split.next().map(|s| s.trim_end_matches('"'));
-
- match (vendor, name) {
- (Some(v), Some(n)) => return Some((v, n)),
- _ => continue,
- }
+impl fmt::Display for FlashromError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.msg)
}
- None
-}
-
-pub struct ROMWriteSpecifics<'a> {
- pub layout_file: Option<&'a str>,
- pub write_file: Option<&'a str>,
- pub name_file: Option<&'a str>,
-}
-
-pub fn write_file_with_layout(
- cmd: &cmd::FlashromCmd,
- rws: &ROMWriteSpecifics,
-) -> Result<bool, FlashromError> {
- let opts = FlashromOpt {
- io_opt: IOOpt {
- write: rws.write_file,
- ..Default::default()
- },
-
- layout: rws.layout_file,
- image: rws.name_file,
-
- ignore_fmap: true,
-
- ..Default::default()
- };
-
- let (stdout, stderr) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- let eoutput = String::from_utf8_lossy(stderr.as_slice());
- debug!("write_file_with_layout()'stdout:\n{}.", output);
- debug!("write_file_with_layout()'stderr:\n{}.", eoutput);
- Ok(true)
}
-pub fn wp_range(
- cmd: &cmd::FlashromCmd,
- range: (i64, i64),
- wp_enable: bool,
-) -> Result<bool, FlashromError> {
- let opts = FlashromOpt {
- wp_opt: WPOpt {
- range: Some(range),
- enable: wp_enable,
- ..Default::default()
- },
- ..Default::default()
- };
+impl error::Error for FlashromError {}
- let (stdout, stderr) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- let eoutput = String::from_utf8_lossy(stderr.as_slice());
- debug!("wp_range()'stdout:\n{}.", output);
- debug!("wp_range()'stderr:\n{}.", eoutput);
- Ok(true)
-}
-
-pub fn wp_list(cmd: &cmd::FlashromCmd) -> Result<String, FlashromError> {
- let opts = FlashromOpt {
- wp_opt: WPOpt {
- list: true,
- ..Default::default()
- },
- ..Default::default()
- };
-
- let (stdout, _) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- if output.len() == 0 {
- return Err(
- "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list".into(),
- );
+impl<T> From<T> for FlashromError
+where
+ T: Into<String>,
+{
+ fn from(msg: T) -> Self {
+ FlashromError { msg: msg.into() }
}
- Ok(output.to_string())
}
-pub fn wp_status(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> {
- let status = if en { "en" } else { "dis" };
- info!("See if chip write protect is {}abled", status);
-
- let opts = FlashromOpt {
- wp_opt: WPOpt {
- status: true,
- ..Default::default()
- },
- ..Default::default()
- };
-
- let (stdout, _) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
-
- debug!("wp_status():\n{}", output);
-
- let s = std::format!("write protect is {}abled", status);
- Ok(output.contains(&s))
-}
-
-pub fn wp_toggle(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> {
- let status = if en { "en" } else { "dis" };
-
- // For MTD, --wp-range and --wp-enable must be used simultaneously.
- let range = if en {
- let rom_sz: i64 = cmd.get_size()?;
- Some((0, rom_sz)) // (start, len)
- } else {
- None
- };
-
- let opts = FlashromOpt {
- wp_opt: WPOpt {
- range: range,
- enable: en,
- disable: !en,
- ..Default::default()
- },
- ..Default::default()
- };
-
- let (stdout, stderr) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- let eoutput = String::from_utf8_lossy(stderr.as_slice());
+pub trait Flashrom {
+ /// Returns the size of the flash in bytes.
+ fn get_size(&self) -> Result<i64, FlashromError>;
- debug!("wp_toggle()'stdout:\n{}.", output);
- debug!("wp_toggle()'stderr:\n{}.", eoutput);
+ /// Returns the vendor name and the flash name.
+ fn name(&self) -> Result<(String, String), FlashromError>;
- match wp_status(&cmd, true) {
- Ok(_ret) => {
- info!("Successfully {}abled write-protect", status);
- Ok(true)
- }
- Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e)),
- }
-}
+ /// Set write protect status and range.
+ fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError>;
-pub fn read(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
- let opts = FlashromOpt {
- io_opt: IOOpt {
- read: Some(path),
- ..Default::default()
- },
- ..Default::default()
- };
+ /// Read the write protect regions for the flash.
+ fn wp_list(&self) -> Result<String, FlashromError>;
- let (stdout, _) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- debug!("read():\n{}", output);
- Ok(())
-}
+ /// Return true if the flash write protect status matches `en`.
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError>;
-pub fn write(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
- let opts = FlashromOpt {
- io_opt: IOOpt {
- write: Some(path),
- ..Default::default()
- },
- ..Default::default()
- };
+ /// Set write protect status.
+ /// If en=true sets wp_range to the whole chip (0,getsize()).
+ /// If en=false sets wp_range to (0,0).
+ /// This is due to the MTD driver, which requires wp enable to use a range
+ /// length != 0 and wp disable to have the range 0,0.
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError>;
- let (stdout, _) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- debug!("write():\n{}", output);
- Ok(())
-}
+ /// Read the whole flash to the file specified by `path`.
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError>;
-pub fn verify(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
- let opts = FlashromOpt {
- io_opt: IOOpt {
- verify: Some(path),
- ..Default::default()
- },
- ..Default::default()
- };
+ /// Read only a region of the flash into the file specified by `path`. Note
+ /// the first byte written to the file is the first byte from the region.
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>;
- let (stdout, _) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- debug!("verify():\n{}", output);
- Ok(())
-}
+ /// Write the whole flash to the file specified by `path`.
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError>;
-pub fn erase(cmd: &cmd::FlashromCmd) -> Result<(), FlashromError> {
- let opts = FlashromOpt {
- io_opt: IOOpt {
- erase: true,
- ..Default::default()
- },
- ..Default::default()
- };
+ /// Write only a region of the flash.
+ /// `path` is a file of the size of the whole flash.
+ /// The `region` name corresponds to a region name in the `layout` file, not the flash.
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError>;
- let (stdout, _) = cmd.dispatch(opts)?;
- let output = String::from_utf8_lossy(stdout.as_slice());
- debug!("erase():\n{}", output);
- Ok(())
-}
+ /// Verify the whole flash against the file specified by `path`.
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError>;
-#[cfg(test)]
-mod tests {
- #[test]
- fn extract_flash_name() {
- use super::extract_flash_name;
+ /// Verify only the region against the file specified by `path`.
+ /// Note the first byte in the file is matched against the first byte of the region.
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>;
- assert_eq!(
- extract_flash_name(
- "coreboot table found at 0x7cc13000\n\
- Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\
- vendor=\"Winbond\" name=\"W25Q64DW\"\n"
- ),
- Some(("Winbond", "W25Q64DW"))
- );
+ /// Erase the whole flash.
+ fn erase(&self) -> Result<(), FlashromError>;
- assert_eq!(
- extract_flash_name(
- "vendor name is TEST\n\
- Something failed!"
- ),
- None
- )
- }
+ /// Return true if the hardware write protect of this flash can be controlled.
+ fn can_control_hw_wp(&self) -> bool;
}
diff --git a/util/flashrom_tester/src/cros_sysinfo.rs b/util/flashrom_tester/src/cros_sysinfo.rs
index ddb080265..37e1ec659 100644
--- a/util/flashrom_tester/src/cros_sysinfo.rs
+++ b/util/flashrom_tester/src/cros_sysinfo.rs
@@ -34,7 +34,7 @@
//
use std::ffi::OsStr;
-use std::fmt::Debug;
+use std::fs;
use std::io::Result as IoResult;
use std::process::{Command, Stdio};
@@ -60,21 +60,11 @@ pub fn bios_info() -> IoResult<String> {
dmidecode_dispatch(&["-q", "-t0"])
}
-pub fn eventlog_list() -> Result<String, std::io::Error> {
- mosys_dispatch(&["eventlog", "list"])
-}
-
-fn mosys_dispatch<S: AsRef<OsStr> + Debug>(args: &[S]) -> IoResult<String> {
- info!("mosys_dispatch() running: /usr/sbin/mosys {:?}", args);
-
- let output = Command::new("/usr/sbin/mosys")
- .args(args)
- .stdin(Stdio::null())
- .output()?;
- if !output.status.success() {
- return Err(utils::translate_command_error(&output));
+pub fn release_description() -> IoResult<String> {
+ for l in fs::read_to_string("/etc/lsb-release")?.lines() {
+ if l.starts_with("CHROMEOS_RELEASE_DESCRIPTION") {
+ return Ok(l.to_string());
+ }
}
-
- let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
- Ok(stdout)
+ Err(std::io::ErrorKind::NotFound.into())
}
diff --git a/util/flashrom_tester/src/logger.rs b/util/flashrom_tester/src/logger.rs
index e1c00f5de..c9c364066 100644
--- a/util/flashrom_tester/src/logger.rs
+++ b/util/flashrom_tester/src/logger.rs
@@ -35,105 +35,81 @@
use flashrom_tester::types;
use std::io::Write;
-use std::path::PathBuf;
-use std::sync::Mutex;
-struct Logger<W: Write + Send> {
+struct Logger {
level: log::LevelFilter,
- target: LogTarget<W>,
+ color: types::Color,
}
-enum LogTarget<W>
-where
- W: Write,
-{
- Terminal,
- Write(Mutex<W>),
-}
-
-impl<W: Write + Send> log::Log for Logger<W> {
+impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &log::Record) {
- fn log_internal<W: Write>(mut w: W, record: &log::Record) -> std::io::Result<()> {
- let now = chrono::Local::now();
- write!(w, "{}{} ", types::MAGENTA, now.format("%Y-%m-%dT%H:%M:%S"))?;
- write!(
- w,
- "{}[ {} ]{} ",
- types::YELLOW,
- record.level(),
- types::RESET
- )?;
- writeln!(w, "{}", record.args())
- }
-
// Write errors deliberately ignored
- let _ = match self.target {
- LogTarget::Terminal => {
- let stdout = std::io::stdout();
- let mut lock = stdout.lock();
- log_internal(&mut lock, record)
- }
- LogTarget::Write(ref mutex) => {
- let mut lock = mutex.lock().unwrap();
- log_internal(&mut *lock, record)
- }
- };
+ let stdout = std::io::stdout();
+ let mut lock = stdout.lock();
+ let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
+ let _ = write!(lock, "{}{} ", self.color.magenta, now);
+ let _ = write!(
+ lock,
+ "{}[ {} ]{} ",
+ self.color.yellow,
+ record.level(),
+ self.color.reset
+ );
+ let _ = writeln!(lock, "{}", record.args());
}
fn flush(&self) {
// Flush errors deliberately ignored
- let _ = match self.target {
- LogTarget::Terminal => std::io::stdout().flush(),
- LogTarget::Write(ref w) => w.lock().unwrap().flush(),
- };
+ let _ = std::io::stdout().flush();
}
}
-pub fn init(to_file: Option<PathBuf>, debug: bool) {
+pub fn init(debug: bool) {
let mut logger = Logger {
level: log::LevelFilter::Info,
- target: LogTarget::Terminal,
+ color: if atty::is(atty::Stream::Stdout) {
+ types::COLOR
+ } else {
+ types::NOCOLOR
+ },
};
if debug {
logger.level = log::LevelFilter::Debug;
}
- if let Some(path) = to_file {
- logger.target = LogTarget::Write(Mutex::new(
- std::fs::File::create(path).expect("Unable to open log file for writing"),
- ));
- }
-
log::set_max_level(logger.level);
log::set_boxed_logger(Box::new(logger)).unwrap();
}
#[cfg(test)]
mod tests {
- use super::{LogTarget, Logger};
+ use std::io::Read;
+
+ use super::Logger;
+ use flashrom_tester::types;
use log::{Level, LevelFilter, Log, Record};
- use std::sync::Mutex;
fn run_records(records: &[Record]) -> String {
- let mut buf = Vec::<u8>::new();
+ let buf = gag::BufferRedirect::stdout().unwrap();
{
- let lock = Mutex::new(&mut buf);
let logger = Logger {
level: LevelFilter::Info,
- target: LogTarget::Write(lock),
+ color: types::COLOR,
};
for record in records {
if logger.enabled(record.metadata()) {
- logger.log(&record);
+ logger.log(record);
}
}
}
- String::from_utf8(buf).unwrap()
+ let mut ret = String::new();
+ buf.into_inner().read_to_string(&mut ret).unwrap();
+ ret
}
/// Log messages have the expected format
@@ -146,9 +122,10 @@ mod tests {
assert_eq!(&buf[..5], "\x1b[35m");
// Time is difficult to test, assume it's formatted okay
+ // Split on the UTC timezone char
assert_eq!(
- &buf[24..],
- " \x1b[33m[ INFO ]\x1b[0m Test message at INFO\n"
+ buf.split_once("Z ").unwrap().1,
+ "\x1b[33m[ INFO ]\x1b[0m Test message at INFO\n"
);
}
diff --git a/util/flashrom_tester/src/main.rs b/util/flashrom_tester/src/main.rs
index 1cc525e62..b8a2581ac 100644
--- a/util/flashrom_tester/src/main.rs
+++ b/util/flashrom_tester/src/main.rs
@@ -39,9 +39,9 @@ extern crate log;
mod logger;
use clap::{App, Arg};
-use flashrom::FlashChip;
+use flashrom::{FlashChip, Flashrom, FlashromCmd, FlashromLib};
use flashrom_tester::{tester, tests};
-use std::path::PathBuf;
+use std::sync::atomic::AtomicBool;
pub mod built_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
@@ -64,11 +64,25 @@ fn main() {
built_info::BUILT_TIME_UTC,
built_info::RUSTC_VERSION,
))
- .arg(Arg::with_name("flashrom_binary").required(true))
+ .arg(
+ Arg::with_name("libflashrom")
+ .long("libflashrom")
+ .takes_value(false)
+ .help("Test the flashrom library instead of a binary"),
+ )
+ .arg(
+ Arg::with_name("flashrom_binary")
+ .long("flashrom_binary")
+ .short("b")
+ .takes_value(true)
+ .required_unless("libflashrom")
+ .conflicts_with("libflashrom")
+ .help("Path to flashrom binary to test"),
+ )
.arg(
Arg::with_name("ccd_target_type")
.required(true)
- .possible_values(&["host", "ec", "servo"]),
+ .possible_values(&["host"]),
)
.arg(
Arg::with_name("print-layout")
@@ -77,13 +91,6 @@ fn main() {
.help("Print the layout file's contents before running tests"),
)
.arg(
- Arg::with_name("log-file")
- .short("o")
- .long("log-file")
- .takes_value(true)
- .help("Write logs to a file rather than stdout"),
- )
- .arg(
Arg::with_name("log_debug")
.short("d")
.long("debug")
@@ -106,15 +113,13 @@ fn main() {
)
.get_matches();
- logger::init(
- matches.value_of_os("log-file").map(PathBuf::from),
- matches.is_present("log_debug"),
- );
+ logger::init(matches.is_present("log_debug"));
debug!("Args parsed and logging initialized OK");
- let flashrom_path = matches
- .value_of("flashrom_binary")
- .expect("flashrom_binary should be required");
+ debug!("Collecting crossystem info");
+ let crossystem =
+ flashrom_tester::utils::collect_crosssystem(&[]).expect("could not run crossystem");
+
let ccd_type = FlashChip::from(
matches
.value_of("ccd_target_type")
@@ -122,6 +127,25 @@ fn main() {
)
.expect("ccd_target_type should admit only known types");
+ let cmd: Box<dyn Flashrom> = if matches.is_present("libflashrom") {
+ Box::new(FlashromLib::new(
+ ccd_type,
+ if matches.is_present("log_debug") {
+ flashrom::FLASHROM_MSG_DEBUG
+ } else {
+ flashrom::FLASHROM_MSG_WARN
+ },
+ ))
+ } else {
+ Box::new(FlashromCmd {
+ path: matches
+ .value_of("flashrom_binary")
+ .expect("flashrom_binary is required")
+ .to_string(),
+ fc: ccd_type,
+ })
+ };
+
let print_layout = matches.is_present("print-layout");
let output_format = matches
.value_of("output-format")
@@ -131,13 +155,57 @@ fn main() {
let test_names = matches.values_of("test_name");
if let Err(e) = tests::generic(
- flashrom_path,
+ cmd.as_ref(),
ccd_type,
print_layout,
output_format,
test_names,
+ Some(handle_sigint()),
+ crossystem,
) {
eprintln!("Failed to run tests: {:?}", e);
std::process::exit(1);
}
}
+
+/// Catch exactly one SIGINT, printing a message in response and setting a flag.
+///
+/// The returned value is false by default, becoming true after a SIGINT is
+/// trapped.
+///
+/// Once a signal is trapped, the default behavior is restored (terminating
+/// the process) for future signals.
+fn handle_sigint() -> &'static AtomicBool {
+ use libc::c_int;
+ use std::sync::atomic::Ordering;
+
+ unsafe {
+ let _ = libc::signal(libc::SIGINT, sigint_handler as libc::sighandler_t);
+ }
+ static TERMINATE_FLAG: AtomicBool = AtomicBool::new(false);
+
+ extern "C" fn sigint_handler(_: c_int) {
+ const STDERR_FILENO: c_int = 2;
+ static MESSAGE: &[u8] = b"
+WARNING: terminating tests prematurely may leave Flash in an inconsistent state,
+rendering your machine unbootable. Testing will end on completion of the current
+test, or press ^C again to exit immediately (possibly bricking your machine).
+";
+
+ // Use raw write() because signal-safety is a very hard problem. Safe because this doesn't
+ // modify any memory.
+ let _ = unsafe {
+ libc::write(
+ STDERR_FILENO,
+ MESSAGE.as_ptr() as *const libc::c_void,
+ MESSAGE.len() as libc::size_t,
+ )
+ };
+ unsafe {
+ let _ = libc::signal(libc::SIGINT, libc::SIG_DFL);
+ }
+ TERMINATE_FLAG.store(true, Ordering::Release);
+ }
+
+ &TERMINATE_FLAG
+}
diff --git a/util/flashrom_tester/src/rand_util.rs b/util/flashrom_tester/src/rand_util.rs
index 51411d0d5..a040c89a3 100644
--- a/util/flashrom_tester/src/rand_util.rs
+++ b/util/flashrom_tester/src/rand_util.rs
@@ -36,15 +36,14 @@
use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
+use std::path::Path;
use rand::prelude::*;
-pub fn gen_rand_testdata(path: &str, size: usize) -> std::io::Result<()> {
+pub fn gen_rand_testdata(path: &Path, size: usize) -> std::io::Result<()> {
let mut buf = BufWriter::new(File::create(path)?);
- let mut a: Vec<u8> = Vec::with_capacity(size);
- // Pad out array to be filled in by Rng::fill().
- a.resize(size, 0b0);
+ let mut a: Vec<u8> = vec![0; size];
thread_rng().fill(a.as_mut_slice());
buf.write_all(a.as_slice())?;
@@ -60,8 +59,8 @@ mod tests {
fn gen_rand_testdata() {
use super::gen_rand_testdata;
- let path0 = "/tmp/idk_test00";
- let path1 = "/tmp/idk_test01";
+ let path0 = Path::new("/tmp/idk_test00");
+ let path1 = Path::new("/tmp/idk_test01");
let sz = 1024;
gen_rand_testdata(path0, sz).unwrap();
diff --git a/util/flashrom_tester/src/tester.rs b/util/flashrom_tester/src/tester.rs
index fbef2016e..4629c2eb7 100644
--- a/util/flashrom_tester/src/tester.rs
+++ b/util/flashrom_tester/src/tester.rs
@@ -36,10 +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::sync::Mutex;
+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>;
@@ -51,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)
@@ -87,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
}
@@ -111,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
}
@@ -122,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() {
@@ -158,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>),
-}
-
-impl InitialState<'_> {
- fn get_target(&self) -> (bool, bool) {
- match self {
- InitialState::Hardware(hw, sw) => (*hw, *sw),
- InitialState::Previous(s) => s.current,
- }
- }
+pub struct WriteProtectState<'a> {
+ current: WriteProtect,
+ initial: WriteProtect,
+ cmd: &'a dyn Flashrom,
+ fc: FlashChip,
}
-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)
@@ -229,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.
@@ -328,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")
}
}
@@ -451,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,
@@ -462,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,
}
@@ -476,26 +372,55 @@ 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 {
+ 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, Clone, Copy)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OutputFormat {
Pretty,
Json,
@@ -524,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 ====");
@@ -531,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);
@@ -542,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 => {}
@@ -552,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)
);
}
}
diff --git a/util/flashrom_tester/src/tests.rs b/util/flashrom_tester/src/tests.rs
index dd756893d..721a789d5 100644
--- a/util/flashrom_tester/src/tests.rs
+++ b/util/flashrom_tester/src/tests.rs
@@ -36,12 +36,15 @@
use super::cros_sysinfo;
use super::tester::{self, OutputFormat, TestCase, TestEnv, TestResult};
use super::utils::{self, LayoutNames};
-use flashrom::{FlashChip, Flashrom, FlashromCmd};
+use flashrom::{FlashChip, Flashrom};
use std::collections::{HashMap, HashSet};
-use std::fs::File;
-use std::io::{BufRead, Write};
+use std::convert::TryInto;
+use std::fs::{self, File};
+use std::io::BufRead;
+use std::sync::atomic::AtomicBool;
-const LAYOUT_FILE: &'static str = "/tmp/layout.file";
+const ELOG_FILE: &str = "/tmp/elog.file";
+const FW_MAIN_B_PATH: &str = "/tmp/FW_MAIN_B.bin";
/// Iterate over tests, yielding only those tests with names matching filter_names.
///
@@ -76,49 +79,29 @@ fn filter_tests<'n, 't: 'n, T: TestCase>(
/// test_names is the case-insensitive names of tests to run; if None, then all
/// tests are run. Provided names that don't match any known test will be logged
/// as a warning.
+#[allow(clippy::or_fun_call)] // This is used for to_string here and we don't care.
pub fn generic<'a, TN: Iterator<Item = &'a str>>(
- path: &str,
+ cmd: &dyn Flashrom,
fc: FlashChip,
print_layout: bool,
output_format: OutputFormat,
test_names: Option<TN>,
+ terminate_flag: Option<&AtomicBool>,
+ crossystem: String,
) -> Result<(), Box<dyn std::error::Error>> {
- let p = path.to_string();
- let cmd = FlashromCmd { path: p, fc };
-
utils::ac_power_warning();
- info!("Calculate ROM partition sizes & Create the layout file.");
- let rom_sz: i64 = cmd.get_size()?;
- let layout_sizes = utils::get_layout_sizes(rom_sz)?;
- {
- let mut f = File::create(LAYOUT_FILE)?;
- let mut buf: Vec<u8> = vec![];
- utils::construct_layout_file(&mut buf, &layout_sizes)?;
-
- f.write_all(&buf)?;
- if print_layout {
- info!(
- "Dumping layout file as requested:\n{}",
- String::from_utf8_lossy(&buf)
- );
- }
- }
-
- info!(
- "Record crossystem information.\n{}",
- utils::collect_crosssystem()?
- );
+ info!("Record crossystem information.\n{}", crossystem);
// Register tests to run:
let tests: &[&dyn TestCase] = &[
&("Get_device_name", get_device_name_test),
&("Coreboot_ELOG_sanity", elog_sanity_test),
&("Host_is_ChromeOS", host_is_chrome_test),
- &("Toggle_WP", wp_toggle_test),
+ &("WP_Region_List", wp_region_list_test),
&("Erase_and_Write", erase_write_test),
&("Fail_to_verify", verify_fail_test),
- &("Lock", lock_test),
+ &("HWWP_Locks_SWWP", hwwp_locks_swwp_test),
&("Lock_top_quad", partial_lock_test(LayoutNames::TopQuad)),
&(
"Lock_bottom_quad",
@@ -133,59 +116,60 @@ pub fn generic<'a, TN: Iterator<Item = &'a str>>(
// Limit the tests to only those requested, unless none are requested
// in which case all tests are included.
- let mut filter_names: Option<HashSet<String>> = if let Some(names) = test_names {
- Some(names.map(|s| s.to_lowercase()).collect())
- } else {
- None
- };
+ let mut filter_names: Option<HashSet<String>> =
+ test_names.map(|names| names.map(|s| s.to_lowercase()).collect());
let tests = filter_tests(tests, &mut filter_names);
+ let chip_name = cmd
+ .name()
+ .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1))
+ .unwrap_or("<Unknown chip>".into());
+
// ------------------------.
// Run all the tests and collate the findings:
- let results = tester::run_all_tests(fc, &cmd, tests);
+ let results = tester::run_all_tests(fc, cmd, tests, terminate_flag, print_layout);
// Any leftover filtered names were specified to be run but don't exist
for leftover in filter_names.iter().flatten() {
warn!("No test matches filter name \"{}\"", leftover);
}
- let chip_name = flashrom::name(&cmd)
- .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1))
- .unwrap_or("<Unknown chip>".into());
- let os_rel = sys_info::os_release().unwrap_or("<Unknown OS>".to_string());
+ let os_release = sys_info::os_release().unwrap_or("<Unknown OS>".to_string());
+ let cros_release = cros_sysinfo::release_description()
+ .unwrap_or("<Unknown or not a ChromeOS release>".to_string());
let system_info = cros_sysinfo::system_info().unwrap_or("<Unknown System>".to_string());
let bios_info = cros_sysinfo::bios_info().unwrap_or("<Unknown BIOS>".to_string());
let meta_data = tester::ReportMetaData {
- chip_name: chip_name,
- os_release: os_rel,
- system_info: system_info,
- bios_info: bios_info,
+ chip_name,
+ os_release,
+ cros_release,
+ system_info,
+ bios_info,
};
tester::collate_all_test_runs(&results, meta_data, output_format);
Ok(())
}
+/// Query the programmer and chip name.
+/// Success means we got something back, which is good enough.
fn get_device_name_test(env: &mut TestEnv) -> TestResult {
- // Success means we got something back, which is good enough.
- flashrom::name(env.cmd)?;
+ env.cmd.name()?;
Ok(())
}
-fn wp_toggle_test(env: &mut TestEnv) -> TestResult {
- // NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms.
- // However, we will warn when it does fail.
- // List the write-protected regions of flash.
- match flashrom::wp_list(env.cmd) {
+/// List the write-protectable regions of flash.
+/// NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms.
+/// However, we will warn when it does fail.
+fn wp_region_list_test(env: &mut TestEnv) -> TestResult {
+ match env.cmd.wp_list() {
Ok(list_str) => info!("\n{}", list_str),
Err(e) => warn!("{}", e),
};
- // Fails if unable to set either one
- env.wp.set_hw(false)?;
- env.wp.set_sw(false)?;
Ok(())
}
+/// Verify that enabling hardware and software write protect prevents chip erase.
fn erase_write_test(env: &mut TestEnv) -> TestResult {
if !env.is_golden() {
info!("Memory has been modified; reflashing to ensure erasure can be detected");
@@ -212,47 +196,53 @@ fn erase_write_test(env: &mut TestEnv) -> TestResult {
Ok(())
}
-fn lock_test(env: &mut TestEnv) -> TestResult {
+/// Verify that enabling hardware write protect prevents disabling software write protect.
+fn hwwp_locks_swwp_test(env: &mut TestEnv) -> TestResult {
if !env.wp.can_control_hw_wp() {
return Err("Lock test requires ability to control hardware write protect".into());
}
env.wp.set_hw(false)?.set_sw(true)?;
- // Toggling software WP off should work when hardware is off.
- // Then enable again for another go.
- env.wp.push().set_sw(false)?;
+ // Toggling software WP off should work when hardware WP is off.
+ // Then enable software WP again for the next test.
+ env.wp.set_sw(false)?.set_sw(true)?;
+ // Toggling software WP off should not work when hardware WP is on.
env.wp.set_hw(true)?;
- // Clearing should fail when hardware is enabled
if env.wp.set_sw(false).is_ok() {
return Err("Software WP was reset despite hardware WP being enabled".into());
}
Ok(())
}
+/// Check that the elog contains *something*, as an indication that Coreboot
+/// is actually able to write to the Flash. This only makes sense for chips
+/// running Coreboot, which we assume is just host.
fn elog_sanity_test(env: &mut TestEnv) -> TestResult {
- // Check that the elog contains *something*, as an indication that Coreboot
- // is actually able to write to the Flash. Because this invokes mosys on the
- // host, it doesn't make sense to run for other chips.
if env.chip_type() != FlashChip::HOST {
info!("Skipping ELOG sanity check for non-host chip");
return Ok(());
}
- // mosys reads the flash, it should be back in the golden state
+ // flash should be back in the golden state
env.ensure_golden()?;
- // Output is one event per line, drop empty lines in the interest of being defensive.
- let event_count = cros_sysinfo::eventlog_list()?
- .lines()
- .filter(|l| !l.is_empty())
- .count();
-
- if event_count == 0 {
- Err("ELOG contained no events".into())
- } else {
- Ok(())
+
+ const ELOG_RW_REGION_NAME: &str = "RW_ELOG";
+ env.cmd
+ .read_region_into_file(ELOG_FILE.as_ref(), ELOG_RW_REGION_NAME)?;
+
+ // Just checking for the magic numer
+ // TODO: improve this test to read the events
+ if fs::metadata(ELOG_FILE)?.len() < 4 {
+ return Err("ELOG contained no data".into());
+ }
+ let data = fs::read(ELOG_FILE)?;
+ if u32::from_le_bytes(data[0..4].try_into()?) != 0x474f4c45 {
+ return Err("ELOG had bad magic number".into());
}
+ Ok(())
}
+/// Check that we are running ChromiumOS.
fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult {
let release_info = if let Ok(f) = File::open("/etc/os-release") {
let buf = std::io::BufReader::new(f);
@@ -278,24 +268,27 @@ fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult {
}
}
+/// Verify that software write protect for a range protects only the requested range.
fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResult {
move |env: &mut TestEnv| {
// Need a clean image for verification
env.ensure_golden()?;
- let (name, start, len) = utils::layout_section(env.layout(), section);
- // Disable software WP so we can do range protection, but hardware WP
- // must remain enabled for (most) range protection to do anything.
- env.wp.set_hw(false)?.set_sw(false)?;
- flashrom::wp_range(env.cmd, (start, len), true)?;
+ let (wp_section_name, start, len) = utils::layout_section(env.layout(), section);
+ // Disable hardware WP so we can modify the protected range.
+ env.wp.set_hw(false)?;
+ // Then enable software WP so the range is enforced and enable hardware
+ // WP so that flashrom does not disable software WP during the
+ // operation.
+ env.wp.set_range((start, len), true)?;
env.wp.set_hw(true)?;
- let rws = flashrom::ROMWriteSpecifics {
- layout_file: Some(LAYOUT_FILE),
- write_file: Some(env.random_data_file()),
- name_file: Some(name),
- };
- if flashrom::write_file_with_layout(env.cmd, &rws).is_ok() {
+ // Check that we cannot write to the protected region.
+ if env
+ .cmd
+ .write_from_file_region(env.random_data_file(), wp_section_name, &env.layout_file)
+ .is_ok()
+ {
return Err(
"Section should be locked, should not have been overwritable with random data"
.into(),
@@ -304,12 +297,32 @@ fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResul
if !env.is_golden() {
return Err("Section didn't lock, has been overwritten with random data!".into());
}
+
+ // Check that we can write to the non protected region.
+ let (non_wp_section_name, _, _) =
+ utils::layout_section(env.layout(), section.get_non_overlapping_section());
+ env.cmd.write_from_file_region(
+ env.random_data_file(),
+ non_wp_section_name,
+ &env.layout_file,
+ )?;
+
Ok(())
}
}
+/// Check that flashrom 'verify' will fail if the provided data does not match the chip data.
fn verify_fail_test(env: &mut TestEnv) -> TestResult {
- // Comparing the flash contents to random data says they're not the same.
+ env.ensure_golden()?;
+ // Verify that verify is Ok when the data matches. We verify only a region
+ // and not the whole chip because coprocessors or firmware may have written
+ // some data in other regions.
+ env.cmd
+ .read_region_into_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?;
+ env.cmd
+ .verify_region_from_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?;
+
+ // Verify that verify is false when the data does not match
match env.verify(env.random_data_file()) {
Ok(_) => Err("Verification says flash is full of random data".into()),
Err(_) => Ok(()),
diff --git a/util/flashrom_tester/src/types.rs b/util/flashrom_tester/src/types.rs
index b22ded2b4..9cefb27e9 100644
--- a/util/flashrom_tester/src/types.rs
+++ b/util/flashrom_tester/src/types.rs
@@ -33,21 +33,40 @@
// Software Foundation.
//
-pub const BOLD: &str = "\x1b[1m";
+pub struct Color {
+ pub bold: &'static str,
+ pub reset: &'static str,
+ pub magenta: &'static str,
+ pub yellow: &'static str,
+ pub green: &'static str,
+ pub red: &'static str,
+}
+
+pub const COLOR: Color = Color {
+ bold: "\x1b[1m",
+ reset: "\x1b[0m",
+ magenta: "\x1b[35m",
+ yellow: "\x1b[33m",
+ green: "\x1b[92m",
+ red: "\x1b[31m",
+};
-pub const RESET: &str = "\x1b[0m";
-pub const MAGENTA: &str = "\x1b[35m";
-pub const YELLOW: &str = "\x1b[33m";
-pub const GREEN: &str = "\x1b[92m";
-pub const RED: &str = "\x1b[31m";
+pub const NOCOLOR: Color = Color {
+ bold: "",
+ reset: "",
+ magenta: "",
+ yellow: "",
+ green: "",
+ red: "",
+};
macro_rules! style_dbg {
- ($s: expr, $c: expr) => {
- format!("{}{:?}{}", $c, $s, types::RESET)
+ ($s: expr, $c: expr, $col: expr) => {
+ format!("{}{:?}{}", $c, $s, $col.reset)
};
}
macro_rules! style {
- ($s: expr, $c: expr) => {
- format!("{}{}{}", $c, $s, types::RESET)
+ ($s: expr, $c: expr, $col: expr) => {
+ format!("{}{}{}", $c, $s, $col.reset)
};
}
diff --git a/util/flashrom_tester/src/utils.rs b/util/flashrom_tester/src/utils.rs
index d17480bcb..4e8dd7cce 100644
--- a/util/flashrom_tester/src/utils.rs
+++ b/util/flashrom_tester/src/utils.rs
@@ -44,6 +44,18 @@ pub enum LayoutNames {
BottomQuad,
}
+impl LayoutNames {
+ // Return a section that does not overlap
+ pub fn get_non_overlapping_section(&self) -> LayoutNames {
+ match self {
+ LayoutNames::TopQuad => LayoutNames::BottomQuad,
+ LayoutNames::TopHalf => LayoutNames::BottomHalf,
+ LayoutNames::BottomHalf => LayoutNames::TopHalf,
+ LayoutNames::BottomQuad => LayoutNames::TopQuad,
+ }
+ }
+}
+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct LayoutSizes {
half_sz: i64,
@@ -88,20 +100,20 @@ pub fn construct_layout_file<F: Write>(mut target: F, ls: &LayoutSizes) -> std::
}
pub fn toggle_hw_wp(dis: bool) -> Result<(), String> {
- // The easist way to toggle the harware write-protect is
+ // The easist way to toggle the hardware write-protect is
// to {dis}connect the battery (and/or open the WP screw).
let s = if dis { "dis" } else { "" };
- info!("Prompt for hardware WP {}able", s);
- eprintln!(" > {}connect the battery (and/or open the WP screw)", s);
- pause();
- let wp = get_hardware_wp()?;
- if wp && dis {
- eprintln!("Hardware write protect is still ENABLED!");
- return toggle_hw_wp(dis);
- }
- if !wp && !dis {
- eprintln!("Hardware write protect is still DISABLED!");
- return toggle_hw_wp(dis);
+ // Print a failure message, but not on the first try.
+ let mut fail_msg = None;
+ while dis == get_hardware_wp()? {
+ if let Some(msg) = fail_msg {
+ eprintln!("{msg}");
+ }
+ fail_msg = Some(format!("Hardware write protect is still {}!", !dis));
+ // The following message is read by the tast test. Do not modify.
+ info!("Prompt for hardware WP {}able", s);
+ eprintln!(" > {}connect the battery (and/or open the WP screw)", s);
+ pause();
}
Ok(())
}
@@ -114,21 +126,36 @@ pub fn ac_power_warning() {
}
fn pause() {
- let mut stdout = std::io::stdout();
- // We want the cursor to stay at the end of the line, so we print without a newline
- // and flush manually.
- stdout.write(b"Press any key to continue...").unwrap();
- stdout.flush().unwrap();
- std::io::stdin().read(&mut [0]).unwrap();
+ // The following message is read by the tast test. Do not modify.
+ println!("Press enter to continue...");
+ // Rust stdout is always LineBuffered at time of writing.
+ // But this is not guaranteed, so flush anyway.
+ std::io::stdout().flush().unwrap();
+ // This reads one line, there is no guarantee the line came
+ // after the above prompt. But it is good enough.
+ if std::io::stdin().read_line(&mut String::new()).unwrap() == 0 {
+ panic!("stdin closed");
+ }
}
pub fn get_hardware_wp() -> std::result::Result<bool, String> {
- let (_, wp) = parse_crosssystem(&collect_crosssystem()?)?;
- Ok(wp)
+ let wp_s_val = collect_crosssystem(&["wpsw_cur"])?.parse::<u32>();
+ match wp_s_val {
+ Ok(v) => {
+ if v == 1 {
+ Ok(true)
+ } else if v == 0 {
+ Ok(false)
+ } else {
+ Err("Unknown write protect value".into())
+ }
+ }
+ Err(_) => Err("Cannot parse write protect value".into()),
+ }
}
-pub fn collect_crosssystem() -> Result<String, String> {
- let cmd = match Command::new("crossystem").output() {
+pub fn collect_crosssystem(args: &[&str]) -> Result<String, String> {
+ let cmd = match Command::new("crossystem").args(args).output() {
Ok(x) => x,
Err(e) => return Err(format!("Failed to run crossystem: {}", e)),
};
@@ -140,39 +167,6 @@ pub fn collect_crosssystem() -> Result<String, String> {
Ok(String::from_utf8_lossy(&cmd.stdout).into_owned())
}
-fn parse_crosssystem(s: &str) -> Result<(Vec<&str>, bool), &'static str> {
- // grep -v 'fwid +=' | grep -v 'hwid +='
- let sysinfo = s
- .split_terminator("\n")
- .filter(|s| !s.contains("fwid +=") && !s.contains("hwid +="));
-
- let state_line = match sysinfo.clone().filter(|s| s.starts_with("wpsw_cur")).next() {
- None => return Err("No wpsw_cur in system info"),
- Some(line) => line,
- };
- let wp_s_val = state_line
- .trim_start_matches("wpsw_cur")
- .trim_start_matches(' ')
- .trim_start_matches('=')
- .trim_start_matches(' ')
- .get(..1)
- .unwrap()
- .parse::<u32>();
-
- match wp_s_val {
- Ok(v) => {
- if v == 1 {
- return Ok((sysinfo.collect(), true));
- } else if v == 0 {
- return Ok((sysinfo.collect(), false));
- } else {
- return Err("Unknown state value");
- }
- }
- Err(_) => return Err("Cannot parse state value"),
- }
-}
-
pub fn translate_command_error(output: &std::process::Output) -> std::io::Error {
use std::io::{Error, ErrorKind};
// There is two cases on failure;
@@ -244,55 +238,4 @@ mod tests {
}
);
}
-
- #[test]
- fn parse_crosssystem() {
- use super::parse_crosssystem;
-
- assert_eq!(
- parse_crosssystem("This is not the tool you are looking for").err(),
- Some("No wpsw_cur in system info")
- );
-
- assert_eq!(
- parse_crosssystem("wpsw_cur = ERROR").err(),
- Some("Cannot parse state value")
- );
-
- assert_eq!(
- parse_crosssystem("wpsw_cur = 3").err(),
- Some("Unknown state value")
- );
-
- assert_eq!(
- parse_crosssystem("wpsw_cur = 0"),
- Ok((vec!["wpsw_cur = 0"], false))
- );
-
- assert_eq!(
- parse_crosssystem("wpsw_cur = 1"),
- Ok((vec!["wpsw_cur = 1"], true))
- );
-
- assert_eq!(
- parse_crosssystem("wpsw_cur=1"),
- Ok((vec!["wpsw_cur=1"], true))
- );
-
- assert_eq!(
- parse_crosssystem(
- "fwid += 123wpsw_cur\n\
- hwid += aaaaa\n\
- wpsw_boot = 0 # [RO/int]\n\
- wpsw_cur = 1 # [RO/int]\n"
- ),
- Ok((
- vec![
- "wpsw_boot = 0 # [RO/int]",
- "wpsw_cur = 1 # [RO/int]"
- ],
- true
- ))
- );
- }
}