diff options
Diffstat (limited to 'util/flashrom_tester/flashrom')
-rw-r--r-- | util/flashrom_tester/flashrom/Cargo.toml | 5 | ||||
-rw-r--r-- | util/flashrom_tester/flashrom/src/cmd.rs | 489 | ||||
-rw-r--r-- | util/flashrom_tester/flashrom/src/flashromlib.rs | 184 | ||||
-rw-r--r-- | util/flashrom_tester/flashrom/src/lib.rs | 372 |
4 files changed, 628 insertions, 422 deletions
diff --git a/util/flashrom_tester/flashrom/Cargo.toml b/util/flashrom_tester/flashrom/Cargo.toml index 27216cbde..4d4fc2fea 100644 --- a/util/flashrom_tester/flashrom/Cargo.toml +++ b/util/flashrom_tester/flashrom/Cargo.toml @@ -3,7 +3,10 @@ name = "flashrom" version = "1.0.0" authors = ["Edward O'Callaghan <quasisec@chromium.org>", "Peter Marheine <pmarheine@chromium.org>"] +description = "Flashrom abstraction for the flashrom_tester tool." +license = "GPL-2.0-only" edition = "2018" [dependencies] -log = "0.4"
\ No newline at end of file +log = "0.4" +libflashrom = { path = "../../../bindings/rust/libflashrom" } diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs index 3fd2ac04d..1f13a8ede 100644 --- a/util/flashrom_tester/flashrom/src/cmd.rs +++ b/util/flashrom_tester/flashrom/src/cmd.rs @@ -33,11 +33,57 @@ // Software Foundation. // -use crate::{FlashChip, FlashromError, FlashromOpt}; +use crate::{FlashChip, FlashromError}; -use std::process::Command; +use std::{ + ffi::{OsStr, OsString}, + path::Path, + process::Command, +}; -#[derive(PartialEq, Debug)] +#[derive(Default)] +pub struct FlashromOpt<'a> { + pub wp_opt: WPOpt, + pub io_opt: Option<IOOpt<'a>>, + + pub flash_name: bool, // --flash-name + pub verbose: bool, // -V +} + +#[derive(Default)] +pub struct WPOpt { + pub range: Option<(i64, i64)>, // --wp-range x0 x1 + pub status: bool, // --wp-status + pub list: bool, // --wp-list + pub enable: bool, // --wp-enable + pub disable: bool, // --wp-disable +} + +pub enum OperationArgs<'a> { + /// The file is the whole chip. + EntireChip(&'a Path), + /// File is the size of the full chip, limited to a single named region. + /// + /// The required path is the file to use, and the optional path is a layout file + /// specifying how to locate regions (if unspecified, flashrom will attempt + /// to discover the layout itself). + FullFileRegion(&'a str, &'a Path, Option<&'a Path>), + /// File is the size of the single named region only. + /// + /// The required path is the file to use, and the optional path is a layout file + /// specifying how to locate regions (if unspecified, flashrom will attempt + /// to discover the layout itself). + RegionFileRegion(&'a str, &'a Path, Option<&'a Path>), // The file contains only the region +} + +pub enum IOOpt<'a> { + Read(OperationArgs<'a>), // -r <file> + Write(OperationArgs<'a>), // -w <file> + Verify(OperationArgs<'a>), // -v <file> + Erase, // -E +} + +#[derive(PartialEq, Eq, Debug)] pub struct FlashromCmd { pub path: String, pub fc: FlashChip, @@ -54,33 +100,204 @@ fn flashrom_extract_size(stdout: &str) -> Result<i64, FlashromError> { .last() .map(str::parse::<i64>) { - None => return Err("Found no purely-numeric lines in flashrom output".into()), + None => Err("Found no purely-numeric lines in flashrom output".into()), Some(Err(e)) => { - return Err(format!( - "Failed to parse flashrom size output as integer: {}", - e - )) + Err(format!("Failed to parse flashrom size output as integer: {}", e).into()) } Some(Ok(sz)) => Ok(sz), } } +impl FlashromCmd { + fn dispatch( + &self, + fropt: FlashromOpt, + debug_name: &str, + ) -> Result<(String, String), FlashromError> { + let params = flashrom_decode_opts(fropt); + flashrom_dispatch(self.path.as_str(), ¶ms, 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(), ¶ms, self.fc) + fn wp_range(&self, range: (i64, i64), en: bool) -> Result<bool, FlashromError> { + let opts = FlashromOpt { + wp_opt: WPOpt { + enable: en, + disable: !en, + range: Some(range), + ..Default::default() + }, + ..Default::default() + }; + + self.dispatch(opts, "wp_range")?; + Ok(true) + } + + fn wp_list(&self) -> Result<String, FlashromError> { + let opts = FlashromOpt { + wp_opt: WPOpt { + list: true, + ..Default::default() + }, + ..Default::default() + }; + + let (stdout, _) = self.dispatch(opts, "wp_list")?; + if stdout.is_empty() { + return Err( + "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list" + .into(), + ); + } + Ok(stdout) + } + + fn wp_status(&self, en: bool) -> Result<bool, FlashromError> { + let status = if en { "en" } else { "dis" }; + info!("See if chip write protect is {}abled", status); + + let opts = FlashromOpt { + wp_opt: WPOpt { + status: true, + ..Default::default() + }, + ..Default::default() + }; + + let (stdout, _) = self.dispatch(opts, "wp_status")?; + let s = std::format!("write protect is {}abled", status); + Ok(stdout.contains(&s)) + } + + fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> { + let range = if en { + let rom_sz: i64 = self.get_size()?; + (0, rom_sz) // (start, len) + } else { + (0, 0) + }; + self.wp_range(range, en)?; + let status = if en { "en" } else { "dis" }; + match self.wp_status(true) { + Ok(_ret) => { + info!("Successfully {}abled write-protect", status); + Ok(true) + } + Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e).into()), + } + } + + fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Read(OperationArgs::EntireChip(path))), + ..Default::default() + }; + + self.dispatch(opts, "read_into_file")?; + Ok(()) + } + + fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Read(OperationArgs::RegionFileRegion( + region, path, None, + ))), + ..Default::default() + }; + + self.dispatch(opts, "read_region_into_file")?; + Ok(()) + } + + fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Write(OperationArgs::EntireChip(path))), + ..Default::default() + }; + + self.dispatch(opts, "write_from_file")?; + Ok(()) + } + + fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Verify(OperationArgs::EntireChip(path))), + ..Default::default() + }; + + self.dispatch(opts, "verify_from_file")?; + Ok(()) + } + + fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Verify(OperationArgs::RegionFileRegion( + region, path, None, + ))), + ..Default::default() + }; + + self.dispatch(opts, "verify_region_from_file")?; + Ok(()) + } + + fn erase(&self) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Erase), + ..Default::default() + }; + + self.dispatch(opts, "erase")?; + Ok(()) + } + + fn can_control_hw_wp(&self) -> bool { + self.fc.can_control_hw_wp() } } -fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> { - let mut params = Vec::<String>::new(); +fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<OsString> { + let mut params = Vec::<OsString>::new(); // ------------ WARNING !!! ------------ // each param must NOT contain spaces! @@ -89,134 +306,144 @@ fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> { // wp_opt if opts.wp_opt.range.is_some() { let (x0, x1) = opts.wp_opt.range.unwrap(); - params.push("--wp-range".to_string()); - params.push(hex_string(x0)); - params.push(hex_string(x1)); + params.push("--wp-range".into()); + params.push(hex_range_string(x0, x1).into()); } if opts.wp_opt.status { - params.push("--wp-status".to_string()); + params.push("--wp-status".into()); } else if opts.wp_opt.list { - params.push("--wp-list".to_string()); + params.push("--wp-list".into()); } else if opts.wp_opt.enable { - params.push("--wp-enable".to_string()); + params.push("--wp-enable".into()); } else if opts.wp_opt.disable { - params.push("--wp-disable".to_string()); + params.push("--wp-disable".into()); } // io_opt - if opts.io_opt.read.is_some() { - params.push("-r".to_string()); - params.push(opts.io_opt.read.unwrap().to_string()); - } else if opts.io_opt.write.is_some() { - params.push("-w".to_string()); - params.push(opts.io_opt.write.unwrap().to_string()); - } else if opts.io_opt.verify.is_some() { - params.push("-v".to_string()); - params.push(opts.io_opt.verify.unwrap().to_string()); - } else if opts.io_opt.erase { - params.push("-E".to_string()); - } - - // misc_opt - if opts.layout.is_some() { - params.push("-l".to_string()); - params.push(opts.layout.unwrap().to_string()); + fn add_operation_args(opts: OperationArgs, params: &mut Vec<OsString>) { + let (file, region, layout) = match opts { + OperationArgs::EntireChip(file) => (Some(file), None, None), + OperationArgs::FullFileRegion(region, file, layout) => { + (Some(file), Some(region.to_string()), layout) + } + OperationArgs::RegionFileRegion(region, file, layout) => ( + None, + Some(format!("{region}:{}", file.to_string_lossy())), + layout, + ), + }; + if let Some(file) = file { + params.push(file.into()) + } + if let Some(region) = region { + params.push("--include".into()); + params.push(region.into()) + } + if let Some(layout) = layout { + params.push("--layout".into()); + params.push(layout.into()) + } } - if opts.image.is_some() { - params.push("-i".to_string()); - params.push(opts.image.unwrap().to_string()); + if let Some(io) = opts.io_opt { + match io { + IOOpt::Read(args) => { + params.push("-r".into()); + add_operation_args(args, &mut params); + } + IOOpt::Write(args) => { + params.push("-w".into()); + add_operation_args(args, &mut params); + } + IOOpt::Verify(args) => { + params.push("-v".into()); + add_operation_args(args, &mut params); + } + IOOpt::Erase => params.push("-E".into()), + } } + // misc_opt if opts.flash_name { - params.push("--flash-name".to_string()); - } - if opts.ignore_fmap { - params.push("--ignore-fmap".to_string()); + params.push("--flash-name".into()); } if opts.verbose { - params.push("-V".to_string()); + params.push("-V".into()); } params } -fn flashrom_dispatch<S: AsRef<str>>( +fn flashrom_dispatch<S: AsRef<OsStr>>( path: &str, params: &[S], fc: FlashChip, -) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { + debug_name: &str, +) -> Result<(String, String), FlashromError> { // from man page: // ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] ' - let mut args: Vec<&str> = vec!["-p", FlashChip::to(fc)]; + let mut args: Vec<&OsStr> = vec![OsStr::new("-p"), OsStr::new(FlashChip::to(fc))]; args.extend(params.iter().map(S::as_ref)); info!("flashrom_dispatch() running: {} {:?}", path, args); let output = match Command::new(path).args(&args).output() { Ok(x) => x, - Err(e) => return Err(format!("Failed to run flashrom: {}", e)), + Err(e) => return Err(format!("Failed to run flashrom: {}", e).into()), }; + + let stdout = String::from_utf8_lossy(output.stdout.as_slice()); + let stderr = String::from_utf8_lossy(output.stderr.as_slice()); + debug!("{}()'stdout: {}.", debug_name, stdout); + debug!("{}()'stderr: {}.", debug_name, stderr); + if !output.status.success() { // There is two cases on failure; // i. ) A bad exit code, // ii.) A SIG killed us. match output.status.code() { Some(code) => { - return Err(format!( - "{}\nExited with error code: {}", - String::from_utf8_lossy(&output.stderr), - code - )); + return Err(format!("{}\nExited with error code: {}", stderr, code).into()); } None => return Err("Process terminated by a signal".into()), } } - Ok((output.stdout, output.stderr)) -} - -pub fn dut_ctrl_toggle_wp(en: bool) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let args = if en { - ["fw_wp_en:off", "fw_wp:on"] - } else { - ["fw_wp_en:on", "fw_wp:off"] - }; - dut_ctrl(&args) + Ok((stdout.into(), stderr.into())) } -pub fn dut_ctrl_servo_type() -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let args = ["servo_type"]; - dut_ctrl(&args) +fn hex_range_string(s: i64, l: i64) -> String { + format!("{:#08X},{:#08X}", s, l) } -fn dut_ctrl(args: &[&str]) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let output = match Command::new("dut-control").args(args).output() { - Ok(x) => x, - Err(e) => return Err(format!("Failed to run dut-control: {}", e)), - }; - if !output.status.success() { - // There is two cases on failure; - // i. ) A bad exit code, - // ii.) A SIG killed us. - match output.status.code() { - Some(code) => { - return Err(format!("Exited with error code: {}", code).into()); - } - None => return Err("Process terminated by a signal".into()), +/// Get a flash vendor and name from the first matching line of flashrom output. +/// +/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name. +/// This is usually the last line of output. +fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> { + for line in stdout.lines() { + if !line.starts_with("vendor=\"") { + continue; } - } - Ok((output.stdout, output.stderr)) -} + let tail = line.trim_start_matches("vendor=\""); + let mut split = tail.splitn(2, "\" name=\""); + let vendor = split.next(); + let name = split.next().map(|s| s.trim_end_matches('"')); -fn hex_string(v: i64) -> String { - format!("{:#08X}", v).to_string() + match (vendor, name) { + (Some(v), Some(n)) => return Some((v, n)), + _ => continue, + } + } + None } #[cfg(test)] mod tests { + use std::path::Path; + use super::flashrom_decode_opts; - use crate::{FlashromOpt, IOOpt, WPOpt}; + use super::{FlashromOpt, IOOpt, WPOpt}; #[test] fn decode_wp_opt() { @@ -237,7 +464,7 @@ mod tests { status: true, ..Default::default() }, - &["--wp-range", "0x000000", "0x0004D2", "--wp-status"], + &["--wp-range", "0x000000,0x0004D2", "--wp-status"], ); test_wp_opt( WPOpt { @@ -267,7 +494,7 @@ mod tests { fn test_io_opt(opts: IOOpt, expected: &[&str]) { assert_eq!( flashrom_decode_opts(FlashromOpt { - io_opt: opts, + io_opt: Some(opts), ..Default::default() }), expected @@ -275,62 +502,48 @@ mod tests { } test_io_opt( - IOOpt { - read: Some("foo.bin"), - ..Default::default() - }, + IOOpt::Read(crate::cmd::OperationArgs::EntireChip(Path::new("foo.bin"))), &["-r", "foo.bin"], ); test_io_opt( - IOOpt { - write: Some("bar.bin"), - ..Default::default() - }, + IOOpt::Write(crate::cmd::OperationArgs::EntireChip(Path::new("bar.bin"))), &["-w", "bar.bin"], ); test_io_opt( - IOOpt { - verify: Some("/tmp/baz.bin"), - ..Default::default() - }, - &["-v", "/tmp/baz.bin"], + IOOpt::Verify(crate::cmd::OperationArgs::EntireChip(Path::new("baz.bin"))), + &["-v", "baz.bin"], ); + test_io_opt(IOOpt::Erase, &["-E"]); test_io_opt( - IOOpt { - erase: true, - ..Default::default() - }, - &["-E"], + IOOpt::Read(crate::cmd::OperationArgs::FullFileRegion( + "RO", + Path::new("foo.bin"), + Some(Path::new("baz.bin")), + )), + &["-r", "foo.bin", "--include", "RO", "--layout", "baz.bin"], ); + + test_io_opt( + IOOpt::Read(crate::cmd::OperationArgs::RegionFileRegion( + "foo", + Path::new("bar.bin"), + None, + )), + &["-r", "--include", "foo:bar.bin"], + ) } #[test] fn decode_misc() { //use Default::default; - assert_eq!( - flashrom_decode_opts(FlashromOpt { - layout: Some("TestLayout"), - ..Default::default() - }), - &["-l", "TestLayout"] - ); - - assert_eq!( - flashrom_decode_opts(FlashromOpt { - image: Some("TestImage"), - ..Default::default() - }), - &["-i", "TestImage"] - ); assert_eq!( flashrom_decode_opts(FlashromOpt { flash_name: true, - ignore_fmap: true, verbose: true, ..Default::default() }), - &["--flash-name", "--ignore-fmap", "-V"] + &["--flash-name", "-V"] ); } @@ -352,4 +565,26 @@ mod tests { Err("Found no purely-numeric lines in flashrom output".into()) ); } + + #[test] + fn extract_flash_name() { + use super::extract_flash_name; + + assert_eq!( + extract_flash_name( + "coreboot table found at 0x7cc13000\n\ + Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\ + vendor=\"Winbond\" name=\"W25Q64DW\"\n" + ), + Some(("Winbond", "W25Q64DW")) + ); + + assert_eq!( + extract_flash_name( + "vendor name is TEST\n\ + Something failed!" + ), + None + ) + } } diff --git a/util/flashrom_tester/flashrom/src/flashromlib.rs b/util/flashrom_tester/flashrom/src/flashromlib.rs new file mode 100644 index 000000000..5e1747b1b --- /dev/null +++ b/util/flashrom_tester/flashrom/src/flashromlib.rs @@ -0,0 +1,184 @@ +// Copyright 2022, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Alternatively, this software may be distributed under the terms of the +// GNU General Public License ("GPL") version 2 as published by the Free +// Software Foundation. +// + +use libflashrom::{Chip, Programmer}; + +use std::{cell::RefCell, convert::TryFrom, fs, path::Path}; + +use crate::{FlashChip, FlashromError}; + +#[derive(Debug)] +pub struct FlashromLib { + // RefCell required here to keep Flashrom trait immutable. + // Cant make Flashrom methods mut because WriteProtectState + // and TestEnv both keep a reference. + pub flashrom: RefCell<Chip>, + pub fc: FlashChip, +} + +impl FlashromLib { + pub fn new(fc: FlashChip, log_level: libflashrom::flashrom_log_level) -> FlashromLib { + libflashrom::set_log_level(Some(log_level)); + let (programmer, options) = FlashChip::to_split(fc); + let flashrom = Chip::new(Programmer::new(programmer, options).unwrap(), None).unwrap(); + FlashromLib { + flashrom: RefCell::new(flashrom), + fc, + } + } +} + +impl crate::Flashrom for FlashromLib { + fn get_size(&self) -> Result<i64, FlashromError> { + Ok(self.flashrom.borrow().get_size() as i64) + } + + fn name(&self) -> Result<(String, String), FlashromError> { + Ok(("not".to_string(), "implemented".to_string())) + } + + fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError> { + let mut cfg = libflashrom::WriteProtectCfg::new()?; + let start = usize::try_from(range.0).unwrap(); + let len = usize::try_from(range.1).unwrap(); + cfg.set_range::<std::ops::Range<usize>>(start..(start + len)); + cfg.set_mode(if wp_enable { + libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_HARDWARE + } else { + libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED + }); + self.flashrom.borrow_mut().set_wp(&cfg)?; + Ok(true) + } + + fn wp_list(&self) -> Result<String, FlashromError> { + let ranges = self.flashrom.borrow_mut().get_wp_ranges()?; + Ok(format!("{:?}", ranges)) + } + + fn wp_status(&self, en: bool) -> Result<bool, FlashromError> { + let ret = self + .flashrom + .borrow_mut() + .get_wp() + .map_err(|e| format!("{:?}", e))? + .get_mode(); + if en { + Ok(ret != libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED) + } else { + Ok(ret == libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED) + } + } + + fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> { + let range = if en { (0, self.get_size()?) } else { (0, 0) }; + self.wp_range(range, en) + } + + fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> { + let buf = self.flashrom.borrow_mut().image_read(None)?; + fs::write(path, buf).map_err(|error| error.to_string())?; + Ok(()) + } + + fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?; + layout.include_region(region)?; + let range = layout.get_region_range(region)?; + let buf = self.flashrom.borrow_mut().image_read(None)?; + fs::write(path, &buf[range]).map_err(|error| error.to_string())?; + Ok(()) + } + + fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let mut buf = fs::read(path).map_err(|error| error.to_string())?; + self.flashrom.borrow_mut().image_write(&mut buf, None)?; + Ok(()) + } + + fn write_from_file_region( + &self, + path: &Path, + region: &str, + layout: &Path, + ) -> Result<bool, FlashromError> { + let buf = fs::read(layout).map_err(|error| error.to_string())?; + let buf = String::from_utf8(buf).unwrap(); + let mut layout: libflashrom::Layout = buf + .parse() + .map_err(|e: Box<dyn std::error::Error>| e.to_string())?; + layout.include_region(region)?; + let mut buf = fs::read(path).map_err(|error| error.to_string())?; + self.flashrom + .borrow_mut() + .image_write(&mut buf, Some(layout))?; + Ok(true) + } + + fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let buf = fs::read(path).map_err(|error| error.to_string())?; + self.flashrom.borrow_mut().image_verify(&buf, None)?; + Ok(()) + } + + fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?; + layout.include_region(region)?; + let range = layout.get_region_range(region)?; + let region_data = fs::read(path).map_err(|error| error.to_string())?; + if region_data.len() != range.len() { + return Err(format!( + "verify region range ({}) does not match provided file size ({})", + range.len(), + region_data.len() + ) + .into()); + } + let mut buf = vec![0; self.get_size()? as usize]; + buf[range].copy_from_slice(®ion_data); + self.flashrom + .borrow_mut() + .image_verify(&buf, Some(layout))?; + Ok(()) + } + + fn erase(&self) -> Result<(), FlashromError> { + self.flashrom.borrow_mut().erase()?; + Ok(()) + } + + fn can_control_hw_wp(&self) -> bool { + self.fc.can_control_hw_wp() + } +} diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs index 734e3ff4a..41393e841 100644 --- a/util/flashrom_tester/flashrom/src/lib.rs +++ b/util/flashrom_tester/flashrom/src/lib.rs @@ -37,36 +37,41 @@ extern crate log; mod cmd; +mod flashromlib; -pub use cmd::{dut_ctrl_toggle_wp, FlashromCmd}; +use std::{error, fmt, path::Path}; -#[derive(Copy, Clone, PartialEq, Debug)] +pub use cmd::FlashromCmd; +pub use flashromlib::FlashromLib; + +pub use libflashrom::{ + flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR, + FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum FlashChip { - EC, HOST, - SERVO, - DEDIPROG, } impl FlashChip { pub fn from(s: &str) -> Result<FlashChip, &str> { - let r = match s { - "ec" => Ok(FlashChip::EC), + match s { "host" => Ok(FlashChip::HOST), - "servo" => Ok(FlashChip::SERVO), - "dediprog" => Ok(FlashChip::DEDIPROG), _ => Err("cannot convert str to enum"), - }; - return r; + } } pub fn to(fc: FlashChip) -> &'static str { - let r = match fc { - FlashChip::EC => "ec", + match fc { FlashChip::HOST => "host", - FlashChip::SERVO => "ft2231_spi:type=servo-v2", - FlashChip::DEDIPROG => "dediprog", - }; - return r; + } + } + + /// Return the programmer string and optional programmer options + pub fn to_split(fc: FlashChip) -> (&'static str, Option<&'static str>) { + let programmer = FlashChip::to(fc); + let mut bits = programmer.splitn(2, ':'); + (bits.next().unwrap(), bits.next()) } /// Return whether the hardware write protect signal can be controlled. @@ -75,307 +80,86 @@ impl FlashChip { /// disabled. pub fn can_control_hw_wp(&self) -> bool { match self { - FlashChip::HOST | FlashChip::EC => true, - FlashChip::SERVO | FlashChip::DEDIPROG => false, + FlashChip::HOST => true, } } } -pub type FlashromError = String; - -#[derive(Default)] -pub struct FlashromOpt<'a> { - pub wp_opt: WPOpt, - pub io_opt: IOOpt<'a>, - - pub layout: Option<&'a str>, // -l <file> - pub image: Option<&'a str>, // -i <name> - - pub flash_name: bool, // --flash-name - pub ignore_fmap: bool, // --ignore-fmap - pub verbose: bool, // -V -} - -#[derive(Default)] -pub struct WPOpt { - pub range: Option<(i64, i64)>, // --wp-range x0 x1 - pub status: bool, // --wp-status - pub list: bool, // --wp-list - pub enable: bool, // --wp-enable - pub disable: bool, // --wp-disable +#[derive(Debug, PartialEq, Eq)] +pub struct FlashromError { + msg: String, } -#[derive(Default)] -pub struct IOOpt<'a> { - pub read: Option<&'a str>, // -r <file> - pub write: Option<&'a str>, // -w <file> - pub verify: Option<&'a str>, // -v <file> - pub erase: bool, // -E -} - -pub trait Flashrom { - fn get_size(&self) -> Result<i64, FlashromError>; - fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError>; -} - -pub fn name(cmd: &cmd::FlashromCmd) -> Result<(String, String), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - ..Default::default() - }, - - flash_name: true, - - ..Default::default() - }; - - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); - debug!("name()'stdout: {:#?}.", output); - debug!("name()'stderr: {:#?}.", eoutput); - - match extract_flash_name(&output) { - None => Err("Didn't find chip vendor/name in flashrom output".into()), - Some((vendor, name)) => Ok((vendor.into(), name.into())), - } -} - -/// Get a flash vendor and name from the first matching line of flashrom output. -/// -/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name. -/// This is usually the last line of output. -fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> { - for line in stdout.lines() { - if !line.starts_with("vendor=\"") { - continue; - } - - let tail = line.trim_start_matches("vendor=\""); - let mut split = tail.splitn(2, "\" name=\""); - let vendor = split.next(); - let name = split.next().map(|s| s.trim_end_matches('"')); - - match (vendor, name) { - (Some(v), Some(n)) => return Some((v, n)), - _ => continue, - } +impl fmt::Display for FlashromError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.msg) } - None -} - -pub struct ROMWriteSpecifics<'a> { - pub layout_file: Option<&'a str>, - pub write_file: Option<&'a str>, - pub name_file: Option<&'a str>, -} - -pub fn write_file_with_layout( - cmd: &cmd::FlashromCmd, - rws: &ROMWriteSpecifics, -) -> Result<bool, FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - write: rws.write_file, - ..Default::default() - }, - - layout: rws.layout_file, - image: rws.name_file, - - ignore_fmap: true, - - ..Default::default() - }; - - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); - debug!("write_file_with_layout()'stdout:\n{}.", output); - debug!("write_file_with_layout()'stderr:\n{}.", eoutput); - Ok(true) } -pub fn wp_range( - cmd: &cmd::FlashromCmd, - range: (i64, i64), - wp_enable: bool, -) -> Result<bool, FlashromError> { - let opts = FlashromOpt { - wp_opt: WPOpt { - range: Some(range), - enable: wp_enable, - ..Default::default() - }, - ..Default::default() - }; +impl error::Error for FlashromError {} - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); - debug!("wp_range()'stdout:\n{}.", output); - debug!("wp_range()'stderr:\n{}.", eoutput); - Ok(true) -} - -pub fn wp_list(cmd: &cmd::FlashromCmd) -> Result<String, FlashromError> { - let opts = FlashromOpt { - wp_opt: WPOpt { - list: true, - ..Default::default() - }, - ..Default::default() - }; - - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - if output.len() == 0 { - return Err( - "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list".into(), - ); +impl<T> From<T> for FlashromError +where + T: Into<String>, +{ + fn from(msg: T) -> Self { + FlashromError { msg: msg.into() } } - Ok(output.to_string()) } -pub fn wp_status(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> { - let status = if en { "en" } else { "dis" }; - info!("See if chip write protect is {}abled", status); - - let opts = FlashromOpt { - wp_opt: WPOpt { - status: true, - ..Default::default() - }, - ..Default::default() - }; - - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - - debug!("wp_status():\n{}", output); - - let s = std::format!("write protect is {}abled", status); - Ok(output.contains(&s)) -} - -pub fn wp_toggle(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> { - let status = if en { "en" } else { "dis" }; - - // For MTD, --wp-range and --wp-enable must be used simultaneously. - let range = if en { - let rom_sz: i64 = cmd.get_size()?; - Some((0, rom_sz)) // (start, len) - } else { - None - }; - - let opts = FlashromOpt { - wp_opt: WPOpt { - range: range, - enable: en, - disable: !en, - ..Default::default() - }, - ..Default::default() - }; - - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); +pub trait Flashrom { + /// Returns the size of the flash in bytes. + fn get_size(&self) -> Result<i64, FlashromError>; - debug!("wp_toggle()'stdout:\n{}.", output); - debug!("wp_toggle()'stderr:\n{}.", eoutput); + /// Returns the vendor name and the flash name. + fn name(&self) -> Result<(String, String), FlashromError>; - match wp_status(&cmd, true) { - Ok(_ret) => { - info!("Successfully {}abled write-protect", status); - Ok(true) - } - Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e)), - } -} + /// Set write protect status and range. + fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError>; -pub fn read(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - read: Some(path), - ..Default::default() - }, - ..Default::default() - }; + /// Read the write protect regions for the flash. + fn wp_list(&self) -> Result<String, FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("read():\n{}", output); - Ok(()) -} + /// Return true if the flash write protect status matches `en`. + fn wp_status(&self, en: bool) -> Result<bool, FlashromError>; -pub fn write(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - write: Some(path), - ..Default::default() - }, - ..Default::default() - }; + /// Set write protect status. + /// If en=true sets wp_range to the whole chip (0,getsize()). + /// If en=false sets wp_range to (0,0). + /// This is due to the MTD driver, which requires wp enable to use a range + /// length != 0 and wp disable to have the range 0,0. + fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("write():\n{}", output); - Ok(()) -} + /// Read the whole flash to the file specified by `path`. + fn read_into_file(&self, path: &Path) -> Result<(), FlashromError>; -pub fn verify(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - verify: Some(path), - ..Default::default() - }, - ..Default::default() - }; + /// Read only a region of the flash into the file specified by `path`. Note + /// the first byte written to the file is the first byte from the region. + fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("verify():\n{}", output); - Ok(()) -} + /// Write the whole flash to the file specified by `path`. + fn write_from_file(&self, path: &Path) -> Result<(), FlashromError>; -pub fn erase(cmd: &cmd::FlashromCmd) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - erase: true, - ..Default::default() - }, - ..Default::default() - }; + /// Write only a region of the flash. + /// `path` is a file of the size of the whole flash. + /// The `region` name corresponds to a region name in the `layout` file, not the flash. + fn write_from_file_region( + &self, + path: &Path, + region: &str, + layout: &Path, + ) -> Result<bool, FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("erase():\n{}", output); - Ok(()) -} + /// Verify the whole flash against the file specified by `path`. + fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError>; -#[cfg(test)] -mod tests { - #[test] - fn extract_flash_name() { - use super::extract_flash_name; + /// Verify only the region against the file specified by `path`. + /// Note the first byte in the file is matched against the first byte of the region. + fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>; - assert_eq!( - extract_flash_name( - "coreboot table found at 0x7cc13000\n\ - Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\ - vendor=\"Winbond\" name=\"W25Q64DW\"\n" - ), - Some(("Winbond", "W25Q64DW")) - ); + /// Erase the whole flash. + fn erase(&self) -> Result<(), FlashromError>; - assert_eq!( - extract_flash_name( - "vendor name is TEST\n\ - Something failed!" - ), - None - ) - } + /// Return true if the hardware write protect of this flash can be controlled. + fn can_control_hw_wp(&self) -> bool; } |