diff options
Diffstat (limited to 'util/flashrom_tester/flashrom')
-rw-r--r-- | util/flashrom_tester/flashrom/Cargo.toml | 9 | ||||
-rw-r--r-- | util/flashrom_tester/flashrom/src/cmd.rs | 355 | ||||
-rw-r--r-- | util/flashrom_tester/flashrom/src/lib.rs | 381 |
3 files changed, 745 insertions, 0 deletions
diff --git a/util/flashrom_tester/flashrom/Cargo.toml b/util/flashrom_tester/flashrom/Cargo.toml new file mode 100644 index 00000000..27216cbd --- /dev/null +++ b/util/flashrom_tester/flashrom/Cargo.toml @@ -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"
\ No newline at end of file diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs new file mode 100644 index 00000000..3fd2ac04 --- /dev/null +++ b/util/flashrom_tester/flashrom/src/cmd.rs @@ -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()) + ); + } +} diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs new file mode 100644 index 00000000..734e3ff4 --- /dev/null +++ b/util/flashrom_tester/flashrom/src/lib.rs @@ -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 + ) + } +} |