mirror of
https://review.coreboot.org/flashrom.git
synced 2025-04-28 07:23:43 +02:00
util/flashrom_tester: Upstream E2E testing framework
The following is a E2E tester for a specific chip/chipset combo. The tester itself is completely self-contained and allows the user to specify which tests they wish to preform. Supported tests include: - chip-name - read - write - erase - wp-locking Change-Id: Ic2905a76cad90b1546b9328d668bf8abbf8aed44 Signed-off-by: Edward O'Callaghan <quasisec@google.com> Reviewed-on: https://review.coreboot.org/c/flashrom/+/38951 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: David Hendricks <david.hendricks@gmail.com>
This commit is contained in:
parent
7a7fee1695
commit
0f510a7458
2
util/flashrom_tester/.gitignore
vendored
Normal file
2
util/flashrom_tester/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Cargo.lock
|
||||||
|
target/
|
32
util/flashrom_tester/Cargo.toml
Normal file
32
util/flashrom_tester/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "flashrom_tester"
|
||||||
|
version = "1.6.0"
|
||||||
|
authors = ["Edward O'Callaghan <quasisec@chromium.org>",
|
||||||
|
"Peter Marheine <pmarheine@chromium.org>"]
|
||||||
|
edition = "2018"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "flashrom_tester"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "flashrom_tester"
|
||||||
|
required-features = ["cli"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] }
|
||||||
|
chrono = { version = "0.4", optional = true }
|
||||||
|
clap = { version = "2.33", default-features = false, optional = true }
|
||||||
|
flashrom = { path = "flashrom/" }
|
||||||
|
log = { version = "0.4", features = ["std"] }
|
||||||
|
rand = "0.6.4"
|
||||||
|
serde_json = "1"
|
||||||
|
sys-info = "0.5.7"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Features required to build the CLI binary but not the library
|
||||||
|
cli = ["chrono", "clap"]
|
||||||
|
default = ["cli"]
|
5
util/flashrom_tester/build.rs
Normal file
5
util/flashrom_tester/build.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
extern crate built;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
built::write_built_file().expect("Failed to acquire build-time information");
|
||||||
|
}
|
9
util/flashrom_tester/flashrom/Cargo.toml
Normal file
9
util/flashrom_tester/flashrom/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "flashrom"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = ["Edward O'Callaghan <quasisec@chromium.org>",
|
||||||
|
"Peter Marheine <pmarheine@chromium.org>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4"
|
355
util/flashrom_tester/flashrom/src/cmd.rs
Normal file
355
util/flashrom_tester/flashrom/src/cmd.rs
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::{FlashChip, FlashromError, FlashromOpt};
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct FlashromCmd {
|
||||||
|
pub path: String,
|
||||||
|
pub fc: FlashChip,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to determine the Flash size given stdout from `flashrom --flash-size`
|
||||||
|
fn flashrom_extract_size(stdout: &str) -> Result<i64, FlashromError> {
|
||||||
|
// Search for the last line of output that contains only digits, assuming
|
||||||
|
// that's the actual size. flashrom sadly tends to write additional messages
|
||||||
|
// to stdout.
|
||||||
|
match stdout
|
||||||
|
.lines()
|
||||||
|
.filter(|line| line.chars().all(|c| c.is_ascii_digit()))
|
||||||
|
.last()
|
||||||
|
.map(str::parse::<i64>)
|
||||||
|
{
|
||||||
|
None => return 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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(Ok(sz)) => Ok(sz),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
flashrom_extract_size(&sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
|
||||||
|
let params = flashrom_decode_opts(fropt);
|
||||||
|
flashrom_dispatch(self.path.as_str(), ¶ms, self.fc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> {
|
||||||
|
let mut params = Vec::<String>::new();
|
||||||
|
|
||||||
|
// ------------ WARNING !!! ------------
|
||||||
|
// each param must NOT contain spaces!
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
if opts.wp_opt.status {
|
||||||
|
params.push("--wp-status".to_string());
|
||||||
|
} else if opts.wp_opt.list {
|
||||||
|
params.push("--wp-list".to_string());
|
||||||
|
} else if opts.wp_opt.enable {
|
||||||
|
params.push("--wp-enable".to_string());
|
||||||
|
} else if opts.wp_opt.disable {
|
||||||
|
params.push("--wp-disable".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
if opts.image.is_some() {
|
||||||
|
params.push("-i".to_string());
|
||||||
|
params.push(opts.image.unwrap().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.flash_name {
|
||||||
|
params.push("--flash-name".to_string());
|
||||||
|
}
|
||||||
|
if opts.ignore_fmap {
|
||||||
|
params.push("--ignore-fmap".to_string());
|
||||||
|
}
|
||||||
|
if opts.verbose {
|
||||||
|
params.push("-V".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
params
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flashrom_dispatch<S: AsRef<str>>(
|
||||||
|
path: &str,
|
||||||
|
params: &[S],
|
||||||
|
fc: FlashChip,
|
||||||
|
) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
|
||||||
|
// from man page:
|
||||||
|
// ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] '
|
||||||
|
let mut args: Vec<&str> = vec!["-p", 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)),
|
||||||
|
};
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dut_ctrl_servo_type() -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
|
||||||
|
let args = ["servo_type"];
|
||||||
|
dut_ctrl(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((output.stdout, output.stderr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_string(v: i64) -> String {
|
||||||
|
format!("{:#08X}", v).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::flashrom_decode_opts;
|
||||||
|
use crate::{FlashromOpt, IOOpt, WPOpt};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_wp_opt() {
|
||||||
|
fn test_wp_opt(wpo: WPOpt, expected: &[&str]) {
|
||||||
|
assert_eq!(
|
||||||
|
flashrom_decode_opts(FlashromOpt {
|
||||||
|
wp_opt: wpo,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_wp_opt(Default::default(), &[]);
|
||||||
|
test_wp_opt(
|
||||||
|
WPOpt {
|
||||||
|
range: Some((0, 1234)),
|
||||||
|
status: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["--wp-range", "0x000000", "0x0004D2", "--wp-status"],
|
||||||
|
);
|
||||||
|
test_wp_opt(
|
||||||
|
WPOpt {
|
||||||
|
list: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["--wp-list"],
|
||||||
|
);
|
||||||
|
test_wp_opt(
|
||||||
|
WPOpt {
|
||||||
|
enable: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["--wp-enable"],
|
||||||
|
);
|
||||||
|
test_wp_opt(
|
||||||
|
WPOpt {
|
||||||
|
disable: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["--wp-disable"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_io_opt() {
|
||||||
|
fn test_io_opt(opts: IOOpt, expected: &[&str]) {
|
||||||
|
assert_eq!(
|
||||||
|
flashrom_decode_opts(FlashromOpt {
|
||||||
|
io_opt: opts,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_io_opt(
|
||||||
|
IOOpt {
|
||||||
|
read: Some("foo.bin"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["-r", "foo.bin"],
|
||||||
|
);
|
||||||
|
test_io_opt(
|
||||||
|
IOOpt {
|
||||||
|
write: Some("bar.bin"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["-w", "bar.bin"],
|
||||||
|
);
|
||||||
|
test_io_opt(
|
||||||
|
IOOpt {
|
||||||
|
verify: Some("/tmp/baz.bin"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["-v", "/tmp/baz.bin"],
|
||||||
|
);
|
||||||
|
test_io_opt(
|
||||||
|
IOOpt {
|
||||||
|
erase: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&["-E"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flashrom_extract_size() {
|
||||||
|
use super::flashrom_extract_size;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
flashrom_extract_size(
|
||||||
|
"coreboot table found at 0x7cc13000.\n\
|
||||||
|
Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\
|
||||||
|
8388608\n"
|
||||||
|
),
|
||||||
|
Ok(8388608)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
flashrom_extract_size("There was a catastrophic error."),
|
||||||
|
Err("Found no purely-numeric lines in flashrom output".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
381
util/flashrom_tester/flashrom/src/lib.rs
Normal file
381
util/flashrom_tester/flashrom/src/lib.rs
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
mod cmd;
|
||||||
|
|
||||||
|
pub use cmd::{dut_ctrl_toggle_wp, FlashromCmd};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, 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),
|
||||||
|
"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",
|
||||||
|
FlashChip::HOST => "host",
|
||||||
|
FlashChip::SERVO => "ft2231_spi:type=servo-v2",
|
||||||
|
FlashChip::DEDIPROG => "dediprog",
|
||||||
|
};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return whether the hardware write protect signal can be controlled.
|
||||||
|
///
|
||||||
|
/// Servo and dediprog adapters are assumed to always have hardware write protect
|
||||||
|
/// disabled.
|
||||||
|
pub fn can_control_hw_wp(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
FlashChip::HOST | FlashChip::EC => true,
|
||||||
|
FlashChip::SERVO | FlashChip::DEDIPROG => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
|
||||||
|
debug!("wp_toggle()'stdout:\n{}.", output);
|
||||||
|
debug!("wp_toggle()'stderr:\n{}.", eoutput);
|
||||||
|
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
|
||||||
|
let opts = FlashromOpt {
|
||||||
|
io_opt: IOOpt {
|
||||||
|
read: Some(path),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (stdout, _) = cmd.dispatch(opts)?;
|
||||||
|
let output = String::from_utf8_lossy(stdout.as_slice());
|
||||||
|
debug!("read():\n{}", output);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
|
||||||
|
let opts = FlashromOpt {
|
||||||
|
io_opt: IOOpt {
|
||||||
|
write: Some(path),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (stdout, _) = cmd.dispatch(opts)?;
|
||||||
|
let output = String::from_utf8_lossy(stdout.as_slice());
|
||||||
|
debug!("write():\n{}", output);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
|
||||||
|
let opts = FlashromOpt {
|
||||||
|
io_opt: IOOpt {
|
||||||
|
verify: Some(path),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (stdout, _) = cmd.dispatch(opts)?;
|
||||||
|
let output = String::from_utf8_lossy(stdout.as_slice());
|
||||||
|
debug!("verify():\n{}", output);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erase(cmd: &cmd::FlashromCmd) -> Result<(), FlashromError> {
|
||||||
|
let opts = FlashromOpt {
|
||||||
|
io_opt: IOOpt {
|
||||||
|
erase: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (stdout, _) = cmd.dispatch(opts)?;
|
||||||
|
let output = String::from_utf8_lossy(stdout.as_slice());
|
||||||
|
debug!("erase():\n{}", output);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
2
util/flashrom_tester/flashrom_remote.sh
Executable file
2
util/flashrom_tester/flashrom_remote.sh
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
/usr/bin/ssh localhost -p 60024 -C /usr/sbin/flashrom "$@"
|
80
util/flashrom_tester/src/cros_sysinfo.rs
Normal file
80
util/flashrom_tester/src/cros_sysinfo.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::io::Result as IoResult;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use super::utils;
|
||||||
|
|
||||||
|
fn dmidecode_dispatch<S: AsRef<OsStr>>(args: &[S]) -> IoResult<String> {
|
||||||
|
let output = Command::new("/usr/sbin/dmidecode")
|
||||||
|
.args(args)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(utils::translate_command_error(&output));
|
||||||
|
}
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_info() -> IoResult<String> {
|
||||||
|
dmidecode_dispatch(&["-q", "-t1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||||
|
Ok(stdout)
|
||||||
|
}
|
46
util/flashrom_tester/src/lib.rs
Normal file
46
util/flashrom_tester/src/lib.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub mod cros_sysinfo;
|
||||||
|
pub mod rand_util;
|
||||||
|
pub mod tester;
|
||||||
|
pub mod tests;
|
||||||
|
pub mod utils;
|
172
util/flashrom_tester/src/logger.rs
Normal file
172
util/flashrom_tester/src/logger.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use flashrom_tester::types;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
struct Logger<W: Write + Send> {
|
||||||
|
level: log::LevelFilter,
|
||||||
|
target: LogTarget<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogTarget<W>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
Terminal,
|
||||||
|
Write(Mutex<W>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + Send> log::Log for Logger<W> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(to_file: Option<PathBuf>, debug: bool) {
|
||||||
|
let mut logger = Logger {
|
||||||
|
level: log::LevelFilter::Info,
|
||||||
|
target: LogTarget::Terminal,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 log::{Level, LevelFilter, Log, Record};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
fn run_records(records: &[Record]) -> String {
|
||||||
|
let mut buf = Vec::<u8>::new();
|
||||||
|
{
|
||||||
|
let lock = Mutex::new(&mut buf);
|
||||||
|
let logger = Logger {
|
||||||
|
level: LevelFilter::Info,
|
||||||
|
target: LogTarget::Write(lock),
|
||||||
|
};
|
||||||
|
|
||||||
|
for record in records {
|
||||||
|
if logger.enabled(record.metadata()) {
|
||||||
|
logger.log(&record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from_utf8(buf).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log messages have the expected format
|
||||||
|
#[test]
|
||||||
|
fn format() {
|
||||||
|
let buf = run_records(&[Record::builder()
|
||||||
|
.args(format_args!("Test message at INFO"))
|
||||||
|
.level(Level::Info)
|
||||||
|
.build()]);
|
||||||
|
|
||||||
|
assert_eq!(&buf[..5], "\x1b[35m");
|
||||||
|
// Time is difficult to test, assume it's formatted okay
|
||||||
|
assert_eq!(
|
||||||
|
&buf[24..],
|
||||||
|
" \x1b[33m[ INFO ]\x1b[0m Test message at INFO\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn level_filter() {
|
||||||
|
let buf = run_records(&[
|
||||||
|
Record::builder()
|
||||||
|
.args(format_args!("Test message at DEBUG"))
|
||||||
|
.level(Level::Debug)
|
||||||
|
.build(),
|
||||||
|
Record::builder()
|
||||||
|
.args(format_args!("Hello, world!"))
|
||||||
|
.level(Level::Error)
|
||||||
|
.build(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// There is one line because the Debug record wasn't written.
|
||||||
|
println!("{}", buf);
|
||||||
|
assert_eq!(buf.lines().count(), 1);
|
||||||
|
}
|
||||||
|
}
|
143
util/flashrom_tester/src/main.rs
Normal file
143
util/flashrom_tester/src/main.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
mod logger;
|
||||||
|
|
||||||
|
use clap::{App, Arg};
|
||||||
|
use flashrom::FlashChip;
|
||||||
|
use flashrom_tester::{tester, tests};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub mod built_info {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = App::new("flashrom_tester")
|
||||||
|
.long_version(&*format!(
|
||||||
|
"{}-{}\n\
|
||||||
|
Target: {}\n\
|
||||||
|
Profile: {}\n\
|
||||||
|
Features: {:?}\n\
|
||||||
|
Build time: {}\n\
|
||||||
|
Compiler: {}",
|
||||||
|
built_info::PKG_VERSION,
|
||||||
|
option_env!("VCSID").unwrap_or("<unknown>"),
|
||||||
|
built_info::TARGET,
|
||||||
|
built_info::PROFILE,
|
||||||
|
built_info::FEATURES,
|
||||||
|
built_info::BUILT_TIME_UTC,
|
||||||
|
built_info::RUSTC_VERSION,
|
||||||
|
))
|
||||||
|
.arg(Arg::with_name("flashrom_binary").required(true))
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("ccd_target_type")
|
||||||
|
.required(true)
|
||||||
|
.possible_values(&["host", "ec", "servo"]),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("print-layout")
|
||||||
|
.short("l")
|
||||||
|
.long("print-layout")
|
||||||
|
.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")
|
||||||
|
.help("Write detailed logs, for debugging"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("output-format")
|
||||||
|
.short("f")
|
||||||
|
.long("output-format")
|
||||||
|
.help("Set the test report format")
|
||||||
|
.takes_value(true)
|
||||||
|
.case_insensitive(true)
|
||||||
|
.possible_values(&["pretty", "json"])
|
||||||
|
.default_value("pretty"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("test_name")
|
||||||
|
.multiple(true)
|
||||||
|
.help("Names of individual tests to run (run all if unspecified)"),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
logger::init(
|
||||||
|
matches.value_of_os("log-file").map(PathBuf::from),
|
||||||
|
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");
|
||||||
|
let ccd_type = FlashChip::from(
|
||||||
|
matches
|
||||||
|
.value_of("ccd_target_type")
|
||||||
|
.expect("ccd_target_type should be required"),
|
||||||
|
)
|
||||||
|
.expect("ccd_target_type should admit only known types");
|
||||||
|
|
||||||
|
let print_layout = matches.is_present("print-layout");
|
||||||
|
let output_format = matches
|
||||||
|
.value_of("output-format")
|
||||||
|
.expect("output-format should have a default value")
|
||||||
|
.parse::<tester::OutputFormat>()
|
||||||
|
.expect("output-format is not a parseable OutputFormat");
|
||||||
|
let test_names = matches.values_of("test_name");
|
||||||
|
|
||||||
|
if let Err(e) = tests::generic(
|
||||||
|
flashrom_path,
|
||||||
|
ccd_type,
|
||||||
|
print_layout,
|
||||||
|
output_format,
|
||||||
|
test_names,
|
||||||
|
) {
|
||||||
|
eprintln!("Failed to run tests: {:?}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
81
util/flashrom_tester/src/rand_util.rs
Normal file
81
util/flashrom_tester/src/rand_util.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::BufWriter;
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
pub fn gen_rand_testdata(path: &str, 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);
|
||||||
|
thread_rng().fill(a.as_mut_slice());
|
||||||
|
|
||||||
|
buf.write_all(a.as_slice())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gen_rand_testdata() {
|
||||||
|
use super::gen_rand_testdata;
|
||||||
|
|
||||||
|
let path0 = "/tmp/idk_test00";
|
||||||
|
let path1 = "/tmp/idk_test01";
|
||||||
|
let sz = 1024;
|
||||||
|
|
||||||
|
gen_rand_testdata(path0, sz).unwrap();
|
||||||
|
gen_rand_testdata(path1, sz).unwrap();
|
||||||
|
|
||||||
|
let mut buf0 = Vec::new();
|
||||||
|
let mut buf1 = Vec::new();
|
||||||
|
|
||||||
|
let mut f = File::open(path0).unwrap();
|
||||||
|
let mut g = File::open(path1).unwrap();
|
||||||
|
|
||||||
|
f.read_to_end(&mut buf0).unwrap();
|
||||||
|
g.read_to_end(&mut buf1).unwrap();
|
||||||
|
|
||||||
|
assert_ne!(buf0, buf1);
|
||||||
|
}
|
||||||
|
}
|
636
util/flashrom_tester/src/tester.rs
Normal file
636
util/flashrom_tester/src/tester.rs
Normal file
@ -0,0 +1,636 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use super::rand_util;
|
||||||
|
use super::types;
|
||||||
|
use super::utils::{self, LayoutSizes};
|
||||||
|
use flashrom::{FlashChip, Flashrom, FlashromCmd};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
// type-signature comes from the return type of lib.rs workers.
|
||||||
|
type TestError = Box<dyn std::error::Error>;
|
||||||
|
pub type TestResult = Result<(), TestError>;
|
||||||
|
|
||||||
|
pub struct TestEnv<'a> {
|
||||||
|
chip_type: FlashChip,
|
||||||
|
/// Flashrom instantiation information.
|
||||||
|
///
|
||||||
|
/// Where possible, prefer to use methods on the TestEnv rather than delegating
|
||||||
|
/// to the raw flashrom functions.
|
||||||
|
pub cmd: &'a FlashromCmd,
|
||||||
|
layout: LayoutSizes,
|
||||||
|
|
||||||
|
pub wp: WriteProtectState<'a, 'static>,
|
||||||
|
/// 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,
|
||||||
|
/// The path to a file containing flash-sized random data
|
||||||
|
// TODO(pmarheine) make this a PathBuf too
|
||||||
|
random_data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TestEnv<'a> {
|
||||||
|
pub fn create(chip_type: FlashChip, cmd: &'a FlashromCmd) -> Result<Self, String> {
|
||||||
|
let rom_sz = cmd.get_size()?;
|
||||||
|
let out = TestEnv {
|
||||||
|
chip_type: chip_type,
|
||||||
|
cmd: cmd,
|
||||||
|
layout: utils::get_layout_sizes(rom_sz)?,
|
||||||
|
wp: WriteProtectState::from_hardware(cmd)?,
|
||||||
|
original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(),
|
||||||
|
random_data: "/tmp/random_content.bin".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
info!("Generating random flash-sized data");
|
||||||
|
rand_util::gen_rand_testdata(&out.random_data, rom_sz as usize)
|
||||||
|
.map_err(|io_err| format!("I/O error writing random data file: {:#}", io_err))?;
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult {
|
||||||
|
let 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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chip_type(&self) -> FlashChip {
|
||||||
|
// This field is not public because it should be immutable to tests,
|
||||||
|
// so this getter enforces that it is copied.
|
||||||
|
self.chip_type
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the path to a file that contains random data and is the same size
|
||||||
|
/// as the flash chip.
|
||||||
|
pub fn random_data_file(&self) -> &str {
|
||||||
|
&self.random_data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout(&self) -> &LayoutSizes {
|
||||||
|
&self.layout
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if the current Flash contents are the same as the golden image
|
||||||
|
/// that was present at the start of testing.
|
||||||
|
pub fn is_golden(&self) -> bool {
|
||||||
|
flashrom::verify(&self.cmd, &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> {
|
||||||
|
self.wp.set_hw(false)?.set_sw(false)?;
|
||||||
|
flashrom::write(&self.cmd, &self.original_flash_contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to erase the flash.
|
||||||
|
pub fn erase(&self) -> Result<(), String> {
|
||||||
|
flashrom::erase(self.cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestEnv<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
info!("Verifying flash remains unmodified");
|
||||||
|
if !self.is_golden() {
|
||||||
|
warn!("ROM seems to be in a different state at finish; restoring original");
|
||||||
|
if let Err(e) = self.ensure_golden() {
|
||||||
|
error!("Failed to write back golden image: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> WriteProtectState<'a, 'static> {
|
||||||
|
/// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
let hw = Self::get_hw(cmd)?;
|
||||||
|
let sw = Self::get_sw(cmd)?;
|
||||||
|
info!("Initial hardware write protect: HW={} SW={}", hw, sw);
|
||||||
|
|
||||||
|
*lock = true;
|
||||||
|
Ok(WriteProtectState {
|
||||||
|
initial: InitialState::Hardware(hw, sw),
|
||||||
|
current: (hw, sw),
|
||||||
|
cmd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the actual hardware write protect state.
|
||||||
|
fn get_hw(cmd: &FlashromCmd) -> Result<bool, String> {
|
||||||
|
if cmd.fc.can_control_hw_wp() {
|
||||||
|
super::utils::get_hardware_wp()
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the actual software write protect state.
|
||||||
|
fn get_sw(cmd: &FlashromCmd) -> Result<bool, String> {
|
||||||
|
flashrom::wp_status(cmd, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the software write protect.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the hardware to what it was when this state was created, reporting errors.
|
||||||
|
///
|
||||||
|
/// This behaves exactly like allowing a state to go out of scope, but it can return
|
||||||
|
/// errors from that process rather than panicking.
|
||||||
|
pub fn close(mut self) -> Result<(), String> {
|
||||||
|
unsafe {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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.
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.drop_internal() }.expect("Error while dropping WriteProtectState")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TestCase {
|
||||||
|
fn get_name(&self) -> &str;
|
||||||
|
fn expected_result(&self) -> TestConclusion;
|
||||||
|
fn run(&self, env: &mut TestEnv) -> TestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: AsRef<str>, F: Fn(&mut TestEnv) -> TestResult> TestCase for (S, F) {
|
||||||
|
fn get_name(&self) -> &str {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_result(&self) -> TestConclusion {
|
||||||
|
TestConclusion::Pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, env: &mut TestEnv) -> TestResult {
|
||||||
|
(self.1)(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TestCase + ?Sized> TestCase for &T {
|
||||||
|
fn get_name(&self) -> &str {
|
||||||
|
(*self).get_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_result(&self) -> TestConclusion {
|
||||||
|
(*self).expected_result()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, env: &mut TestEnv) -> TestResult {
|
||||||
|
(*self).run(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum TestConclusion {
|
||||||
|
Pass,
|
||||||
|
Fail,
|
||||||
|
UnexpectedPass,
|
||||||
|
UnexpectedFail,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ReportMetaData {
|
||||||
|
pub chip_name: String,
|
||||||
|
pub os_release: String,
|
||||||
|
pub system_info: String,
|
||||||
|
pub bios_info: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, Option<TestError>) {
|
||||||
|
use TestConclusion::*;
|
||||||
|
|
||||||
|
match (res, con) {
|
||||||
|
(Ok(_), Fail) => (UnexpectedPass, None),
|
||||||
|
(Err(e), Pass) => (UnexpectedFail, Some(e)),
|
||||||
|
_ => (Pass, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_all_tests<T, TS>(
|
||||||
|
chip: FlashChip,
|
||||||
|
cmd: &FlashromCmd,
|
||||||
|
ts: TS,
|
||||||
|
) -> 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 results = Vec::new();
|
||||||
|
for t in ts {
|
||||||
|
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)]
|
||||||
|
pub enum OutputFormat {
|
||||||
|
Pretty,
|
||||||
|
Json,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for OutputFormat {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
use OutputFormat::*;
|
||||||
|
|
||||||
|
if s.eq_ignore_ascii_case("pretty") {
|
||||||
|
Ok(Pretty)
|
||||||
|
} else if s.eq_ignore_ascii_case("json") {
|
||||||
|
Ok(Json)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collate_all_test_runs(
|
||||||
|
truns: &[(String, (TestConclusion, Option<TestError>))],
|
||||||
|
meta_data: ReportMetaData,
|
||||||
|
format: OutputFormat,
|
||||||
|
) {
|
||||||
|
match format {
|
||||||
|
OutputFormat::Pretty => {
|
||||||
|
println!();
|
||||||
|
println!(" =============================");
|
||||||
|
println!(" ===== AVL qual RESULTS ====");
|
||||||
|
println!(" =============================");
|
||||||
|
println!();
|
||||||
|
println!(" %---------------------------%");
|
||||||
|
println!(" os release: {}", meta_data.os_release);
|
||||||
|
println!(" chip name: {}", meta_data.chip_name);
|
||||||
|
println!(" system info: \n{}", meta_data.system_info);
|
||||||
|
println!(" bios info: \n{}", meta_data.bios_info);
|
||||||
|
println!(" %---------------------------%");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
for trun in truns.iter() {
|
||||||
|
let (name, (result, error)) = trun;
|
||||||
|
if *result != TestConclusion::Pass {
|
||||||
|
println!(
|
||||||
|
" {} {}",
|
||||||
|
style!(format!(" <+> {} test:", name), types::BOLD),
|
||||||
|
style_dbg!(result, types::RED)
|
||||||
|
);
|
||||||
|
match error {
|
||||||
|
None => {}
|
||||||
|
Some(e) => info!(" - {} failure details:\n{}", name, e.to_string()),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
" {} {}",
|
||||||
|
style!(format!(" <+> {} test:", name), types::BOLD),
|
||||||
|
style_dbg!(result, types::GREEN)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
OutputFormat::Json => {
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
let mut all_pass = true;
|
||||||
|
let mut tests = Map::<String, Value>::new();
|
||||||
|
for (name, (result, error)) in truns {
|
||||||
|
let passed = *result == TestConclusion::Pass;
|
||||||
|
all_pass &= passed;
|
||||||
|
|
||||||
|
let error = match error {
|
||||||
|
Some(e) => Value::String(format!("{:#?}", e)),
|
||||||
|
None => Value::Null,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!tests.contains_key(name),
|
||||||
|
"Found multiple tests named {:?}",
|
||||||
|
name
|
||||||
|
);
|
||||||
|
tests.insert(
|
||||||
|
name.into(),
|
||||||
|
json!({
|
||||||
|
"pass": passed,
|
||||||
|
"error": error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = json!({
|
||||||
|
"pass": all_pass,
|
||||||
|
"metadata": {
|
||||||
|
"os_release": meta_data.os_release,
|
||||||
|
"chip_name": meta_data.chip_name,
|
||||||
|
"system_info": meta_data.system_info,
|
||||||
|
"bios_info": meta_data.bios_info,
|
||||||
|
},
|
||||||
|
"tests": tests,
|
||||||
|
});
|
||||||
|
println!("{:#}", json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn decode_test_result() {
|
||||||
|
use super::decode_test_result;
|
||||||
|
use super::TestConclusion::*;
|
||||||
|
|
||||||
|
let (result, err) = decode_test_result(Ok(()), Pass);
|
||||||
|
assert_eq!(result, Pass);
|
||||||
|
assert!(err.is_none());
|
||||||
|
|
||||||
|
let (result, err) = decode_test_result(Ok(()), Fail);
|
||||||
|
assert_eq!(result, UnexpectedPass);
|
||||||
|
assert!(err.is_none());
|
||||||
|
|
||||||
|
let (result, err) = decode_test_result(Err("broken".into()), Pass);
|
||||||
|
assert_eq!(result, UnexpectedFail);
|
||||||
|
assert!(err.is_some());
|
||||||
|
|
||||||
|
let (result, err) = decode_test_result(Err("broken".into()), Fail);
|
||||||
|
assert_eq!(result, Pass);
|
||||||
|
assert!(err.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_format_round_trip() {
|
||||||
|
use super::OutputFormat::{self, *};
|
||||||
|
|
||||||
|
assert_eq!(format!("{:?}", Pretty).parse::<OutputFormat>(), Ok(Pretty));
|
||||||
|
assert_eq!(format!("{:?}", Json).parse::<OutputFormat>(), Ok(Json));
|
||||||
|
}
|
||||||
|
}
|
385
util/flashrom_tester/src/tests.rs
Normal file
385
util/flashrom_tester/src/tests.rs
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use super::cros_sysinfo;
|
||||||
|
use super::tester::{self, OutputFormat, TestCase, TestEnv, TestResult};
|
||||||
|
use super::utils::{self, LayoutNames};
|
||||||
|
use flashrom::{FlashChip, Flashrom, FlashromCmd};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufRead, Write};
|
||||||
|
|
||||||
|
const LAYOUT_FILE: &'static str = "/tmp/layout.file";
|
||||||
|
|
||||||
|
/// Iterate over tests, yielding only those tests with names matching filter_names.
|
||||||
|
///
|
||||||
|
/// If filter_names is None, all tests will be run. None is distinct from Some(∅);
|
||||||
|
// Some(∅) runs no tests.
|
||||||
|
///
|
||||||
|
/// Name comparisons are performed in lower-case: values in filter_names must be
|
||||||
|
/// converted to lowercase specifically.
|
||||||
|
///
|
||||||
|
/// When an entry in filter_names matches a test, it is removed from that set.
|
||||||
|
/// This allows the caller to determine if any entries in the original set failed
|
||||||
|
/// to match any test, which may be user error.
|
||||||
|
fn filter_tests<'n, 't: 'n, T: TestCase>(
|
||||||
|
tests: &'t [T],
|
||||||
|
filter_names: &'n mut Option<HashSet<String>>,
|
||||||
|
) -> impl 'n + Iterator<Item = &'t T> {
|
||||||
|
tests.iter().filter(move |test| match filter_names {
|
||||||
|
// Accept all tests if no names are given
|
||||||
|
None => true,
|
||||||
|
Some(ref mut filter_names) => {
|
||||||
|
// Pop a match to the test name from the filter set, retaining the test
|
||||||
|
// if there was a match.
|
||||||
|
filter_names.remove(&test.get_name().to_lowercase())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run tests.
|
||||||
|
///
|
||||||
|
/// Only returns an Error if there was an internal error; test failures are Ok.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
pub fn generic<'a, TN: Iterator<Item = &'a str>>(
|
||||||
|
path: &str,
|
||||||
|
fc: FlashChip,
|
||||||
|
print_layout: bool,
|
||||||
|
output_format: OutputFormat,
|
||||||
|
test_names: Option<TN>,
|
||||||
|
) -> 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()?
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
&("Erase_and_Write", erase_write_test),
|
||||||
|
&("Fail_to_verify", verify_fail_test),
|
||||||
|
&("Lock", lock_test),
|
||||||
|
&("Lock_top_quad", partial_lock_test(LayoutNames::TopQuad)),
|
||||||
|
&(
|
||||||
|
"Lock_bottom_quad",
|
||||||
|
partial_lock_test(LayoutNames::BottomQuad),
|
||||||
|
),
|
||||||
|
&(
|
||||||
|
"Lock_bottom_half",
|
||||||
|
partial_lock_test(LayoutNames::BottomHalf),
|
||||||
|
),
|
||||||
|
&("Lock_top_half", partial_lock_test(LayoutNames::TopHalf)),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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 tests = filter_tests(tests, &mut filter_names);
|
||||||
|
|
||||||
|
// ------------------------.
|
||||||
|
// Run all the tests and collate the findings:
|
||||||
|
let results = tester::run_all_tests(fc, &cmd, tests);
|
||||||
|
|
||||||
|
// 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 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,
|
||||||
|
};
|
||||||
|
tester::collate_all_test_runs(&results, meta_data, output_format);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_device_name_test(env: &mut TestEnv) -> TestResult {
|
||||||
|
// Success means we got something back, which is good enough.
|
||||||
|
flashrom::name(env.cmd)?;
|
||||||
|
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) {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase_write_test(env: &mut TestEnv) -> TestResult {
|
||||||
|
if !env.is_golden() {
|
||||||
|
info!("Memory has been modified; reflashing to ensure erasure can be detected");
|
||||||
|
env.ensure_golden()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// With write protect enabled erase should fail.
|
||||||
|
env.wp.set_sw(true)?.set_hw(true)?;
|
||||||
|
if env.erase().is_ok() {
|
||||||
|
info!("Flashrom returned Ok but this may be incorrect; verifying");
|
||||||
|
if !env.is_golden() {
|
||||||
|
return Err("Hardware write protect asserted however can still erase!".into());
|
||||||
|
}
|
||||||
|
info!("Erase claimed to succeed but verify is Ok; assume erase failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// With write protect disabled erase should succeed.
|
||||||
|
env.wp.set_hw(false)?.set_sw(false)?;
|
||||||
|
env.erase()?;
|
||||||
|
if env.is_golden() {
|
||||||
|
return Err("Successful erase didn't modify memory".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lock_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)?;
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
parse_os_release(buf.lines().flatten())
|
||||||
|
} else {
|
||||||
|
info!("Unable to read /etc/os-release to probe system information");
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
match release_info.get("ID") {
|
||||||
|
Some(id) if id == "chromeos" || id == "chromiumos" => Ok(()),
|
||||||
|
oid => {
|
||||||
|
let id = match oid {
|
||||||
|
Some(s) => s,
|
||||||
|
None => "UNKNOWN",
|
||||||
|
};
|
||||||
|
Err(format!(
|
||||||
|
"Test host os-release \"{}\" should be but is not chromeos",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
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() {
|
||||||
|
return Err(
|
||||||
|
"Section should be locked, should not have been overwritable with random data"
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !env.is_golden() {
|
||||||
|
return Err("Section didn't lock, has been overwritten with random data!".into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_fail_test(env: &mut TestEnv) -> TestResult {
|
||||||
|
// Comparing the flash contents to random data says they're not the same.
|
||||||
|
match env.verify(env.random_data_file()) {
|
||||||
|
Ok(_) => Err("Verification says flash is full of random data".into()),
|
||||||
|
Err(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ad-hoc parsing of os-release(5); mostly according to the spec,
|
||||||
|
/// but ignores quotes and escaping.
|
||||||
|
fn parse_os_release<I: IntoIterator<Item = String>>(lines: I) -> HashMap<String, String> {
|
||||||
|
fn parse_line(line: String) -> Option<(String, String)> {
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let delimiter = match line.find('=') {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => {
|
||||||
|
warn!("os-release entry seems malformed: {:?}", line);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some((
|
||||||
|
line[..delimiter].to_owned(),
|
||||||
|
line[delimiter + 1..].to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.into_iter().filter_map(parse_line).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_os_release() {
|
||||||
|
let lines = [
|
||||||
|
"BUILD_ID=12516.0.0",
|
||||||
|
"# this line is a comment followed by an empty line",
|
||||||
|
"",
|
||||||
|
"ID_LIKE=chromiumos",
|
||||||
|
"ID=chromeos",
|
||||||
|
"VERSION=79",
|
||||||
|
"EMPTY_VALUE=",
|
||||||
|
];
|
||||||
|
let map = parse_os_release(lines.iter().map(|&s| s.to_owned()));
|
||||||
|
|
||||||
|
fn get<'a, 'b>(m: &'a HashMap<String, String>, k: &'b str) -> Option<&'a str> {
|
||||||
|
m.get(k).map(|s| s.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(get(&map, "ID"), Some("chromeos"));
|
||||||
|
assert_eq!(get(&map, "BUILD_ID"), Some("12516.0.0"));
|
||||||
|
assert_eq!(get(&map, "EMPTY_VALUE"), Some(""));
|
||||||
|
assert_eq!(get(&map, ""), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_name_filter() {
|
||||||
|
let test_one = ("Test One", |_: &mut TestEnv| Ok(()));
|
||||||
|
let test_two = ("Test Two", |_: &mut TestEnv| Ok(()));
|
||||||
|
let tests: &[&dyn TestCase] = &[&test_one, &test_two];
|
||||||
|
|
||||||
|
let mut names = None;
|
||||||
|
// All tests pass through
|
||||||
|
assert_eq!(filter_tests(tests, &mut names).count(), 2);
|
||||||
|
|
||||||
|
names = Some(["test two"].iter().map(|s| s.to_string()).collect());
|
||||||
|
// Filtered out test one
|
||||||
|
assert_eq!(filter_tests(tests, &mut names).count(), 1);
|
||||||
|
|
||||||
|
names = Some(["test three"].iter().map(|s| s.to_string()).collect());
|
||||||
|
// No tests emitted
|
||||||
|
assert_eq!(filter_tests(tests, &mut names).count(), 0);
|
||||||
|
// Name got left behind because no test matched it
|
||||||
|
assert_eq!(names.unwrap().len(), 1);
|
||||||
|
}
|
53
util/flashrom_tester/src/types.rs
Normal file
53
util/flashrom_tester/src/types.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
pub const BOLD: &str = "\x1b[1m";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
macro_rules! style_dbg {
|
||||||
|
($s: expr, $c: expr) => {
|
||||||
|
format!("{}{:?}{}", $c, $s, types::RESET)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! style {
|
||||||
|
($s: expr, $c: expr) => {
|
||||||
|
format!("{}{}{}", $c, $s, types::RESET)
|
||||||
|
};
|
||||||
|
}
|
298
util/flashrom_tester/src/utils.rs
Normal file
298
util/flashrom_tester/src/utils.rs
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Alternatively, this software may be distributed under the terms of the
|
||||||
|
// GNU General Public License ("GPL") version 2 as published by the Free
|
||||||
|
// Software Foundation.
|
||||||
|
//
|
||||||
|
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum LayoutNames {
|
||||||
|
TopQuad,
|
||||||
|
TopHalf,
|
||||||
|
BottomHalf,
|
||||||
|
BottomQuad,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub struct LayoutSizes {
|
||||||
|
half_sz: i64,
|
||||||
|
quad_sz: i64,
|
||||||
|
rom_top: i64,
|
||||||
|
bottom_half_top: i64,
|
||||||
|
bottom_quad_top: i64,
|
||||||
|
top_quad_bottom: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_layout_sizes(rom_sz: i64) -> Result<LayoutSizes, String> {
|
||||||
|
if rom_sz <= 0 {
|
||||||
|
return Err("invalid rom size provided".into());
|
||||||
|
}
|
||||||
|
if rom_sz & (rom_sz - 1) != 0 {
|
||||||
|
return Err("invalid rom size, not a power of 2".into());
|
||||||
|
}
|
||||||
|
Ok(LayoutSizes {
|
||||||
|
half_sz: rom_sz / 2,
|
||||||
|
quad_sz: rom_sz / 4,
|
||||||
|
rom_top: rom_sz - 1,
|
||||||
|
bottom_half_top: (rom_sz / 2) - 1,
|
||||||
|
bottom_quad_top: (rom_sz / 4) - 1,
|
||||||
|
top_quad_bottom: (rom_sz / 4) * 3,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout_section(ls: &LayoutSizes, ln: LayoutNames) -> (&'static str, i64, i64) {
|
||||||
|
match ln {
|
||||||
|
LayoutNames::TopQuad => ("TOP_QUAD", ls.top_quad_bottom, ls.quad_sz),
|
||||||
|
LayoutNames::TopHalf => ("TOP_HALF", ls.half_sz, ls.half_sz),
|
||||||
|
LayoutNames::BottomHalf => ("BOTTOM_HALF", 0, ls.half_sz),
|
||||||
|
LayoutNames::BottomQuad => ("BOTTOM_QUAD", 0, ls.quad_sz),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_layout_file<F: Write>(mut target: F, ls: &LayoutSizes) -> std::io::Result<()> {
|
||||||
|
writeln!(target, "000000:{:x} BOTTOM_QUAD", ls.bottom_quad_top)?;
|
||||||
|
writeln!(target, "000000:{:x} BOTTOM_HALF", ls.bottom_half_top)?;
|
||||||
|
writeln!(target, "{:x}:{:x} TOP_HALF", ls.half_sz, ls.rom_top)?;
|
||||||
|
writeln!(target, "{:x}:{:x} TOP_QUAD", ls.top_quad_bottom, ls.rom_top)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_hw_wp(dis: bool) -> Result<(), String> {
|
||||||
|
// The easist way to toggle the harware 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);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ac_power_warning() {
|
||||||
|
info!("*****************************");
|
||||||
|
info!("AC power *must be* connected!");
|
||||||
|
info!("*****************************");
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hardware_wp() -> std::result::Result<bool, String> {
|
||||||
|
let (_, wp) = parse_crosssystem(&collect_crosssystem()?)?;
|
||||||
|
Ok(wp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_crosssystem() -> Result<String, String> {
|
||||||
|
let cmd = match Command::new("crossystem").output() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return Err(format!("Failed to run crossystem: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !cmd.status.success() {
|
||||||
|
return Err(translate_command_error(&cmd).to_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;
|
||||||
|
// i. ) A bad exit code,
|
||||||
|
// ii.) A SIG killed us.
|
||||||
|
match output.status.code() {
|
||||||
|
Some(code) => {
|
||||||
|
let e = format!(
|
||||||
|
"{}\nExited with error code: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr),
|
||||||
|
code
|
||||||
|
);
|
||||||
|
Error::new(ErrorKind::Other, e)
|
||||||
|
}
|
||||||
|
None => Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Process terminated by a signal".to_string(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn construct_layout_file() {
|
||||||
|
use super::{construct_layout_file, get_layout_sizes};
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
construct_layout_file(
|
||||||
|
&mut buf,
|
||||||
|
&get_layout_sizes(0x10000).expect("64k is a valid chip size"),
|
||||||
|
)
|
||||||
|
.expect("no I/O errors expected");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&buf[..],
|
||||||
|
&b"000000:3fff BOTTOM_QUAD\n\
|
||||||
|
000000:7fff BOTTOM_HALF\n\
|
||||||
|
8000:ffff TOP_HALF\n\
|
||||||
|
c000:ffff TOP_QUAD\n"[..]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_layout_sizes() {
|
||||||
|
use super::get_layout_sizes;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_layout_sizes(-128).err(),
|
||||||
|
Some("invalid rom size provided".into())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_layout_sizes(3 << 20).err(),
|
||||||
|
Some("invalid rom size, not a power of 2".into())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_layout_sizes(64 << 10).unwrap(),
|
||||||
|
LayoutSizes {
|
||||||
|
half_sz: 0x8000,
|
||||||
|
quad_sz: 0x4000,
|
||||||
|
rom_top: 0xFFFF,
|
||||||
|
bottom_half_top: 0x7FFF,
|
||||||
|
bottom_quad_top: 0x3FFF,
|
||||||
|
top_quad_bottom: 0xC000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user