summaryrefslogtreecommitdiffstats
path: root/util/flashrom_tester/flashrom/src/cmd.rs
diff options
context:
space:
mode:
Diffstat (limited to 'util/flashrom_tester/flashrom/src/cmd.rs')
-rw-r--r--util/flashrom_tester/flashrom/src/cmd.rs489
1 files changed, 362 insertions, 127 deletions
diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs
index 3fd2ac04d..1f13a8ede 100644
--- a/util/flashrom_tester/flashrom/src/cmd.rs
+++ b/util/flashrom_tester/flashrom/src/cmd.rs
@@ -33,11 +33,57 @@
// Software Foundation.
//
-use crate::{FlashChip, FlashromError, FlashromOpt};
+use crate::{FlashChip, FlashromError};
-use std::process::Command;
+use std::{
+ ffi::{OsStr, OsString},
+ path::Path,
+ process::Command,
+};
-#[derive(PartialEq, Debug)]
+#[derive(Default)]
+pub struct FlashromOpt<'a> {
+ pub wp_opt: WPOpt,
+ pub io_opt: Option<IOOpt<'a>>,
+
+ pub flash_name: bool, // --flash-name
+ pub verbose: bool, // -V
+}
+
+#[derive(Default)]
+pub struct WPOpt {
+ pub range: Option<(i64, i64)>, // --wp-range x0 x1
+ pub status: bool, // --wp-status
+ pub list: bool, // --wp-list
+ pub enable: bool, // --wp-enable
+ pub disable: bool, // --wp-disable
+}
+
+pub enum OperationArgs<'a> {
+ /// The file is the whole chip.
+ EntireChip(&'a Path),
+ /// File is the size of the full chip, limited to a single named region.
+ ///
+ /// The required path is the file to use, and the optional path is a layout file
+ /// specifying how to locate regions (if unspecified, flashrom will attempt
+ /// to discover the layout itself).
+ FullFileRegion(&'a str, &'a Path, Option<&'a Path>),
+ /// File is the size of the single named region only.
+ ///
+ /// The required path is the file to use, and the optional path is a layout file
+ /// specifying how to locate regions (if unspecified, flashrom will attempt
+ /// to discover the layout itself).
+ RegionFileRegion(&'a str, &'a Path, Option<&'a Path>), // The file contains only the region
+}
+
+pub enum IOOpt<'a> {
+ Read(OperationArgs<'a>), // -r <file>
+ Write(OperationArgs<'a>), // -w <file>
+ Verify(OperationArgs<'a>), // -v <file>
+ Erase, // -E
+}
+
+#[derive(PartialEq, Eq, Debug)]
pub struct FlashromCmd {
pub path: String,
pub fc: FlashChip,
@@ -54,33 +100,204 @@ fn flashrom_extract_size(stdout: &str) -> Result<i64, FlashromError> {
.last()
.map(str::parse::<i64>)
{
- None => return Err("Found no purely-numeric lines in flashrom output".into()),
+ None => Err("Found no purely-numeric lines in flashrom output".into()),
Some(Err(e)) => {
- return Err(format!(
- "Failed to parse flashrom size output as integer: {}",
- e
- ))
+ Err(format!("Failed to parse flashrom size output as integer: {}", e).into())
}
Some(Ok(sz)) => Ok(sz),
}
}
+impl FlashromCmd {
+ fn dispatch(
+ &self,
+ fropt: FlashromOpt,
+ debug_name: &str,
+ ) -> Result<(String, String), FlashromError> {
+ let params = flashrom_decode_opts(fropt);
+ flashrom_dispatch(self.path.as_str(), &params, self.fc, debug_name)
+ }
+}
+
impl crate::Flashrom for FlashromCmd {
fn get_size(&self) -> Result<i64, FlashromError> {
- let (stdout, _) = flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc)?;
- let sz = String::from_utf8_lossy(&stdout);
+ let (stdout, _) =
+ flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc, "get_size")?;
+ flashrom_extract_size(&stdout)
+ }
+
+ fn name(&self) -> Result<(String, String), FlashromError> {
+ let opts = FlashromOpt {
+ flash_name: true,
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "name")?;
+ match extract_flash_name(&stdout) {
+ None => Err("Didn't find chip vendor/name in flashrom output".into()),
+ Some((vendor, name)) => Ok((vendor.into(), name.into())),
+ }
+ }
- flashrom_extract_size(&sz)
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Write(OperationArgs::FullFileRegion(
+ region,
+ path,
+ Some(layout),
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "write_file_with_layout")?;
+ Ok(true)
}
- fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let params = flashrom_decode_opts(fropt);
- flashrom_dispatch(self.path.as_str(), &params, self.fc)
+ fn wp_range(&self, range: (i64, i64), en: bool) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ enable: en,
+ disable: !en,
+ range: Some(range),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "wp_range")?;
+ Ok(true)
+ }
+
+ fn wp_list(&self) -> Result<String, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ list: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "wp_list")?;
+ if stdout.is_empty() {
+ return Err(
+ "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list"
+ .into(),
+ );
+ }
+ Ok(stdout)
+ }
+
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError> {
+ let status = if en { "en" } else { "dis" };
+ info!("See if chip write protect is {}abled", status);
+
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ status: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "wp_status")?;
+ let s = std::format!("write protect is {}abled", status);
+ Ok(stdout.contains(&s))
+ }
+
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> {
+ let range = if en {
+ let rom_sz: i64 = self.get_size()?;
+ (0, rom_sz) // (start, len)
+ } else {
+ (0, 0)
+ };
+ self.wp_range(range, en)?;
+ let status = if en { "en" } else { "dis" };
+ match self.wp_status(true) {
+ Ok(_ret) => {
+ info!("Successfully {}abled write-protect", status);
+ Ok(true)
+ }
+ Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e).into()),
+ }
+ }
+
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Read(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "read_into_file")?;
+ Ok(())
+ }
+
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Read(OperationArgs::RegionFileRegion(
+ region, path, None,
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "read_region_into_file")?;
+ Ok(())
+ }
+
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Write(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "write_from_file")?;
+ Ok(())
+ }
+
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Verify(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "verify_from_file")?;
+ Ok(())
+ }
+
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Verify(OperationArgs::RegionFileRegion(
+ region, path, None,
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "verify_region_from_file")?;
+ Ok(())
+ }
+
+ fn erase(&self) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Erase),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "erase")?;
+ Ok(())
+ }
+
+ fn can_control_hw_wp(&self) -> bool {
+ self.fc.can_control_hw_wp()
}
}
-fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> {
- let mut params = Vec::<String>::new();
+fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<OsString> {
+ let mut params = Vec::<OsString>::new();
// ------------ WARNING !!! ------------
// each param must NOT contain spaces!
@@ -89,134 +306,144 @@ fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> {
// wp_opt
if opts.wp_opt.range.is_some() {
let (x0, x1) = opts.wp_opt.range.unwrap();
- params.push("--wp-range".to_string());
- params.push(hex_string(x0));
- params.push(hex_string(x1));
+ params.push("--wp-range".into());
+ params.push(hex_range_string(x0, x1).into());
}
if opts.wp_opt.status {
- params.push("--wp-status".to_string());
+ params.push("--wp-status".into());
} else if opts.wp_opt.list {
- params.push("--wp-list".to_string());
+ params.push("--wp-list".into());
} else if opts.wp_opt.enable {
- params.push("--wp-enable".to_string());
+ params.push("--wp-enable".into());
} else if opts.wp_opt.disable {
- params.push("--wp-disable".to_string());
+ params.push("--wp-disable".into());
}
// io_opt
- if opts.io_opt.read.is_some() {
- params.push("-r".to_string());
- params.push(opts.io_opt.read.unwrap().to_string());
- } else if opts.io_opt.write.is_some() {
- params.push("-w".to_string());
- params.push(opts.io_opt.write.unwrap().to_string());
- } else if opts.io_opt.verify.is_some() {
- params.push("-v".to_string());
- params.push(opts.io_opt.verify.unwrap().to_string());
- } else if opts.io_opt.erase {
- params.push("-E".to_string());
- }
-
- // misc_opt
- if opts.layout.is_some() {
- params.push("-l".to_string());
- params.push(opts.layout.unwrap().to_string());
+ fn add_operation_args(opts: OperationArgs, params: &mut Vec<OsString>) {
+ let (file, region, layout) = match opts {
+ OperationArgs::EntireChip(file) => (Some(file), None, None),
+ OperationArgs::FullFileRegion(region, file, layout) => {
+ (Some(file), Some(region.to_string()), layout)
+ }
+ OperationArgs::RegionFileRegion(region, file, layout) => (
+ None,
+ Some(format!("{region}:{}", file.to_string_lossy())),
+ layout,
+ ),
+ };
+ if let Some(file) = file {
+ params.push(file.into())
+ }
+ if let Some(region) = region {
+ params.push("--include".into());
+ params.push(region.into())
+ }
+ if let Some(layout) = layout {
+ params.push("--layout".into());
+ params.push(layout.into())
+ }
}
- if opts.image.is_some() {
- params.push("-i".to_string());
- params.push(opts.image.unwrap().to_string());
+ if let Some(io) = opts.io_opt {
+ match io {
+ IOOpt::Read(args) => {
+ params.push("-r".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Write(args) => {
+ params.push("-w".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Verify(args) => {
+ params.push("-v".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Erase => params.push("-E".into()),
+ }
}
+ // misc_opt
if opts.flash_name {
- params.push("--flash-name".to_string());
- }
- if opts.ignore_fmap {
- params.push("--ignore-fmap".to_string());
+ params.push("--flash-name".into());
}
if opts.verbose {
- params.push("-V".to_string());
+ params.push("-V".into());
}
params
}
-fn flashrom_dispatch<S: AsRef<str>>(
+fn flashrom_dispatch<S: AsRef<OsStr>>(
path: &str,
params: &[S],
fc: FlashChip,
-) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ debug_name: &str,
+) -> Result<(String, String), FlashromError> {
// from man page:
// ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] '
- let mut args: Vec<&str> = vec!["-p", FlashChip::to(fc)];
+ let mut args: Vec<&OsStr> = vec![OsStr::new("-p"), OsStr::new(FlashChip::to(fc))];
args.extend(params.iter().map(S::as_ref));
info!("flashrom_dispatch() running: {} {:?}", path, args);
let output = match Command::new(path).args(&args).output() {
Ok(x) => x,
- Err(e) => return Err(format!("Failed to run flashrom: {}", e)),
+ Err(e) => return Err(format!("Failed to run flashrom: {}", e).into()),
};
+
+ let stdout = String::from_utf8_lossy(output.stdout.as_slice());
+ let stderr = String::from_utf8_lossy(output.stderr.as_slice());
+ debug!("{}()'stdout: {}.", debug_name, stdout);
+ debug!("{}()'stderr: {}.", debug_name, stderr);
+
if !output.status.success() {
// There is two cases on failure;
// i. ) A bad exit code,
// ii.) A SIG killed us.
match output.status.code() {
Some(code) => {
- return Err(format!(
- "{}\nExited with error code: {}",
- String::from_utf8_lossy(&output.stderr),
- code
- ));
+ return Err(format!("{}\nExited with error code: {}", stderr, code).into());
}
None => return Err("Process terminated by a signal".into()),
}
}
- Ok((output.stdout, output.stderr))
-}
-
-pub fn dut_ctrl_toggle_wp(en: bool) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let args = if en {
- ["fw_wp_en:off", "fw_wp:on"]
- } else {
- ["fw_wp_en:on", "fw_wp:off"]
- };
- dut_ctrl(&args)
+ Ok((stdout.into(), stderr.into()))
}
-pub fn dut_ctrl_servo_type() -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let args = ["servo_type"];
- dut_ctrl(&args)
+fn hex_range_string(s: i64, l: i64) -> String {
+ format!("{:#08X},{:#08X}", s, l)
}
-fn dut_ctrl(args: &[&str]) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
- let output = match Command::new("dut-control").args(args).output() {
- Ok(x) => x,
- Err(e) => return Err(format!("Failed to run dut-control: {}", e)),
- };
- if !output.status.success() {
- // There is two cases on failure;
- // i. ) A bad exit code,
- // ii.) A SIG killed us.
- match output.status.code() {
- Some(code) => {
- return Err(format!("Exited with error code: {}", code).into());
- }
- None => return Err("Process terminated by a signal".into()),
+/// Get a flash vendor and name from the first matching line of flashrom output.
+///
+/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name.
+/// This is usually the last line of output.
+fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> {
+ for line in stdout.lines() {
+ if !line.starts_with("vendor=\"") {
+ continue;
}
- }
- Ok((output.stdout, output.stderr))
-}
+ let tail = line.trim_start_matches("vendor=\"");
+ let mut split = tail.splitn(2, "\" name=\"");
+ let vendor = split.next();
+ let name = split.next().map(|s| s.trim_end_matches('"'));
-fn hex_string(v: i64) -> String {
- format!("{:#08X}", v).to_string()
+ match (vendor, name) {
+ (Some(v), Some(n)) => return Some((v, n)),
+ _ => continue,
+ }
+ }
+ None
}
#[cfg(test)]
mod tests {
+ use std::path::Path;
+
use super::flashrom_decode_opts;
- use crate::{FlashromOpt, IOOpt, WPOpt};
+ use super::{FlashromOpt, IOOpt, WPOpt};
#[test]
fn decode_wp_opt() {
@@ -237,7 +464,7 @@ mod tests {
status: true,
..Default::default()
},
- &["--wp-range", "0x000000", "0x0004D2", "--wp-status"],
+ &["--wp-range", "0x000000,0x0004D2", "--wp-status"],
);
test_wp_opt(
WPOpt {
@@ -267,7 +494,7 @@ mod tests {
fn test_io_opt(opts: IOOpt, expected: &[&str]) {
assert_eq!(
flashrom_decode_opts(FlashromOpt {
- io_opt: opts,
+ io_opt: Some(opts),
..Default::default()
}),
expected
@@ -275,62 +502,48 @@ mod tests {
}
test_io_opt(
- IOOpt {
- read: Some("foo.bin"),
- ..Default::default()
- },
+ IOOpt::Read(crate::cmd::OperationArgs::EntireChip(Path::new("foo.bin"))),
&["-r", "foo.bin"],
);
test_io_opt(
- IOOpt {
- write: Some("bar.bin"),
- ..Default::default()
- },
+ IOOpt::Write(crate::cmd::OperationArgs::EntireChip(Path::new("bar.bin"))),
&["-w", "bar.bin"],
);
test_io_opt(
- IOOpt {
- verify: Some("/tmp/baz.bin"),
- ..Default::default()
- },
- &["-v", "/tmp/baz.bin"],
+ IOOpt::Verify(crate::cmd::OperationArgs::EntireChip(Path::new("baz.bin"))),
+ &["-v", "baz.bin"],
);
+ test_io_opt(IOOpt::Erase, &["-E"]);
test_io_opt(
- IOOpt {
- erase: true,
- ..Default::default()
- },
- &["-E"],
+ IOOpt::Read(crate::cmd::OperationArgs::FullFileRegion(
+ "RO",
+ Path::new("foo.bin"),
+ Some(Path::new("baz.bin")),
+ )),
+ &["-r", "foo.bin", "--include", "RO", "--layout", "baz.bin"],
);
+
+ test_io_opt(
+ IOOpt::Read(crate::cmd::OperationArgs::RegionFileRegion(
+ "foo",
+ Path::new("bar.bin"),
+ None,
+ )),
+ &["-r", "--include", "foo:bar.bin"],
+ )
}
#[test]
fn decode_misc() {
//use Default::default;
- assert_eq!(
- flashrom_decode_opts(FlashromOpt {
- layout: Some("TestLayout"),
- ..Default::default()
- }),
- &["-l", "TestLayout"]
- );
-
- assert_eq!(
- flashrom_decode_opts(FlashromOpt {
- image: Some("TestImage"),
- ..Default::default()
- }),
- &["-i", "TestImage"]
- );
assert_eq!(
flashrom_decode_opts(FlashromOpt {
flash_name: true,
- ignore_fmap: true,
verbose: true,
..Default::default()
}),
- &["--flash-name", "--ignore-fmap", "-V"]
+ &["--flash-name", "-V"]
);
}
@@ -352,4 +565,26 @@ mod tests {
Err("Found no purely-numeric lines in flashrom output".into())
);
}
+
+ #[test]
+ fn extract_flash_name() {
+ use super::extract_flash_name;
+
+ assert_eq!(
+ extract_flash_name(
+ "coreboot table found at 0x7cc13000\n\
+ Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\
+ vendor=\"Winbond\" name=\"W25Q64DW\"\n"
+ ),
+ Some(("Winbond", "W25Q64DW"))
+ );
+
+ assert_eq!(
+ extract_flash_name(
+ "vendor name is TEST\n\
+ Something failed!"
+ ),
+ None
+ )
+ }
}