aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/flashrom.bash-completion.tmpl77
-rw-r--r--util/flashrom_tester/.cargo/config.toml2
-rw-r--r--util/flashrom_tester/.gitignore2
-rw-r--r--util/flashrom_tester/Cargo.toml39
-rw-r--r--util/flashrom_tester/build.rs5
-rw-r--r--util/flashrom_tester/flashrom/Cargo.toml12
-rw-r--r--util/flashrom_tester/flashrom/src/cmd.rs590
-rw-r--r--util/flashrom_tester/flashrom/src/flashromlib.rs184
-rw-r--r--util/flashrom_tester/flashrom/src/lib.rs165
-rwxr-xr-xutil/flashrom_tester/flashrom_remote.sh2
-rw-r--r--util/flashrom_tester/src/cros_sysinfo.rs70
-rw-r--r--util/flashrom_tester/src/lib.rs46
-rw-r--r--util/flashrom_tester/src/logger.rs149
-rw-r--r--util/flashrom_tester/src/main.rs211
-rw-r--r--util/flashrom_tester/src/rand_util.rs80
-rw-r--r--util/flashrom_tester/src/tester.rs567
-rw-r--r--util/flashrom_tester/src/tests.rs398
-rw-r--r--util/flashrom_tester/src/types.rs72
-rw-r--r--util/flashrom_tester/src/utils.rs241
-rw-r--r--util/flashrom_udev.rules (renamed from util/z60_flashrom.rules)4
-rwxr-xr-xutil/getrevision.sh2
-rw-r--r--util/ich_descriptors_tool/Makefile23
-rw-r--r--util/ich_descriptors_tool/ich_descriptors_tool.c30
-rw-r--r--util/ich_descriptors_tool/meson.build5
-rw-r--r--util/lint/helper_functions.sh45
-rwxr-xr-xutil/lint/lint-extended-020-signed-off-by23
-rw-r--r--util/manibuilder/Dockerfile.alpine24
-rw-r--r--util/manibuilder/Dockerfile.anita65
-rw-r--r--util/manibuilder/Dockerfile.centos18
-rw-r--r--util/manibuilder/Dockerfile.debian-debootstrap21
-rw-r--r--util/manibuilder/Dockerfile.djgpp30
-rw-r--r--util/manibuilder/Dockerfile.fedora19
-rw-r--r--util/manibuilder/Dockerfile.qemu-user-static3
-rw-r--r--util/manibuilder/Dockerfile.ubuntu-debootstrap34
-rw-r--r--util/manibuilder/Makefile93
-rw-r--r--util/manibuilder/Makefile.anita52
-rw-r--r--util/manibuilder/Makefile.targets223
-rw-r--r--util/manibuilder/README.md72
-rw-r--r--util/manibuilder/anita-wrapper.sh18
-rw-r--r--util/manibuilder/mani-wrapper.sh9
-rw-r--r--util/meson.build1
-rw-r--r--util/shell.nix18
-rwxr-xr-xutil/ubertest/ubertest.sh4
43 files changed, 3727 insertions, 21 deletions
diff --git a/util/flashrom.bash-completion.tmpl b/util/flashrom.bash-completion.tmpl
new file mode 100644
index 00000000..afb7ae98
--- /dev/null
+++ b/util/flashrom.bash-completion.tmpl
@@ -0,0 +1,77 @@
+# Completion file for bash
+#
+# This file is part of the flashrom project.
+#
+# Copyright 2022 Alexander Goncharov <chat@joursoir.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+
+_flashrom()
+{
+ local cur prev OPTS
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ case $prev in
+ '-r'|'--read'|'-w'|'--write'|'-v'|'--verify'|'-l'|'--layout'| \
+ '--fmap-file'|'-o'|'--output'|'--flash-contents')
+ local IFS=$'\n'
+ compopt -o filenames
+ COMPREPLY=( $(compgen -f -- $cur) )
+ return 0
+ ;;
+ '-c'|'--chip'|'--wp-range'|'--wp-region'|'-i'|'--include')
+ return 0
+ ;;
+ '-p'|'--programmer')
+ COMPREPLY=( $(compgen -W "@PROGRAMMERS@" -- $cur) )
+ return 0
+ ;;
+ '-h'|'--help'|'-R'|'--version'|'-L'|'--list-supported')
+ return 0
+ ;;
+ esac
+ OPTS="--help
+ --version
+ --read
+ --write
+ --verify
+ --erase
+ --verbose
+ --chip
+ --force
+ --noverify
+ --noverify-all
+ --extract
+ --layout
+ --wp-disable
+ --wp-enable
+ --wp-list
+ --wp-status
+ --wp-range
+ --wp-region
+ --flash-name
+ --flash-size
+ --fmap
+ --fmap-file
+ --ifd
+ --include
+ --output
+ --flash-contents
+ --list-supported
+ --progress
+ --programmer"
+ COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+ return 0
+}
+
+complete -F _flashrom flashrom
diff --git a/util/flashrom_tester/.cargo/config.toml b/util/flashrom_tester/.cargo/config.toml
new file mode 100644
index 00000000..8af59dd8
--- /dev/null
+++ b/util/flashrom_tester/.cargo/config.toml
@@ -0,0 +1,2 @@
+[env]
+RUST_TEST_THREADS = "1"
diff --git a/util/flashrom_tester/.gitignore b/util/flashrom_tester/.gitignore
new file mode 100644
index 00000000..1e7caa9e
--- /dev/null
+++ b/util/flashrom_tester/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target/
diff --git a/util/flashrom_tester/Cargo.toml b/util/flashrom_tester/Cargo.toml
new file mode 100644
index 00000000..e6ed9c04
--- /dev/null
+++ b/util/flashrom_tester/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "flashrom_tester"
+version = "1.6.0"
+authors = ["Edward O'Callaghan <quasisec@chromium.org>",
+ "Peter Marheine <pmarheine@chromium.org>"]
+description = "A tool to verify flashrom and flash chip behaviour."
+license = "GPL-2.0-only"
+edition = "2018"
+build = "build.rs"
+
+[lib]
+name = "flashrom_tester"
+
+[[bin]]
+name = "flashrom_tester"
+required-features = ["cli"]
+
+[dependencies]
+atty = "0.2"
+built = { version = "0.5", features = ["chrono"] }
+chrono = { version = "0.4", optional = true }
+clap = { version = "2.33", default-features = false, optional = true }
+flashrom = { path = "flashrom/" }
+libc = "0.2"
+log = { version = "0.4", features = ["std"] }
+rand = "0.6.4"
+serde_json = "1"
+sys-info = "0.9"
+
+[build-dependencies]
+built = { version = "0.5", features = ["chrono"] }
+
+[dev-dependencies]
+gag = "1"
+
+[features]
+# Features required to build the CLI binary but not the library
+cli = ["chrono", "clap"]
+default = ["cli"]
diff --git a/util/flashrom_tester/build.rs b/util/flashrom_tester/build.rs
new file mode 100644
index 00000000..3800c173
--- /dev/null
+++ b/util/flashrom_tester/build.rs
@@ -0,0 +1,5 @@
+extern crate built;
+
+fn main() {
+ built::write_built_file().expect("Failed to acquire build-time information");
+}
diff --git a/util/flashrom_tester/flashrom/Cargo.toml b/util/flashrom_tester/flashrom/Cargo.toml
new file mode 100644
index 00000000..4d4fc2fe
--- /dev/null
+++ b/util/flashrom_tester/flashrom/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+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"
+libflashrom = { path = "../../../bindings/rust/libflashrom" }
diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs
new file mode 100644
index 00000000..1f13a8ed
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/cmd.rs
@@ -0,0 +1,590 @@
+//
+// 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};
+
+use std::{
+ ffi::{OsStr, OsString},
+ path::Path,
+ process::Command,
+};
+
+#[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,
+}
+
+/// 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 => Err("Found no purely-numeric lines in flashrom output".into()),
+ Some(Err(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, "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())),
+ }
+ }
+
+ 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 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<OsString> {
+ let mut params = Vec::<OsString>::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".into());
+ params.push(hex_range_string(x0, x1).into());
+ }
+ if opts.wp_opt.status {
+ params.push("--wp-status".into());
+ } else if opts.wp_opt.list {
+ params.push("--wp-list".into());
+ } else if opts.wp_opt.enable {
+ params.push("--wp-enable".into());
+ } else if opts.wp_opt.disable {
+ params.push("--wp-disable".into());
+ }
+
+ // io_opt
+ 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 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".into());
+ }
+ if opts.verbose {
+ params.push("-V".into());
+ }
+
+ params
+}
+
+fn flashrom_dispatch<S: AsRef<OsStr>>(
+ path: &str,
+ params: &[S],
+ fc: FlashChip,
+ debug_name: &str,
+) -> Result<(String, String), FlashromError> {
+ // from man page:
+ // ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] '
+ 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).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: {}", stderr, code).into());
+ }
+ None => return Err("Process terminated by a signal".into()),
+ }
+ }
+
+ Ok((stdout.into(), stderr.into()))
+}
+
+fn hex_range_string(s: i64, l: i64) -> String {
+ format!("{:#08X},{:#08X}", s, l)
+}
+
+/// 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
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::Path;
+
+ use super::flashrom_decode_opts;
+ use super::{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: Some(opts),
+ ..Default::default()
+ }),
+ expected
+ );
+ }
+
+ test_io_opt(
+ IOOpt::Read(crate::cmd::OperationArgs::EntireChip(Path::new("foo.bin"))),
+ &["-r", "foo.bin"],
+ );
+ test_io_opt(
+ IOOpt::Write(crate::cmd::OperationArgs::EntireChip(Path::new("bar.bin"))),
+ &["-w", "bar.bin"],
+ );
+ test_io_opt(
+ IOOpt::Verify(crate::cmd::OperationArgs::EntireChip(Path::new("baz.bin"))),
+ &["-v", "baz.bin"],
+ );
+ test_io_opt(IOOpt::Erase, &["-E"]);
+ test_io_opt(
+ 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 {
+ flash_name: true,
+ verbose: true,
+ ..Default::default()
+ }),
+ &["--flash-name", "-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())
+ );
+ }
+
+ #[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 00000000..5e1747b1
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/flashromlib.rs
@@ -0,0 +1,184 @@
+// Copyright 2022, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use libflashrom::{Chip, Programmer};
+
+use std::{cell::RefCell, convert::TryFrom, fs, path::Path};
+
+use crate::{FlashChip, FlashromError};
+
+#[derive(Debug)]
+pub struct FlashromLib {
+ // RefCell required here to keep Flashrom trait immutable.
+ // Cant make Flashrom methods mut because WriteProtectState
+ // and TestEnv both keep a reference.
+ pub flashrom: RefCell<Chip>,
+ pub fc: FlashChip,
+}
+
+impl FlashromLib {
+ pub fn new(fc: FlashChip, log_level: libflashrom::flashrom_log_level) -> FlashromLib {
+ libflashrom::set_log_level(Some(log_level));
+ let (programmer, options) = FlashChip::to_split(fc);
+ let flashrom = Chip::new(Programmer::new(programmer, options).unwrap(), None).unwrap();
+ FlashromLib {
+ flashrom: RefCell::new(flashrom),
+ fc,
+ }
+ }
+}
+
+impl crate::Flashrom for FlashromLib {
+ fn get_size(&self) -> Result<i64, FlashromError> {
+ Ok(self.flashrom.borrow().get_size() as i64)
+ }
+
+ fn name(&self) -> Result<(String, String), FlashromError> {
+ Ok(("not".to_string(), "implemented".to_string()))
+ }
+
+ fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError> {
+ let mut cfg = libflashrom::WriteProtectCfg::new()?;
+ let start = usize::try_from(range.0).unwrap();
+ let len = usize::try_from(range.1).unwrap();
+ cfg.set_range::<std::ops::Range<usize>>(start..(start + len));
+ cfg.set_mode(if wp_enable {
+ libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_HARDWARE
+ } else {
+ libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED
+ });
+ self.flashrom.borrow_mut().set_wp(&cfg)?;
+ Ok(true)
+ }
+
+ fn wp_list(&self) -> Result<String, FlashromError> {
+ let ranges = self.flashrom.borrow_mut().get_wp_ranges()?;
+ Ok(format!("{:?}", ranges))
+ }
+
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError> {
+ let ret = self
+ .flashrom
+ .borrow_mut()
+ .get_wp()
+ .map_err(|e| format!("{:?}", e))?
+ .get_mode();
+ if en {
+ Ok(ret != libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED)
+ } else {
+ Ok(ret == libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED)
+ }
+ }
+
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> {
+ let range = if en { (0, self.get_size()?) } else { (0, 0) };
+ self.wp_range(range, en)
+ }
+
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let buf = self.flashrom.borrow_mut().image_read(None)?;
+ fs::write(path, buf).map_err(|error| error.to_string())?;
+ Ok(())
+ }
+
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?;
+ layout.include_region(region)?;
+ let range = layout.get_region_range(region)?;
+ let buf = self.flashrom.borrow_mut().image_read(None)?;
+ fs::write(path, &buf[range]).map_err(|error| error.to_string())?;
+ Ok(())
+ }
+
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let mut buf = fs::read(path).map_err(|error| error.to_string())?;
+ self.flashrom.borrow_mut().image_write(&mut buf, None)?;
+ Ok(())
+ }
+
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError> {
+ let buf = fs::read(layout).map_err(|error| error.to_string())?;
+ let buf = String::from_utf8(buf).unwrap();
+ let mut layout: libflashrom::Layout = buf
+ .parse()
+ .map_err(|e: Box<dyn std::error::Error>| e.to_string())?;
+ layout.include_region(region)?;
+ let mut buf = fs::read(path).map_err(|error| error.to_string())?;
+ self.flashrom
+ .borrow_mut()
+ .image_write(&mut buf, Some(layout))?;
+ Ok(true)
+ }
+
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let buf = fs::read(path).map_err(|error| error.to_string())?;
+ self.flashrom.borrow_mut().image_verify(&buf, None)?;
+ Ok(())
+ }
+
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?;
+ layout.include_region(region)?;
+ let range = layout.get_region_range(region)?;
+ let region_data = fs::read(path).map_err(|error| error.to_string())?;
+ if region_data.len() != range.len() {
+ return Err(format!(
+ "verify region range ({}) does not match provided file size ({})",
+ range.len(),
+ region_data.len()
+ )
+ .into());
+ }
+ let mut buf = vec![0; self.get_size()? as usize];
+ buf[range].copy_from_slice(&region_data);
+ self.flashrom
+ .borrow_mut()
+ .image_verify(&buf, Some(layout))?;
+ Ok(())
+ }
+
+ fn erase(&self) -> Result<(), FlashromError> {
+ self.flashrom.borrow_mut().erase()?;
+ Ok(())
+ }
+
+ fn can_control_hw_wp(&self) -> bool {
+ self.fc.can_control_hw_wp()
+ }
+}
diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs
new file mode 100644
index 00000000..41393e84
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/lib.rs
@@ -0,0 +1,165 @@
+//
+// 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;
+mod flashromlib;
+
+use std::{error, fmt, path::Path};
+
+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 {
+ HOST,
+}
+
+impl FlashChip {
+ pub fn from(s: &str) -> Result<FlashChip, &str> {
+ match s {
+ "host" => Ok(FlashChip::HOST),
+ _ => Err("cannot convert str to enum"),
+ }
+ }
+ pub fn to(fc: FlashChip) -> &'static str {
+ match fc {
+ FlashChip::HOST => "host",
+ }
+ }
+
+ /// 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.
+ ///
+ /// 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 => true,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct FlashromError {
+ msg: String,
+}
+
+impl fmt::Display for FlashromError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.msg)
+ }
+}
+
+impl error::Error for FlashromError {}
+
+impl<T> From<T> for FlashromError
+where
+ T: Into<String>,
+{
+ fn from(msg: T) -> Self {
+ FlashromError { msg: msg.into() }
+ }
+}
+
+pub trait Flashrom {
+ /// Returns the size of the flash in bytes.
+ fn get_size(&self) -> Result<i64, FlashromError>;
+
+ /// Returns the vendor name and the flash name.
+ fn name(&self) -> Result<(String, String), FlashromError>;
+
+ /// Set write protect status and range.
+ fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError>;
+
+ /// Read the write protect regions for the flash.
+ fn wp_list(&self) -> Result<String, FlashromError>;
+
+ /// Return true if the flash write protect status matches `en`.
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError>;
+
+ /// 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>;
+
+ /// Read the whole flash to the file specified by `path`.
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError>;
+
+ /// 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>;
+
+ /// Write the whole flash to the file specified by `path`.
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError>;
+
+ /// 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>;
+
+ /// Verify the whole flash against the file specified by `path`.
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError>;
+
+ /// 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>;
+
+ /// Erase the whole flash.
+ fn erase(&self) -> Result<(), FlashromError>;
+
+ /// Return true if the hardware write protect of this flash can be controlled.
+ fn can_control_hw_wp(&self) -> bool;
+}
diff --git a/util/flashrom_tester/flashrom_remote.sh b/util/flashrom_tester/flashrom_remote.sh
new file mode 100755
index 00000000..8dba9430
--- /dev/null
+++ b/util/flashrom_tester/flashrom_remote.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+/usr/bin/ssh localhost -p 60024 -C /usr/sbin/flashrom "$@"
diff --git a/util/flashrom_tester/src/cros_sysinfo.rs b/util/flashrom_tester/src/cros_sysinfo.rs
new file mode 100644
index 00000000..37e1ec65
--- /dev/null
+++ b/util/flashrom_tester/src/cros_sysinfo.rs
@@ -0,0 +1,70 @@
+//
+// 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::fs;
+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 release_description() -> IoResult<String> {
+ for l in fs::read_to_string("/etc/lsb-release")?.lines() {
+ if l.starts_with("CHROMEOS_RELEASE_DESCRIPTION") {
+ return Ok(l.to_string());
+ }
+ }
+ Err(std::io::ErrorKind::NotFound.into())
+}
diff --git a/util/flashrom_tester/src/lib.rs b/util/flashrom_tester/src/lib.rs
new file mode 100644
index 00000000..d8f1cb6a
--- /dev/null
+++ b/util/flashrom_tester/src/lib.rs
@@ -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;
diff --git a/util/flashrom_tester/src/logger.rs b/util/flashrom_tester/src/logger.rs
new file mode 100644
index 00000000..c9c36406
--- /dev/null
+++ b/util/flashrom_tester/src/logger.rs
@@ -0,0 +1,149 @@
+//
+// 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;
+
+struct Logger {
+ level: log::LevelFilter,
+ color: types::Color,
+}
+
+impl log::Log for Logger {
+ fn enabled(&self, metadata: &log::Metadata) -> bool {
+ metadata.level() <= self.level
+ }
+
+ fn log(&self, record: &log::Record) {
+ // Write errors deliberately ignored
+ let stdout = std::io::stdout();
+ let mut lock = stdout.lock();
+ let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
+ let _ = write!(lock, "{}{} ", self.color.magenta, now);
+ let _ = write!(
+ lock,
+ "{}[ {} ]{} ",
+ self.color.yellow,
+ record.level(),
+ self.color.reset
+ );
+ let _ = writeln!(lock, "{}", record.args());
+ }
+
+ fn flush(&self) {
+ // Flush errors deliberately ignored
+ let _ = std::io::stdout().flush();
+ }
+}
+
+pub fn init(debug: bool) {
+ let mut logger = Logger {
+ level: log::LevelFilter::Info,
+ color: if atty::is(atty::Stream::Stdout) {
+ types::COLOR
+ } else {
+ types::NOCOLOR
+ },
+ };
+
+ if debug {
+ logger.level = log::LevelFilter::Debug;
+ }
+ log::set_max_level(logger.level);
+ log::set_boxed_logger(Box::new(logger)).unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+ use std::io::Read;
+
+ use super::Logger;
+ use flashrom_tester::types;
+ use log::{Level, LevelFilter, Log, Record};
+
+ fn run_records(records: &[Record]) -> String {
+ let buf = gag::BufferRedirect::stdout().unwrap();
+ {
+ let logger = Logger {
+ level: LevelFilter::Info,
+ color: types::COLOR,
+ };
+
+ for record in records {
+ if logger.enabled(record.metadata()) {
+ logger.log(record);
+ }
+ }
+ }
+ let mut ret = String::new();
+ buf.into_inner().read_to_string(&mut ret).unwrap();
+ ret
+ }
+
+ /// 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
+ // Split on the UTC timezone char
+ assert_eq!(
+ buf.split_once("Z ").unwrap().1,
+ "\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);
+ }
+}
diff --git a/util/flashrom_tester/src/main.rs b/util/flashrom_tester/src/main.rs
new file mode 100644
index 00000000..b8a2581a
--- /dev/null
+++ b/util/flashrom_tester/src/main.rs
@@ -0,0 +1,211 @@
+//
+// 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, Flashrom, FlashromCmd, FlashromLib};
+use flashrom_tester::{tester, tests};
+use std::sync::atomic::AtomicBool;
+
+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("libflashrom")
+ .long("libflashrom")
+ .takes_value(false)
+ .help("Test the flashrom library instead of a binary"),
+ )
+ .arg(
+ Arg::with_name("flashrom_binary")
+ .long("flashrom_binary")
+ .short("b")
+ .takes_value(true)
+ .required_unless("libflashrom")
+ .conflicts_with("libflashrom")
+ .help("Path to flashrom binary to test"),
+ )
+ .arg(
+ Arg::with_name("ccd_target_type")
+ .required(true)
+ .possible_values(&["host"]),
+ )
+ .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_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.is_present("log_debug"));
+ debug!("Args parsed and logging initialized OK");
+
+ debug!("Collecting crossystem info");
+ let crossystem =
+ flashrom_tester::utils::collect_crosssystem(&[]).expect("could not run crossystem");
+
+ let ccd_type = FlashChip::from(
+ matches
+ .value_of("ccd_target_type")
+ .expect("ccd_target_type should be required"),
+ )
+ .expect("ccd_target_type should admit only known types");
+
+ let cmd: Box<dyn Flashrom> = if matches.is_present("libflashrom") {
+ Box::new(FlashromLib::new(
+ ccd_type,
+ if matches.is_present("log_debug") {
+ flashrom::FLASHROM_MSG_DEBUG
+ } else {
+ flashrom::FLASHROM_MSG_WARN
+ },
+ ))
+ } else {
+ Box::new(FlashromCmd {
+ path: matches
+ .value_of("flashrom_binary")
+ .expect("flashrom_binary is required")
+ .to_string(),
+ fc: ccd_type,
+ })
+ };
+
+ let print_layout = matches.is_present("print-layout");
+ let output_format = matches
+ .value_of("output-format")
+ .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(
+ cmd.as_ref(),
+ ccd_type,
+ print_layout,
+ output_format,
+ test_names,
+ Some(handle_sigint()),
+ crossystem,
+ ) {
+ eprintln!("Failed to run tests: {:?}", e);
+ std::process::exit(1);
+ }
+}
+
+/// Catch exactly one SIGINT, printing a message in response and setting a flag.
+///
+/// The returned value is false by default, becoming true after a SIGINT is
+/// trapped.
+///
+/// Once a signal is trapped, the default behavior is restored (terminating
+/// the process) for future signals.
+fn handle_sigint() -> &'static AtomicBool {
+ use libc::c_int;
+ use std::sync::atomic::Ordering;
+
+ unsafe {
+ let _ = libc::signal(libc::SIGINT, sigint_handler as libc::sighandler_t);
+ }
+ static TERMINATE_FLAG: AtomicBool = AtomicBool::new(false);
+
+ extern "C" fn sigint_handler(_: c_int) {
+ const STDERR_FILENO: c_int = 2;
+ static MESSAGE: &[u8] = b"
+WARNING: terminating tests prematurely may leave Flash in an inconsistent state,
+rendering your machine unbootable. Testing will end on completion of the current
+test, or press ^C again to exit immediately (possibly bricking your machine).
+";
+
+ // Use raw write() because signal-safety is a very hard problem. Safe because this doesn't
+ // modify any memory.
+ let _ = unsafe {
+ libc::write(
+ STDERR_FILENO,
+ MESSAGE.as_ptr() as *const libc::c_void,
+ MESSAGE.len() as libc::size_t,
+ )
+ };
+ unsafe {
+ let _ = libc::signal(libc::SIGINT, libc::SIG_DFL);
+ }
+ TERMINATE_FLAG.store(true, Ordering::Release);
+ }
+
+ &TERMINATE_FLAG
+}
diff --git a/util/flashrom_tester/src/rand_util.rs b/util/flashrom_tester/src/rand_util.rs
new file mode 100644
index 00000000..a040c89a
--- /dev/null
+++ b/util/flashrom_tester/src/rand_util.rs
@@ -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::fs::File;
+use std::io::prelude::*;
+use std::io::BufWriter;
+use std::path::Path;
+
+use rand::prelude::*;
+
+pub fn gen_rand_testdata(path: &Path, size: usize) -> std::io::Result<()> {
+ let mut buf = BufWriter::new(File::create(path)?);
+
+ let mut a: Vec<u8> = vec![0; size];
+ 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 = Path::new("/tmp/idk_test00");
+ let path1 = Path::new("/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);
+ }
+}
diff --git a/util/flashrom_tester/src/tester.rs b/util/flashrom_tester/src/tester.rs
new file mode 100644
index 00000000..4629c2eb
--- /dev/null
+++ b/util/flashrom_tester/src/tester.rs
@@ -0,0 +1,567 @@
+//
+// 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::FlashromError;
+use flashrom::{FlashChip, Flashrom};
+use serde_json::json;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+// type-signature comes from the return type of lib.rs workers.
+type TestError = Box<dyn std::error::Error>;
+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 dyn Flashrom,
+ layout: LayoutSizes,
+
+ pub wp: WriteProtectState<'a>,
+ /// The path to a file containing the flash contents at test start.
+ original_flash_contents: PathBuf,
+ /// The path to a file containing flash-sized random data
+ random_data: PathBuf,
+ /// The path to a file containing layout data.
+ pub layout_file: PathBuf,
+}
+
+impl<'a> TestEnv<'a> {
+ pub fn create(
+ chip_type: FlashChip,
+ cmd: &'a dyn Flashrom,
+ print_layout: bool,
+ ) -> Result<Self, FlashromError> {
+ let rom_sz = cmd.get_size()?;
+ let out = TestEnv {
+ chip_type,
+ cmd,
+ layout: utils::get_layout_sizes(rom_sz)?,
+ wp: WriteProtectState::from_hardware(cmd, chip_type)?,
+ original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(),
+ random_data: "/tmp/random_content.bin".into(),
+ layout_file: create_layout_file(rom_sz, Path::new("/tmp/"), print_layout),
+ };
+
+ info!("Stashing golden image for verification/recovery on completion");
+ out.cmd.read_into_file(&out.original_flash_contents)?;
+ out.cmd.verify_from_file(&out.original_flash_contents)?;
+
+ info!("Generating random flash-sized data");
+ rand_util::gen_rand_testdata(&out.random_data, rom_sz as usize)
+ .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 name = test.get_name();
+ info!("Beginning test: {}", name);
+ let out = test.run(self);
+ info!("Completed test: {}; result {:?}", name, out);
+ 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) -> &Path {
+ &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 {
+ self.cmd
+ .verify_from_file(&self.original_flash_contents)
+ .is_ok()
+ }
+
+ /// Do whatever is necessary to make the current Flash contents the same as they
+ /// were at the start of testing.
+ pub fn ensure_golden(&mut self) -> Result<(), FlashromError> {
+ self.wp.set_hw(false)?.set_sw(false)?;
+ self.cmd.write_from_file(&self.original_flash_contents)?;
+ Ok(())
+ }
+
+ /// Attempt to erase the flash.
+ pub fn erase(&self) -> Result<(), FlashromError> {
+ self.cmd.erase()?;
+ Ok(())
+ }
+
+ /// Verify that the current Flash contents are the same as the file at the given
+ /// path.
+ ///
+ /// Returns Err if they are not the same.
+ pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> {
+ self.cmd.verify_from_file(contents_path)?;
+ Ok(())
+ }
+}
+
+impl<'a> Drop for TestEnv<'a> {
+ 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);
+ }
+ }
+ }
+}
+
+struct WriteProtect {
+ hw: bool,
+ sw: bool,
+}
+
+/// RAII handle for setting write protect in either hardware or software.
+///
+/// Given an instance, the state of either write protect can be modified by calling
+/// `set`. When it goes out of scope, the write protects will be returned
+/// to the state they had then it was created.
+pub struct WriteProtectState<'a> {
+ current: WriteProtect,
+ initial: WriteProtect,
+ cmd: &'a dyn Flashrom,
+ fc: FlashChip,
+}
+
+impl<'a> WriteProtectState<'a> {
+ /// Initialize a state from the current state of the hardware.
+ pub fn from_hardware(cmd: &'a dyn Flashrom, fc: FlashChip) -> Result<Self, FlashromError> {
+ let hw = Self::get_hw(cmd)?;
+ let sw = Self::get_sw(cmd)?;
+ info!("Initial write protect state: HW={} SW={}", hw, sw);
+
+ Ok(WriteProtectState {
+ current: WriteProtect { hw, sw },
+ initial: WriteProtect { hw, sw },
+ cmd,
+ fc,
+ })
+ }
+
+ /// Get the actual hardware write protect state.
+ fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> {
+ if cmd.can_control_hw_wp() {
+ super::utils::get_hardware_wp()
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// Get the actual software write protect state.
+ fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> {
+ let b = cmd.wp_status(true)?;
+ Ok(b)
+ }
+
+ /// 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.can_control_hw_wp()
+ }
+
+ /// Set the software write protect and check that the state is as expected.
+ pub fn set_sw(&mut self, enable: bool) -> Result<&mut Self, String> {
+ info!("set_sw request={}, current={}", enable, self.current.sw);
+ if self.current.sw != enable {
+ self.cmd
+ .wp_toggle(/* en= */ enable)
+ .map_err(|e| e.to_string())?;
+ }
+ if Self::get_sw(self.cmd).map_err(|e| e.to_string())? != enable {
+ Err(format!(
+ "Software write protect did not change state to {} when requested",
+ enable
+ ))
+ } else {
+ self.current.sw = enable;
+ Ok(self)
+ }
+ }
+
+ // Set software write protect with a custom range
+ pub fn set_range(&mut self, range: (i64, i64), enable: bool) -> Result<&mut Self, String> {
+ info!("set_range request={}, current={}", enable, self.current.sw);
+ self.cmd
+ .wp_range(range, enable)
+ .map_err(|e| e.to_string())?;
+ let actual_state = Self::get_sw(self.cmd).map_err(|e| e.to_string())?;
+ if actual_state != enable {
+ Err(format!(
+ "set_range request={}, real={}",
+ enable, actual_state
+ ))
+ } else {
+ self.current.sw = enable;
+ Ok(self)
+ }
+ }
+
+ /// Set the hardware write protect if supported and check that the state is as expected.
+ pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> {
+ info!("set_hw request={}, current={}", enable, self.current.hw);
+ if self.can_control_hw_wp() {
+ if self.current.hw != enable {
+ super::utils::toggle_hw_wp(/* dis= */ !enable)?;
+ }
+ // toggle_hw_wp does check this, but we might not have called toggle_hw_wp so check again.
+ if Self::get_hw(self.cmd)? != enable {
+ return Err(format!(
+ "Hardware write protect did not change state to {} when requested",
+ enable
+ ));
+ }
+ } else {
+ info!(
+ "Ignoring attempt to set hardware WP with {:?} programmer",
+ self.fc
+ );
+ }
+ self.current.hw = enable;
+ Ok(self)
+ }
+
+ /// Reset the hardware to what it was when this state was created, reporting errors.
+ ///
+ /// 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> {
+ let out = self.drop_internal();
+ // We just ran drop, don't do it again
+ std::mem::forget(self);
+ out
+ }
+
+ /// Sets both write protects to the state they had when this state was created.
+ fn drop_internal(&mut self) -> Result<(), String> {
+ // Toggle both protects back to their initial states.
+ // Software first because we can't change it once hardware is enabled.
+ if self.set_sw(self.initial.sw).is_err() {
+ self.set_hw(false)?;
+ self.set_sw(self.initial.sw)?;
+ }
+ self.set_hw(self.initial.hw)?;
+
+ Ok(())
+ }
+}
+
+impl<'a> Drop for WriteProtectState<'a> {
+ fn drop(&mut self) {
+ 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, Eq, Debug)]
+pub enum TestConclusion {
+ Pass,
+ Fail,
+ UnexpectedPass,
+ UnexpectedFail,
+}
+
+pub struct ReportMetaData {
+ pub chip_name: String,
+ pub os_release: String,
+ pub cros_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),
+ }
+}
+
+fn create_layout_file(rom_sz: i64, tmp_dir: &Path, print_layout: bool) -> PathBuf {
+ info!("Calculate ROM partition sizes & Create the layout file.");
+ let layout_sizes = utils::get_layout_sizes(rom_sz).expect("Could not partition rom");
+
+ let layout_file = tmp_dir.join("layout.file");
+ let mut f = File::create(&layout_file).expect("Could not create layout file");
+ let mut buf: Vec<u8> = vec![];
+ utils::construct_layout_file(&mut buf, &layout_sizes).expect("Could not construct layout file");
+
+ f.write_all(&buf).expect("Writing layout file failed");
+ if print_layout {
+ info!(
+ "Dumping layout file as requested:\n{}",
+ String::from_utf8_lossy(&buf)
+ );
+ }
+ layout_file
+}
+
+pub fn run_all_tests<T, TS>(
+ chip: FlashChip,
+ cmd: &dyn Flashrom,
+ ts: TS,
+ terminate_flag: Option<&AtomicBool>,
+ print_layout: bool,
+) -> Vec<(String, (TestConclusion, Option<TestError>))>
+where
+ T: TestCase + Copy,
+ TS: IntoIterator<Item = T>,
+{
+ let mut env =
+ TestEnv::create(chip, cmd, print_layout).expect("Failed to set up test environment");
+
+ let mut results = Vec::new();
+ for t in ts {
+ if terminate_flag
+ .map(|b| b.load(Ordering::Acquire))
+ .unwrap_or(false)
+ {
+ break;
+ }
+
+ let result = decode_test_result(env.run_test(t), t.expected_result());
+ results.push((t.get_name().into(), result));
+ }
+ results
+}
+
+#[derive(Debug, PartialEq, Eq, 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 => {
+ let color = if atty::is(atty::Stream::Stdout) {
+ types::COLOR
+ } else {
+ types::NOCOLOR
+ };
+ println!();
+ println!(" =============================");
+ println!(" ===== AVL qual RESULTS ====");
+ println!(" =============================");
+ println!();
+ println!(" %---------------------------%");
+ println!(" os release: {}", meta_data.os_release);
+ println!(" cros release: {}", meta_data.cros_release);
+ println!(" chip name: {}", meta_data.chip_name);
+ println!(" system info: \n{}", meta_data.system_info);
+ println!(" bios info: \n{}", meta_data.bios_info);
+ println!(" %---------------------------%");
+ println!();
+
+ for trun in truns.iter() {
+ let (name, (result, error)) = trun;
+ if *result != TestConclusion::Pass {
+ println!(
+ " {} {}",
+ style!(format!(" <+> {} test:", name), color.bold, color),
+ style_dbg!(result, color.red, color)
+ );
+ match error {
+ None => {}
+ Some(e) => info!(" - {} failure details:\n{}", name, e.to_string()),
+ };
+ } else {
+ println!(
+ " {} {}",
+ style!(format!(" <+> {} test:", name), color.bold, color),
+ style_dbg!(result, color.green, color)
+ );
+ }
+ }
+ 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));
+ }
+}
diff --git a/util/flashrom_tester/src/tests.rs b/util/flashrom_tester/src/tests.rs
new file mode 100644
index 00000000..721a789d
--- /dev/null
+++ b/util/flashrom_tester/src/tests.rs
@@ -0,0 +1,398 @@
+//
+// 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};
+use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
+use std::fs::{self, File};
+use std::io::BufRead;
+use std::sync::atomic::AtomicBool;
+
+const ELOG_FILE: &str = "/tmp/elog.file";
+const FW_MAIN_B_PATH: &str = "/tmp/FW_MAIN_B.bin";
+
+/// Iterate over tests, yielding only those tests with names matching filter_names.
+///
+/// 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.
+#[allow(clippy::or_fun_call)] // This is used for to_string here and we don't care.
+pub fn generic<'a, TN: Iterator<Item = &'a str>>(
+ cmd: &dyn Flashrom,
+ fc: FlashChip,
+ print_layout: bool,
+ output_format: OutputFormat,
+ test_names: Option<TN>,
+ terminate_flag: Option<&AtomicBool>,
+ crossystem: String,
+) -> Result<(), Box<dyn std::error::Error>> {
+ utils::ac_power_warning();
+
+ info!("Record crossystem information.\n{}", crossystem);
+
+ // Register tests to run:
+ let tests: &[&dyn TestCase] = &[
+ &("Get_device_name", get_device_name_test),
+ &("Coreboot_ELOG_sanity", elog_sanity_test),
+ &("Host_is_ChromeOS", host_is_chrome_test),
+ &("WP_Region_List", wp_region_list_test),
+ &("Erase_and_Write", erase_write_test),
+ &("Fail_to_verify", verify_fail_test),
+ &("HWWP_Locks_SWWP", hwwp_locks_swwp_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>> =
+ test_names.map(|names| names.map(|s| s.to_lowercase()).collect());
+ let tests = filter_tests(tests, &mut filter_names);
+
+ let chip_name = cmd
+ .name()
+ .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1))
+ .unwrap_or("<Unknown chip>".into());
+
+ // ------------------------.
+ // Run all the tests and collate the findings:
+ let results = tester::run_all_tests(fc, cmd, tests, terminate_flag, print_layout);
+
+ // Any leftover filtered names were specified to be run but don't exist
+ for leftover in filter_names.iter().flatten() {
+ warn!("No test matches filter name \"{}\"", leftover);
+ }
+
+ let os_release = sys_info::os_release().unwrap_or("<Unknown OS>".to_string());
+ let cros_release = cros_sysinfo::release_description()
+ .unwrap_or("<Unknown or not a ChromeOS release>".to_string());
+ let system_info = cros_sysinfo::system_info().unwrap_or("<Unknown System>".to_string());
+ let bios_info = cros_sysinfo::bios_info().unwrap_or("<Unknown BIOS>".to_string());
+
+ let meta_data = tester::ReportMetaData {
+ chip_name,
+ os_release,
+ cros_release,
+ system_info,
+ bios_info,
+ };
+ tester::collate_all_test_runs(&results, meta_data, output_format);
+ Ok(())
+}
+
+/// Query the programmer and chip name.
+/// Success means we got something back, which is good enough.
+fn get_device_name_test(env: &mut TestEnv) -> TestResult {
+ env.cmd.name()?;
+ Ok(())
+}
+
+/// List the write-protectable regions of flash.
+/// NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms.
+/// However, we will warn when it does fail.
+fn wp_region_list_test(env: &mut TestEnv) -> TestResult {
+ match env.cmd.wp_list() {
+ Ok(list_str) => info!("\n{}", list_str),
+ Err(e) => warn!("{}", e),
+ };
+ Ok(())
+}
+
+/// Verify that enabling hardware and software write protect prevents chip erase.
+fn erase_write_test(env: &mut TestEnv) -> TestResult {
+ if !env.is_golden() {
+ info!("Memory has been modified; reflashing to ensure erasure can be detected");
+ 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(())
+}
+
+/// Verify that enabling hardware write protect prevents disabling software write protect.
+fn hwwp_locks_swwp_test(env: &mut TestEnv) -> TestResult {
+ if !env.wp.can_control_hw_wp() {
+ return Err("Lock test requires ability to control hardware write protect".into());
+ }
+
+ env.wp.set_hw(false)?.set_sw(true)?;
+ // Toggling software WP off should work when hardware WP is off.
+ // Then enable software WP again for the next test.
+ env.wp.set_sw(false)?.set_sw(true)?;
+
+ // Toggling software WP off should not work when hardware WP is on.
+ env.wp.set_hw(true)?;
+ if env.wp.set_sw(false).is_ok() {
+ return Err("Software WP was reset despite hardware WP being enabled".into());
+ }
+ Ok(())
+}
+
+/// Check that the elog contains *something*, as an indication that Coreboot
+/// is actually able to write to the Flash. This only makes sense for chips
+/// running Coreboot, which we assume is just host.
+fn elog_sanity_test(env: &mut TestEnv) -> TestResult {
+ if env.chip_type() != FlashChip::HOST {
+ info!("Skipping ELOG sanity check for non-host chip");
+ return Ok(());
+ }
+ // flash should be back in the golden state
+ env.ensure_golden()?;
+
+ const ELOG_RW_REGION_NAME: &str = "RW_ELOG";
+ env.cmd
+ .read_region_into_file(ELOG_FILE.as_ref(), ELOG_RW_REGION_NAME)?;
+
+ // Just checking for the magic numer
+ // TODO: improve this test to read the events
+ if fs::metadata(ELOG_FILE)?.len() < 4 {
+ return Err("ELOG contained no data".into());
+ }
+ let data = fs::read(ELOG_FILE)?;
+ if u32::from_le_bytes(data[0..4].try_into()?) != 0x474f4c45 {
+ return Err("ELOG had bad magic number".into());
+ }
+ Ok(())
+}
+
+/// Check that we are running ChromiumOS.
+fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult {
+ let release_info = if let Ok(f) = File::open("/etc/os-release") {
+ let buf = std::io::BufReader::new(f);
+ 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())
+ }
+ }
+}
+
+/// Verify that software write protect for a range protects only the requested range.
+fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResult {
+ move |env: &mut TestEnv| {
+ // Need a clean image for verification
+ env.ensure_golden()?;
+
+ let (wp_section_name, start, len) = utils::layout_section(env.layout(), section);
+ // Disable hardware WP so we can modify the protected range.
+ env.wp.set_hw(false)?;
+ // Then enable software WP so the range is enforced and enable hardware
+ // WP so that flashrom does not disable software WP during the
+ // operation.
+ env.wp.set_range((start, len), true)?;
+ env.wp.set_hw(true)?;
+
+ // Check that we cannot write to the protected region.
+ if env
+ .cmd
+ .write_from_file_region(env.random_data_file(), wp_section_name, &env.layout_file)
+ .is_ok()
+ {
+ return Err(
+ "Section should be locked, should not have been overwritable with random data"
+ .into(),
+ );
+ }
+ if !env.is_golden() {
+ return Err("Section didn't lock, has been overwritten with random data!".into());
+ }
+
+ // Check that we can write to the non protected region.
+ let (non_wp_section_name, _, _) =
+ utils::layout_section(env.layout(), section.get_non_overlapping_section());
+ env.cmd.write_from_file_region(
+ env.random_data_file(),
+ non_wp_section_name,
+ &env.layout_file,
+ )?;
+
+ Ok(())
+ }
+}
+
+/// Check that flashrom 'verify' will fail if the provided data does not match the chip data.
+fn verify_fail_test(env: &mut TestEnv) -> TestResult {
+ env.ensure_golden()?;
+ // Verify that verify is Ok when the data matches. We verify only a region
+ // and not the whole chip because coprocessors or firmware may have written
+ // some data in other regions.
+ env.cmd
+ .read_region_into_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?;
+ env.cmd
+ .verify_region_from_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?;
+
+ // Verify that verify is false when the data does not match
+ match env.verify(env.random_data_file()) {
+ Ok(_) => Err("Verification says flash is full of random data".into()),
+ Err(_) => Ok(()),
+ }
+}
+
+/// 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);
+}
diff --git a/util/flashrom_tester/src/types.rs b/util/flashrom_tester/src/types.rs
new file mode 100644
index 00000000..9cefb27e
--- /dev/null
+++ b/util/flashrom_tester/src/types.rs
@@ -0,0 +1,72 @@
+//
+// 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 struct Color {
+ pub bold: &'static str,
+ pub reset: &'static str,
+ pub magenta: &'static str,
+ pub yellow: &'static str,
+ pub green: &'static str,
+ pub red: &'static str,
+}
+
+pub const COLOR: Color = Color {
+ bold: "\x1b[1m",
+ reset: "\x1b[0m",
+ magenta: "\x1b[35m",
+ yellow: "\x1b[33m",
+ green: "\x1b[92m",
+ red: "\x1b[31m",
+};
+
+pub const NOCOLOR: Color = Color {
+ bold: "",
+ reset: "",
+ magenta: "",
+ yellow: "",
+ green: "",
+ red: "",
+};
+
+macro_rules! style_dbg {
+ ($s: expr, $c: expr, $col: expr) => {
+ format!("{}{:?}{}", $c, $s, $col.reset)
+ };
+}
+macro_rules! style {
+ ($s: expr, $c: expr, $col: expr) => {
+ format!("{}{}{}", $c, $s, $col.reset)
+ };
+}
diff --git a/util/flashrom_tester/src/utils.rs b/util/flashrom_tester/src/utils.rs
new file mode 100644
index 00000000..4e8dd7cc
--- /dev/null
+++ b/util/flashrom_tester/src/utils.rs
@@ -0,0 +1,241 @@
+//
+// 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,
+}
+
+impl LayoutNames {
+ // Return a section that does not overlap
+ pub fn get_non_overlapping_section(&self) -> LayoutNames {
+ match self {
+ LayoutNames::TopQuad => LayoutNames::BottomQuad,
+ LayoutNames::TopHalf => LayoutNames::BottomHalf,
+ LayoutNames::BottomHalf => LayoutNames::TopHalf,
+ LayoutNames::BottomQuad => LayoutNames::TopQuad,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct LayoutSizes {
+ half_sz: i64,
+ 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 hardware write-protect is
+ // to {dis}connect the battery (and/or open the WP screw).
+ let s = if dis { "dis" } else { "" };
+ // Print a failure message, but not on the first try.
+ let mut fail_msg = None;
+ while dis == get_hardware_wp()? {
+ if let Some(msg) = fail_msg {
+ eprintln!("{msg}");
+ }
+ fail_msg = Some(format!("Hardware write protect is still {}!", !dis));
+ // The following message is read by the tast test. Do not modify.
+ info!("Prompt for hardware WP {}able", s);
+ eprintln!(" > {}connect the battery (and/or open the WP screw)", s);
+ pause();
+ }
+ Ok(())
+}
+
+pub fn ac_power_warning() {
+ info!("*****************************");
+ info!("AC power *must be* connected!");
+ info!("*****************************");
+ pause();
+}
+
+fn pause() {
+ // The following message is read by the tast test. Do not modify.
+ println!("Press enter to continue...");
+ // Rust stdout is always LineBuffered at time of writing.
+ // But this is not guaranteed, so flush anyway.
+ std::io::stdout().flush().unwrap();
+ // This reads one line, there is no guarantee the line came
+ // after the above prompt. But it is good enough.
+ if std::io::stdin().read_line(&mut String::new()).unwrap() == 0 {
+ panic!("stdin closed");
+ }
+}
+
+pub fn get_hardware_wp() -> std::result::Result<bool, String> {
+ let wp_s_val = collect_crosssystem(&["wpsw_cur"])?.parse::<u32>();
+ match wp_s_val {
+ Ok(v) => {
+ if v == 1 {
+ Ok(true)
+ } else if v == 0 {
+ Ok(false)
+ } else {
+ Err("Unknown write protect value".into())
+ }
+ }
+ Err(_) => Err("Cannot parse write protect value".into()),
+ }
+}
+
+pub fn collect_crosssystem(args: &[&str]) -> Result<String, String> {
+ let cmd = match Command::new("crossystem").args(args).output() {
+ Ok(x) => x,
+ Err(e) => return Err(format!("Failed to run crossystem: {}", e)),
+ };
+
+ if !cmd.status.success() {
+ return Err(translate_command_error(&cmd).to_string());
+ };
+
+ Ok(String::from_utf8_lossy(&cmd.stdout).into_owned())
+}
+
+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,
+ }
+ );
+ }
+}
diff --git a/util/z60_flashrom.rules b/util/flashrom_udev.rules
index 59de68be..9fd33308 100644
--- a/util/z60_flashrom.rules
+++ b/util/flashrom_udev.rules
@@ -62,6 +62,10 @@ ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="5001", MODE="664", GROUP="plugdev"
ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="5002", MODE="664", GROUP="plugdev"
ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="5003", MODE="664", GROUP="plugdev"
+# Kristech KT-LINK
+# https://kristech.pl/files/KT-LINK-UM-ENG.pdf
+ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bbe2", MODE="664", GROUP="plugdev"
+
# Olimex ARM-USB-OCD
# http://olimex.com/dev/arm-usb-ocd.html
ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="0003", MODE="664", GROUP="plugdev"
diff --git a/util/getrevision.sh b/util/getrevision.sh
index 168dd632..f7d6bf3b 100755
--- a/util/getrevision.sh
+++ b/util/getrevision.sh
@@ -129,7 +129,7 @@ timestamp() {
revision() {
local r
if git_is_file_tracked "$1" ; then
- r=$(git describe $(git_last_commit "$1"))
+ r=$(git describe --always $(git_last_commit "$1"))
if git_has_local_changes "$1" ; then
r="$r-dirty"
fi
diff --git a/util/ich_descriptors_tool/Makefile b/util/ich_descriptors_tool/Makefile
index c32e30be..aa1b696c 100644
--- a/util/ich_descriptors_tool/Makefile
+++ b/util/ich_descriptors_tool/Makefile
@@ -4,6 +4,8 @@
# This Makefile works standalone, but it is usually called from the main
# Makefile in the flashrom directory.
+include ../../Makefile.include
+
PROGRAM=ich_descriptors_tool
EXTRAINCDIRS = ../../ .
DEPPATH = .dep
@@ -16,31 +18,33 @@ WARNERROR ?= yes
SRC = $(wildcard *.c)
-CC ?= gcc
-
# If the user has specified custom CFLAGS, all CFLAGS settings below will be
# completely ignored by gnumake.
CFLAGS ?= -Os -Wall -Wshadow
+override CFLAGS += -I$(SHAREDSRCDIR)/include
+# Auto determine HOST_OS and TARGET_OS if they are not set as argument
HOST_OS ?= $(shell uname)
+TARGET_OS := $(call c_macro_test, ../../Makefile.d/os_test.h)
+
ifeq ($(findstring MINGW, $(HOST_OS)), MINGW)
# Explicitly set CC = gcc on MinGW, otherwise: "cc: command not found".
CC = gcc
-EXEC_SUFFIX := .exe
-# Some functions provided by Microsoft do not work as described in C99 specifications. This macro fixes that
-# for MinGW. See http://sourceforge.net/p/mingw-w64/wiki2/printf%20and%20scanf%20family/ */
-FLASHROM_CFLAGS += -D__USE_MINGW_ANSI_STDIO=1
endif
-override TARGET_OS := $(shell $(CC) $(CPPFLAGS) -E $(SHAREDSRCDIR)/os.h | grep -v '^\#' | grep '"' | \
- cut -f 2 -d'"')
-
ifeq ($(TARGET_OS), DOS)
EXEC_SUFFIX := .exe
# DJGPP has odd uint*_t definitions which cause lots of format string warnings.
CFLAGS += -Wno-format
endif
+ifeq ($(TARGET_OS), MinGW)
+EXEC_SUFFIX := .exe
+# Some functions provided by Microsoft do not work as described in C99 specifications. This macro fixes that
+# for MinGW. See http://sourceforge.net/p/mingw-w64/wiki2/printf%20and%20scanf%20family/
+CFLAGS += -D__USE_MINGW_ANSI_STDIO=1
+endif
+
ifeq ($(WARNERROR), yes)
CFLAGS += -Werror
endif
@@ -68,6 +72,7 @@ $(SHAREDOBJ): $(OBJATH)/%.o : $(SHAREDSRCDIR)/%.c
$(PROGRAM)$(EXEC_SUFFIX): $(OBJ) $(SHAREDOBJ)
$(CC) $(LDFLAGS) -o $(PROGRAM)$(EXEC_SUFFIX) $(OBJ) $(SHAREDOBJ)
+# We don't use EXEC_SUFFIX here because we want to clean everything.
clean:
rm -f $(PROGRAM) $(PROGRAM).exe
rm -rf $(DEPPATH) $(OBJATH)
diff --git a/util/ich_descriptors_tool/ich_descriptors_tool.c b/util/ich_descriptors_tool/ich_descriptors_tool.c
index 6f13749b..a5a59ad2 100644
--- a/util/ich_descriptors_tool/ich_descriptors_tool.c
+++ b/util/ich_descriptors_tool/ich_descriptors_tool.c
@@ -46,7 +46,6 @@ static const char *const region_names[] = {
static void dump_file(const char *prefix, const uint32_t *dump, unsigned int len,
const struct ich_desc_region *const reg, unsigned int i)
{
- int ret;
char *fn;
const char *reg_name;
uint32_t file_len;
@@ -85,8 +84,8 @@ static void dump_file(const char *prefix, const uint32_t *dump, unsigned int len
}
free(fn);
- ret = write(fh, &dump[base >> 2], file_len);
- if (ret != file_len) {
+ const ssize_t ret = write(fh, &dump[base >> 2], file_len);
+ if (ret < 0 || ((size_t) ret) != file_len) {
fprintf(stderr, "FAILED.\n");
exit(1);
}
@@ -127,6 +126,9 @@ static void usage(char *argv[], const char *error)
"\t- \"ich10\",\n"
"\t- \"silvermont\" for chipsets from Intel's Silvermont architecture (e.g. Bay Trail),\n"
"\t- \"apollo\" for Intel's Apollo Lake SoC.\n"
+"\t- \"gemini\" for Intel's Gemini Lake SoC.\n"
+"\t- \"jasper\" for Intel's Jasper Lake SoC.\n"
+"\t- \"meteor\" for Intel's Meteor Lake SoC.\n"
"\t- \"5\" or \"ibex\" for Intel's 5 series chipsets,\n"
"\t- \"6\" or \"cougar\" for Intel's 6 series chipsets,\n"
"\t- \"7\" or \"panther\" for Intel's 7 series chipsets.\n"
@@ -134,6 +136,9 @@ static void usage(char *argv[], const char *error)
"\t- \"9\" or \"wildcat\" for Intel's 9 series chipsets.\n"
"\t- \"100\" or \"sunrise\" for Intel's 100 series chipsets.\n"
"\t- \"300\" or \"cannon\" for Intel's 300 series chipsets.\n"
+"\t- \"400\" or \"comet\" for Intel's 400 series chipsets.\n"
+"\t- \"500\" or \"tiger\" for Intel's 500 series chipsets.\n"
+"\t- \"600\" or \"alder\" for Intel's 600 series chipsets.\n"
"If '-d' is specified some regions such as the BIOS image as seen by the CPU or\n"
"the GbE blob that is required to initialize the GbE are also dumped to files.\n",
argv[0], argv[0]);
@@ -225,8 +230,24 @@ int main(int argc, char *argv[])
else if ((strcmp(csn, "300") == 0) ||
(strcmp(csn, "cannon") == 0))
cs = CHIPSET_300_SERIES_CANNON_POINT;
+ else if ((strcmp(csn, "400") == 0) ||
+ (strcmp(csn, "comet") == 0))
+ cs = CHIPSET_400_SERIES_COMET_POINT;
+ else if ((strcmp(csn, "500") == 0) ||
+ (strcmp(csn, "tiger") == 0))
+ cs = CHIPSET_500_SERIES_TIGER_POINT;
+ else if (strcmp(csn, "600") == 0)
+ cs = CHIPSET_600_SERIES_ALDER_POINT;
else if (strcmp(csn, "apollo") == 0)
cs = CHIPSET_APOLLO_LAKE;
+ else if (strcmp(csn, "gemini") == 0)
+ cs = CHIPSET_GEMINI_LAKE;
+ else if (strcmp(csn, "jasper") == 0)
+ cs = CHIPSET_JASPER_LAKE;
+ else if (strcmp(csn, "elkhart") == 0)
+ cs = CHIPSET_ELKHART_LAKE;
+ else if (strcmp(csn, "meteor") == 0)
+ cs = CHIPSET_METEOR_LAKE;
}
ret = read_ich_descriptors_from_dump(buf, len, &cs, &desc);
@@ -248,7 +269,8 @@ int main(int argc, char *argv[])
prettyprint_ich_descriptors(cs, &desc);
pMAC = (uint8_t *) &buf[ICH_FREG_BASE(desc.region.FLREGs[3]) >> 2];
- if (len >= ICH_FREG_BASE(desc.region.FLREGs[3]) + 6 && pMAC[0] != 0xff)
+ /* The case len < 0 is handled above as error. At this point len is non-negative. */
+ if (((size_t) len) >= ICH_FREG_BASE(desc.region.FLREGs[3]) + 6 && pMAC[0] != 0xff)
printf("The MAC address might be at offset 0x%x: "
"%02x:%02x:%02x:%02x:%02x:%02x\n",
ICH_FREG_BASE(desc.region.FLREGs[3]),
diff --git a/util/ich_descriptors_tool/meson.build b/util/ich_descriptors_tool/meson.build
index b5bf09ec..80f4a521 100644
--- a/util/ich_descriptors_tool/meson.build
+++ b/util/ich_descriptors_tool/meson.build
@@ -4,10 +4,7 @@ executable(
'ich_descriptors_tool.c',
'../../ich_descriptors.c',
],
- dependencies : [
- deps,
- ],
- include_directories : include_directories('../..'),
+ include_directories : include_dir,
c_args : [
'-DICH_DESCRIPTORS_FROM_DUMP_ONLY',
],
diff --git a/util/lint/helper_functions.sh b/util/lint/helper_functions.sh
new file mode 100644
index 00000000..7abdab86
--- /dev/null
+++ b/util/lint/helper_functions.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env sh
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+# This file is sourced by the linters so that each one doesn't have to
+# specify these routines individually
+
+LC_ALL=C export LC_ALL
+
+if [ -z "$GIT" ]; then
+ GIT="$(command -v git)"
+else
+ # If git is specified, Do a basic check that it runs and seems like
+ # it's actually git
+ if ! "${GIT}" --version | grep -q git; then
+ echo "Error: ${GIT} does not seem to be valid."
+ exit 1;
+ fi
+fi
+
+if [ "$(${GIT} rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
+ IN_GIT_TREE=1
+else
+ IN_GIT_TREE=0
+fi
+
+if [ "${IN_GIT_TREE}" -eq 1 ] && [ -z "${GIT}" ]; then
+ echo "This test needs git to run. Please install it, then run this test again."
+ exit 1
+fi
+
+# Use git ls-files if the code is in a git repo, otherwise use find.
+if [ "${IN_GIT_TREE}" -eq 1 ]; then
+ FIND_FILES="${GIT} ls-files"
+else
+ FIND_FILES="find "
+ FINDOPTS="-type f"
+fi
+
+# Use git grep if the code is in a git repo, otherwise use grep.
+if [ "${IN_GIT_TREE}" -eq 1 ]; then
+ GREP_FILES="${GIT} grep"
+else
+ GREP_FILES="grep -r"
+fi
diff --git a/util/lint/lint-extended-020-signed-off-by b/util/lint/lint-extended-020-signed-off-by
new file mode 100755
index 00000000..ef62a45a
--- /dev/null
+++ b/util/lint/lint-extended-020-signed-off-by
@@ -0,0 +1,23 @@
+#!/usr/bin/env sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# DESCR: Check for a signed-off-by line on the latest commit
+
+
+LINTDIR="$(
+ cd -- "$(dirname "$0")" > /dev/null 2>&1 || return
+ pwd -P
+)"
+
+# shellcheck source=helper_functions.sh
+. "${LINTDIR}/helper_functions.sh"
+
+if [ "${IN_GIT_TREE}" -eq 0 ]; then
+ exit 0
+fi
+
+# This test is mainly for the jenkins server
+if ! ${GIT} log -n 1 | grep -q '[[:space:]]\+Signed-off-by: '; then
+ echo "No Signed-off-by line in commit message"
+ exit 1
+fi
diff --git a/util/manibuilder/Dockerfile.alpine b/util/manibuilder/Dockerfile.alpine
new file mode 100644
index 00000000..674b5451
--- /dev/null
+++ b/util/manibuilder/Dockerfile.alpine
@@ -0,0 +1,24 @@
+FROM manibase
+
+ARG PROTO=https
+RUN \
+ adduser -D mani mani && \
+ sed -i "s/https/${PROTO}/" /etc/apk/repositories && \
+ apk update && \
+ apk add ca-certificates build-base linux-headers git ccache \
+ pciutils-dev libusb-compat-dev libusb-dev
+
+# fix weird permissions in armhf-v3.11
+RUN [ -d /usr/share/git-core/templates ] && \
+ chmod -R a+r /usr/share/git-core/templates
+
+ENV GIT_SSL_NO_VERIFY=1
+USER mani
+RUN \
+ cd && \
+ mkdir .ccache && chown mani:mani .ccache && \
+ git clone https://review.coreboot.org/flashrom.git
+
+ENV DEVSHELL /bin/sh
+COPY mani-wrapper.sh /home/mani/
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Dockerfile.anita b/util/manibuilder/Dockerfile.anita
new file mode 100644
index 00000000..52befce0
--- /dev/null
+++ b/util/manibuilder/Dockerfile.anita
@@ -0,0 +1,65 @@
+FROM debian:bullseye
+
+ARG PKG_PATH=http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/7.1/All
+ARG INST_IMG=http://ftp.de.netbsd.org/pub/NetBSD/NetBSD-7.1/amd64/
+
+RUN \
+ useradd -p locked -m mani && \
+ apt-get -qq update && \
+ apt-get -qq upgrade && \
+ apt-get -qqy install git python-is-python3 python3-pexpect \
+ python3-distutils genisoimage qemu-system && \
+ apt-get clean
+
+RUN git clone https://github.com/gson1703/anita.git
+RUN cd anita && python setup.py install
+
+ARG DISK_SIZE=1G
+ARG INSTALL_MEM=128M
+USER mani
+RUN cd && mkdir .ccache && chown mani:mani .ccache && \
+ anita --sets kern-GENERIC,modules,base,etc,comp \
+ --disk-size ${DISK_SIZE} --memory-size=${INSTALL_MEM} \
+ install ${INST_IMG} && \
+ rm -rf work-*/download
+
+ARG EXTRA_PKG=""
+ARG RUNTIME_MEM=128M
+RUN cd && anita --persist --memory-size=${RUNTIME_MEM} --run \
+"echo 'dhcpcd' >init && \
+ echo 'export PKG_PATH=${PKG_PATH}' >>init && \
+ . ./init && \
+ pkg_add gmake git-base ccache pciutils libusb1 libusb-compat libftdi \
+ ${EXTRA_PKG} && \
+ git config --global --add http.sslVerify false && \
+ git clone https://review.coreboot.org/flashrom.git" \
+ boot ${INST_IMG}
+
+RUN cd && dd if=/dev/zero bs=1M count=64 of=cache.img && \
+ anita --vmm-args '-hdb cache.img' --persist \
+ --memory-size=${RUNTIME_MEM} --run \
+"if [ \$(uname -m) = i386 -o \$(uname -m) = amd64 ]; then \
+ bdev=wd1d; \
+ else \
+ bdev=wd1c; \
+ fi; \
+ newfs -I \${bdev} && \
+ mkdir .ccache && \
+ mount /dev/\${bdev} .ccache && \
+ ccache -M 60M && \
+ umount .ccache && \
+ echo 'manitest() {' >>init && \
+ echo ' fsck -y -t ffs /dev/'\${bdev} >>init && \
+ echo ' mount /dev/'\${bdev}' ~/.ccache' >>init && \
+ echo ' (cd ~/flashrom && eval \" \$*\")' >>init && \
+ echo ' ret=\$?' >>init && \
+ echo ' umount ~/.ccache' >>init && \
+ echo ' return \$ret' >>init && \
+ echo '}' >>init" \
+ boot ${INST_IMG} && \
+ gzip cache.img
+
+COPY anita-wrapper.sh /home/mani/mani-wrapper.sh
+ENV INST_IMG ${INST_IMG}
+ENV MEM_SIZE ${RUNTIME_MEM}
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Dockerfile.centos b/util/manibuilder/Dockerfile.centos
new file mode 100644
index 00000000..aa28fc48
--- /dev/null
+++ b/util/manibuilder/Dockerfile.centos
@@ -0,0 +1,18 @@
+FROM manibase
+
+RUN \
+ useradd -p locked -m mani && \
+ yum install -q -y ca-certificates git gcc systemd-devel \
+ pciutils-devel libusb-devel libusbx-devel && \
+ yum clean -q -y all
+
+ENV GIT_SSL_NO_VERIFY=1
+USER mani
+RUN \
+ cd && \
+ mkdir .ccache && chown mani:mani .ccache && \
+ git clone https://review.coreboot.org/flashrom.git
+
+ENV DEVSHELL /bin/bash
+COPY mani-wrapper.sh /home/mani/
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Dockerfile.debian-debootstrap b/util/manibuilder/Dockerfile.debian-debootstrap
new file mode 100644
index 00000000..c9af0bf3
--- /dev/null
+++ b/util/manibuilder/Dockerfile.debian-debootstrap
@@ -0,0 +1,21 @@
+FROM manibase
+
+RUN \
+ useradd -p locked -m mani && \
+ apt-get -qq update && \
+ apt-get -qq upgrade && \
+ apt-get -qqy install gcc make git doxygen ccache pkg-config \
+ libpci-dev libusb-dev libftdi-dev libusb-1.0-0-dev && \
+ { apt-get -qqy install libjaylink-dev || true; } && \
+ apt-get clean
+
+ENV GIT_SSL_NO_VERIFY=1
+USER mani
+RUN \
+ cd && \
+ mkdir .ccache && chown mani:mani .ccache && \
+ git clone https://review.coreboot.org/flashrom.git
+
+ENV DEVSHELL /bin/bash
+COPY mani-wrapper.sh /home/mani/
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Dockerfile.djgpp b/util/manibuilder/Dockerfile.djgpp
new file mode 100644
index 00000000..7bd5c7bd
--- /dev/null
+++ b/util/manibuilder/Dockerfile.djgpp
@@ -0,0 +1,30 @@
+FROM anibali/djgpp:6.1.0
+
+USER root
+RUN \
+ userdel appuser && \
+ useradd -p locked -m mani && \
+ zypper -q install -y tar make git ccache
+
+ENV GIT_SSL_NO_VERIFY=1
+USER mani
+RUN cd && \
+ mkdir .ccache && chown mani:users .ccache && \
+ git clone https://review.coreboot.org/flashrom.git && \
+ git clone https://git.kernel.org/pub/scm/utils/pciutils/pciutils.git && \
+ cd pciutils && \
+ git checkout v3.5.6 && \
+ curl --insecure https://flashrom.org/images/6/6a/Pciutils-3.5.6.patch.gz | zcat | git apply && \
+ make ZLIB=no DNS=no HOST=i386-djgpp-djgpp \
+ CROSS_COMPILE=i586-pc-msdosdjgpp- \
+ PREFIX=/ DESTDIR=$PWD/../ \
+ STRIP="--strip-program=i586-pc-msdosdjgpp-strip -s" \
+ install install-lib && \
+ cd ../ && \
+ curl --insecure https://flashrom.org/images/3/3d/Libgetopt.tar.gz | zcat | tar x && \
+ cd libgetopt && \
+ make && cp libgetopt.a ../lib/ && cp getopt.h ../include/
+
+ENV DEVSHELL /bin/bash
+COPY mani-wrapper.sh /home/mani/
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Dockerfile.fedora b/util/manibuilder/Dockerfile.fedora
new file mode 100644
index 00000000..79a939a1
--- /dev/null
+++ b/util/manibuilder/Dockerfile.fedora
@@ -0,0 +1,19 @@
+FROM manibase
+
+RUN \
+ useradd -p locked -m mani && \
+ dnf install -q -y ca-certificates git gcc ccache make systemd-devel \
+ pciutils-devel libusb-devel libusbx-devel libftdi-devel \
+ libjaylink-devel && \
+ dnf clean -q -y all
+
+ENV GIT_SSL_NO_VERIFY=1
+USER mani
+RUN \
+ cd && \
+ mkdir .ccache && chown mani:mani .ccache && \
+ git clone https://review.coreboot.org/flashrom.git
+
+ENV DEVSHELL /bin/bash
+COPY mani-wrapper.sh /home/mani/
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Dockerfile.qemu-user-static b/util/manibuilder/Dockerfile.qemu-user-static
new file mode 100644
index 00000000..b6de7eb2
--- /dev/null
+++ b/util/manibuilder/Dockerfile.qemu-user-static
@@ -0,0 +1,3 @@
+FROM multiarch/qemu-user-static:register
+
+RUN sed -i -e's/ mipsn32 mipsn32el / /' /qemu-binfmt-conf.sh
diff --git a/util/manibuilder/Dockerfile.ubuntu-debootstrap b/util/manibuilder/Dockerfile.ubuntu-debootstrap
new file mode 100644
index 00000000..62cc4615
--- /dev/null
+++ b/util/manibuilder/Dockerfile.ubuntu-debootstrap
@@ -0,0 +1,34 @@
+FROM manibase
+
+RUN \
+ useradd -p locked -m mani && \
+ if grep -q main /etc/apt/sources.list; then \
+ if ! grep -q universe /etc/apt/sources.list; then \
+ sed -i -e 's/ main$/ main universe/' \
+ /etc/apt/sources.list || exit 1; \
+ fi; \
+ else \
+ url="http://ports.ubuntu.com/" && \
+ cn="$(sed -ne's/DISTRIB_CODENAME=//p' /etc/lsb-release)" && \
+ for t in "" "-updates" "-security"; do \
+ echo "deb ${url} ${cn}${t} main universe" \
+ >>/etc/apt/sources.list || exit 1; \
+ done; \
+ fi && \
+ apt-get -qq update && \
+ apt-get -qq upgrade && \
+ apt-get -qqy install gcc make git doxygen ccache pkg-config \
+ libpci-dev libusb-dev libftdi-dev libusb-1.0-0-dev && \
+ { apt-get -qqy install libjaylink-dev || true; } && \
+ apt-get clean
+
+ENV GIT_SSL_NO_VERIFY=1
+USER mani
+RUN \
+ cd && \
+ mkdir .ccache && chown mani:mani .ccache && \
+ git clone https://review.coreboot.org/flashrom.git
+
+ENV DEVSHELL /bin/bash
+COPY mani-wrapper.sh /home/mani/
+ENTRYPOINT ["/bin/sh", "/home/mani/mani-wrapper.sh"]
diff --git a/util/manibuilder/Makefile b/util/manibuilder/Makefile
new file mode 100644
index 00000000..98ed30c9
--- /dev/null
+++ b/util/manibuilder/Makefile
@@ -0,0 +1,93 @@
+QUIET_TEST := @
+
+include Makefile.targets
+
+CC := ccache cc
+MAKECMD := make
+MAKEARGS := CONFIG_EVERYTHING=yes
+
+spc :=
+spc := $(spc) $(spc)
+
+stem = $(word 1,$(subst :,$(spc),$(subst \:,$(spc),$(1))))
+ident = $(subst :,_,$(subst \:,_,$(1)))
+
+include Makefile.anita
+
+define build_template
+Dockerfile.$(call ident,$(1)): Dockerfile.$(call stem,$(1)) mani-wrapper.sh
+ $(QUIET_SETUP)sed -e 's|^FROM manibase|FROM $(2)/$(1)|' $$< >$$@
+
+.INTERMEDIATE: Dockerfile.$(call ident,$(1))
+
+$(1)-build: Dockerfile.$(call ident,$(1))
+ $(QUIET_SETUP)docker build . -f $$< -t mani/$(1) $$(DOCKER_BUILD_ARGS)
+endef
+
+$(foreach tag,$(MULTIARCH_TAGS), \
+ $(eval $(call build_template,$(tag),multiarch)))
+
+$(addsuffix -build,$(filter alpine%v3.7 alpine%v3.8,$(MULTIARCH_TAGS))): \
+ DOCKER_BUILD_ARGS = --build-arg PROTO=http
+
+djgpp\:6.1.0-build: %-build: Dockerfile.djgpp mani-wrapper.sh
+ $(QUIET_SETUP)docker build . -f $< -t mani/$*
+
+$(addsuffix -check-build,$(ALL_TAGS)): %-check-build:
+ $(QUIET_SETUP)\
+ [ $$(docker image ls -q mani/$*) ] \
+ || $(MAKE) $*-build $(if $(QUIET_SETUP),>/dev/null 2>/dev/null)
+
+$(filter centos%,$(MULTIARCH_TAGS)) anita\:7.1-sparc: CC=cc
+djgpp\:6.1.0: CC=ccache i586-pc-msdosdjgpp-gcc
+djgpp\:6.1.0: STRIP=i586-pc-msdosdjgpp-strip
+djgpp\:6.1.0: LIBS_BASE=../
+djgpp\:6.1.0: MAKEARGS+=strip CONFIG_JLINK_SPI=no
+$(ANITA_TAGS): MAKECMD=gmake
+$(ANITA_TAGS): MAKEARGS+=CONFIG_JLINK_SPI=no WARNERROR=no
+$(filter alpine% centos%,$(MULTIARCH_TAGS)): MAKEARGS+=CONFIG_JLINK_SPI=no
+$(filter %-xenial %-stretch,$(MULTIARCH_TAGS)): MAKEARGS+=CONFIG_JLINK_SPI=no
+$(filter centos%,$(MULTIARCH_TAGS)): MAKEARGS+=WARNERROR=no
+$(ALL_TAGS): export QUIET_SETUP=$(QUIET_TEST)
+$(ALL_TAGS): %: %-check-build
+ $(QUIET_TEST)docker rm -f mani_$(call ident,$*) >/dev/null 2>&1 || true
+ $(QUIET_TEST)\
+ docker run \
+ --env IDENT=$(call ident,$*) \
+ --volume manicache:/home/mani/.ccache \
+ --name mani_$(call ident,$*) mani/$* \
+ "git fetch origin $${TEST_REVISION:-master} && \
+ git checkout FETCH_HEAD && \
+ $(MAKECMD) clean && $(MAKECMD) -j$${CPUS:-1} CC='$(CC)' \
+ $(if $(STRIP),STRIP='$(STRIP)') \
+ $(if $(LIBS_BASE),LIBS_BASE='$(LIBS_BASE)') \
+ $(MAKEARGS)" \
+ $(if $(QUIET_TEST),>/dev/null 2>&1) || echo $*: $$?
+
+$(addsuffix -shell,$(ALL_TAGS)): %-shell: %-check-build
+ $(QUIET_SETUP)\
+ if [ $$(docker ps -a -q -f name=mani_$(call ident,$*)) ]; then \
+ docker commit mani_$(call ident,$*) mani_run/$* && \
+ docker run --rm -it \
+ --env IDENT=$(call ident,$*) \
+ --volume manicache:/home/mani/.ccache \
+ --entrypoint /bin/sh mani_run/$* \
+ /home/mani/mani-wrapper.sh \
+ $(patsubst %,"%",$(SHELL_ARG)); \
+ docker image rm mani_run/$*; \
+ else \
+ docker run --rm -it \
+ --env IDENT=$(call ident,$*) \
+ --volume manicache:/home/mani/.ccache \
+ mani/$* $(patsubst %,"%",$(SHELL_ARG)); \
+ fi
+
+.PHONY: $(foreach s,-build -check-build -shell, $(addsuffix $(s),$(ALL_TAGS)))
+
+register:
+ docker build . \
+ -f Dockerfile.qemu-user-static \
+ -t mani/qemu-user-static:register
+ docker run --rm --privileged mani/qemu-user-static:register --reset
+
+.PHONY: register
diff --git a/util/manibuilder/Makefile.anita b/util/manibuilder/Makefile.anita
new file mode 100644
index 00000000..ba8c82d6
--- /dev/null
+++ b/util/manibuilder/Makefile.anita
@@ -0,0 +1,52 @@
+PKGSRC_MIRROR = http://cdn.netbsd.org/
+NETBSD_MIRROR = http://ftp.de.netbsd.org/
+
+anita\:9.1-sparc64-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/sparc64/9.1/All
+anita\:9.1-sparc64-build: NETBSD_IMAGE=pub/NetBSD/iso/9.1/NetBSD-9.1-sparc64.iso
+anita\:9.1-sparc64-build: QEMU_DISK_SIZE=2G
+anita\:9.1-sparc64-build: QEMU_INSTALL_MEM=192M
+anita\:9.1-sparc64-build: QEMU_RUNTIME_MEM=512M
+
+anita\:9.1-amd64-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/amd64/9.1/All
+anita\:9.1-amd64-build: NETBSD_IMAGE=pub/NetBSD/NetBSD-9.1/amd64/
+anita\:9.1-amd64-build: QEMU_DISK_SIZE=2G
+anita\:9.1-amd64-build: QEMU_INSTALL_MEM=192M
+anita\:9.1-amd64-build: QEMU_RUNTIME_MEM=512M
+
+anita\:9.1-i386-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/i386/9.1/All
+anita\:9.1-i386-build: NETBSD_IMAGE=pub/NetBSD/NetBSD-9.1/i386/
+anita\:9.1-i386-build: QEMU_DISK_SIZE=2G
+anita\:9.1-i386-build: QEMU_INSTALL_MEM=128M
+anita\:9.1-i386-build: QEMU_RUNTIME_MEM=256M
+
+anita\:8.2-amd64-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/amd64/8.2/All
+anita\:8.2-amd64-build: NETBSD_IMAGE=pub/NetBSD/NetBSD-8.2/amd64/
+anita\:8.2-amd64-build: QEMU_DISK_SIZE=2G
+anita\:8.2-amd64-build: QEMU_INSTALL_MEM=192M
+anita\:8.2-amd64-build: QEMU_RUNTIME_MEM=512M
+
+anita\:8.2-i386-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/i386/8.2/All
+anita\:8.2-i386-build: NETBSD_IMAGE=pub/NetBSD/NetBSD-8.2/i386/
+anita\:8.2-i386-build: QEMU_DISK_SIZE=2G
+anita\:8.2-i386-build: QEMU_INSTALL_MEM=128M
+anita\:8.2-i386-build: QEMU_RUNTIME_MEM=256M
+
+anita\:7.1-amd64-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/amd64/7.1/All
+anita\:7.1-amd64-build: NETBSD_IMAGE=pub/NetBSD/NetBSD-7.1/amd64/
+anita\:7.1-amd64-build: QEMU_DISK_SIZE=1G
+anita\:7.1-amd64-build: QEMU_INSTALL_MEM=192M
+anita\:7.1-amd64-build: QEMU_RUNTIME_MEM=512M
+
+anita\:7.1-i386-build: PKGSRC_PATH=pub/pkgsrc/packages/NetBSD/i386/7.1/All
+anita\:7.1-i386-build: NETBSD_IMAGE=pub/NetBSD/NetBSD-7.1/i386/
+anita\:7.1-i386-build: QEMU_DISK_SIZE=1G
+anita\:7.1-i386-build: QEMU_INSTALL_MEM=128M
+anita\:7.1-i386-build: QEMU_RUNTIME_MEM=256M
+
+$(addsuffix -build,$(ANITA_TAGS)): %-build: Dockerfile.anita anita-wrapper.sh
+ $(QUIET_SETUP)docker build . -f $< -t mani/$* \
+ --build-arg PKG_PATH=$(PKGSRC_MIRROR)$(PKGSRC_PATH) \
+ --build-arg INST_IMG=$(NETBSD_MIRROR)$(NETBSD_IMAGE) \
+ --build-arg DISK_SIZE=$(QEMU_DISK_SIZE) \
+ --build-arg INSTALL_MEM=$(QEMU_INSTALL_MEM) \
+ --build-arg RUNTIME_MEM=$(QEMU_RUNTIME_MEM)
diff --git a/util/manibuilder/Makefile.targets b/util/manibuilder/Makefile.targets
new file mode 100644
index 00000000..dff38797
--- /dev/null
+++ b/util/manibuilder/Makefile.targets
@@ -0,0 +1,223 @@
+ANITA_TAGS := \
+ anita\:9.1-amd64 anita\:9.1-i386 anita\:9.1-sparc64 \
+ anita\:8.2-amd64 anita\:8.2-i386 \
+ anita\:7.1-amd64 anita\:7.1-i386 \
+
+MULTIARCH_TAGS := \
+ centos\:7.6-armhfp-clean centos\:7.6-amd64-clean \
+ centos\:7.3-aarch64-clean centos\:7.3-amd64-clean \
+ centos\:7.2-amd64-clean \
+ $(foreach a,x86_64 aarch64, \
+ $(foreach v,34 33 32 31 30 29 25 24, \
+ fedora\:$(v)-$(a))) \
+ $(foreach a,ppc64le, \
+ $(foreach v,34 33 29 25 24, \
+ fedora\:$(v)-$(a))) \
+ $(foreach a,s390x, \
+ $(foreach v,34 33 32 31 30 29, \
+ fedora\:$(v)-$(a))) \
+ fedora\:28-armhfp \
+ $(foreach a,ppc64el armhf mipsel amd64 i386, \
+ $(foreach v,bullseye buster stretch, \
+ debian-debootstrap\:$(a)-$(v))) \
+ $(foreach a,arm64 mips, \
+ $(foreach v,buster stretch, \
+ debian-debootstrap\:$(a)-$(v))) \
+ $(foreach a,ppc64el arm64 armhf amd64, \
+ $(foreach v,jammy focal bionic xenial, \
+ ubuntu-debootstrap\:$(a)-$(v))) \
+ $(foreach a,i386, \
+ $(foreach v,bionic xenial, \
+ ubuntu-debootstrap\:$(a)-$(v))) \
+ ubuntu-debootstrap\:powerpc-xenial \
+ $(foreach a,aarch64 armhf amd64 i386, \
+ $(foreach v,v3.14 v3.13 v3.12 v3.11 v3.10 v3.9 v3.8 v3.7 v3.6, \
+ alpine\:$(a)-$(v))) \
+
+OTHER_TAGS := djgpp\:6.1.0
+
+ALL_TAGS := $(ANITA_TAGS) $(MULTIARCH_TAGS) $(OTHER_TAGS)
+
+BROKEN_TAGS := anita\:7.1-amd64 anita\:7.1-i386 \
+ centos\:7.6-armhfp-clean \
+ fedora\:30-s390x fedora\:28-armhfp \
+
+WORKING_TAGS := $(filter-out $(BROKEN_TAGS),$(ALL_TAGS))
+
+arch_filter = $(sort \
+ $(foreach arch,$(1), \
+ $(filter-out $(subst $(arch),,$(MULTIARCH_TAGS)),$(MULTIARCH_TAGS))))
+
+machine_map = \
+ $(if $(filter i386 i686 x86,$(1)),i386 x86, \
+ $(if $(filter x86_64,$(1)),amd64 i386 x86, \
+ $(if $(filter armv7l armv6l,$(1)),armhf, \
+ $(if $(filter aarch64,$(1)),aarch64 arm64, \
+ $(if $(filter ppc64le,$(1)),ppc64le ppc64el, \
+ $(if $(filter ppc,$(1)),powerpc, \
+ $(if $(filter mips,$(1)),mips mipsel, \
+ $(1))))))))
+
+NATIVE_TAGS := $(call arch_filter,$(call machine_map,$(shell uname -m)))
+
+# rather arbitrary selection of images that seem to work (focus on amd64)
+DEFAULT_TAGS := \
+ anita\:9.1-sparc64 \
+ anita\:9.1-amd64 \
+ anita\:9.1-i386 \
+ anita\:8.2-amd64 \
+ djgpp\:6.1.0 \
+ fedora\:30-aarch64 \
+ fedora\:25-x86_64 \
+ fedora\:25-ppc64le \
+ fedora\:25-aarch64 \
+ fedora\:24-x86_64 \
+ centos\:7.3-aarch64-clean \
+ centos\:7.3-amd64-clean \
+ centos\:7.2-amd64-clean \
+ debian-debootstrap\:ppc64el-stretch \
+ debian-debootstrap\:armhf-stretch \
+ debian-debootstrap\:mips-stretch \
+ debian-debootstrap\:mipsel-stretch \
+ debian-debootstrap\:amd64-stretch \
+ debian-debootstrap\:i386-stretch \
+ ubuntu-debootstrap\:arm64-xenial \
+ ubuntu-debootstrap\:amd64-xenial \
+ ubuntu-debootstrap\:powerpc-xenial \
+ ubuntu-debootstrap\:amd64-bionic \
+ alpine\:aarch64-v3.9 \
+ alpine\:amd64-v3.8 \
+ alpine\:amd64-v3.7 \
+
+# also run all native tests by default
+DEFAULT_TAGS += $(filter-out $(DEFAULT_TAGS),$(NATIVE_TAGS))
+
+# original 1.0.x tags
+10X_TAGS := \
+ anita\:7.1-amd64 \
+ anita\:7.1-i386 \
+ djgpp\:6.1.0 \
+ alpine\:amd64-v3.6 \
+ alpine\:amd64-v3.7 \
+ alpine\:i386-v3.6 \
+ alpine\:i386-v3.7 \
+ centos\:7.2-amd64-clean \
+ centos\:7.3-aarch64-clean \
+ centos\:7.3-amd64-clean \
+ debian-debootstrap\:amd64-sid \
+ debian-debootstrap\:amd64-stretch \
+ debian-debootstrap\:armhf-stretch \
+ debian-debootstrap\:i386-sid \
+ debian-debootstrap\:i386-stretch \
+ debian-debootstrap\:mips-stretch \
+ debian-debootstrap\:mipsel-stretch \
+ debian-debootstrap\:powerpc-sid \
+ debian-debootstrap\:ppc64el-stretch \
+ fedora\:24-x86_64 \
+ fedora\:25-aarch64 \
+ fedora\:25-ppc64le \
+ fedora\:25-x86_64 \
+ ubuntu-debootstrap\:amd64-xenial \
+ ubuntu-debootstrap\:amd64-zesty \
+ ubuntu-debootstrap\:arm64-xenial \
+ ubuntu-debootstrap\:i386-xenial \
+ ubuntu-debootstrap\:i386-zesty \
+
+# additional tags added after initial release
+10X_TAGS += \
+ alpine\:aarch64-v3.8 \
+ alpine\:armhf-v3.8 \
+ alpine\:amd64-v3.8 \
+ alpine\:i386-v3.8 \
+ debian-debootstrap\:amd64-buster \
+ debian-debootstrap\:arm64-buster \
+ debian-debootstrap\:i386-buster \
+ ubuntu-debootstrap\:amd64-bionic \
+ ubuntu-debootstrap\:arm64-bionic \
+ ubuntu-debootstrap\:i386-bionic \
+
+# can only run what is still maintained
+10X_TAGS := $(filter $(10X_TAGS),$(ALL_TAGS))
+
+# original 1.1.x tags
+11X_TAGS := \
+ anita\:7.1-amd64 \
+ djgpp\:6.1.0 \
+ fedora\:30-x86_64 \
+ fedora\:30-aarch64 \
+ fedora\:29-x86_64 \
+ fedora\:25-x86_64 \
+ fedora\:25-ppc64le \
+ fedora\:25-aarch64 \
+ fedora\:24-x86_64 \
+ centos\:7.6-amd64-clean \
+ centos\:7.3-aarch64-clean \
+ centos\:7.3-amd64-clean \
+ centos\:7.2-amd64-clean \
+ debian-debootstrap\:ppc64el-stretch \
+ debian-debootstrap\:armhf-stretch \
+ debian-debootstrap\:mips-stretch \
+ debian-debootstrap\:mipsel-stretch \
+ debian-debootstrap\:amd64-stretch \
+ debian-debootstrap\:i386-stretch \
+ debian-debootstrap\:amd64-sid \
+ ubuntu-debootstrap\:arm64-xenial \
+ ubuntu-debootstrap\:amd64-xenial \
+ ubuntu-debootstrap\:powerpc-xenial \
+ ubuntu-debootstrap\:amd64-bionic \
+ alpine\:aarch64-v3.9 \
+ alpine\:amd64-v3.9 \
+ alpine\:amd64-v3.8 \
+ alpine\:amd64-v3.7 \
+ alpine\:amd64-v3.6 \
+ alpine\:armhf-v3.8 \
+ alpine\:i386-v3.9 \
+ alpine\:i386-v3.8 \
+ alpine\:i386-v3.7 \
+ alpine\:i386-v3.6 \
+ debian-debootstrap\:amd64-buster \
+ debian-debootstrap\:i386-buster \
+ debian-debootstrap\:i386-sid \
+ ubuntu-debootstrap\:armhf-xenial \
+ ubuntu-debootstrap\:i386-bionic \
+ ubuntu-debootstrap\:i386-xenial \
+ ubuntu-debootstrap\:ppc64el-xenial \
+
+# can only run what is still maintained
+11X_TAGS := $(filter $(11X_TAGS),$(ALL_TAGS))
+
+default: $(DEFAULT_TAGS)
+
+native: $(NATIVE_TAGS)
+
+working: $(WORKING_TAGS)
+
+all: $(ALL_TAGS)
+
+1.0.x: export TEST_REVISION=refs/heads/1.0.x
+1.0.x: $(10X_TAGS)
+
+1.1.x: export TEST_REVISION=refs/heads/1.1.x
+1.1.x: $(11X_TAGS)
+
+show-default:
+ @printf "%s\n" $(DEFAULT_TAGS)
+
+show-native:
+ @printf "%s\n" $(NATIVE_TAGS)
+
+show-working:
+ @printf "%s\n" $(WORKING_TAGS)
+
+show-all:
+ @printf "%s\n" $(ALL_TAGS)
+
+show-1.0.x:
+ @printf "%s\n" $(10X_TAGS)
+
+show-1.1.x:
+ @printf "%s\n" $(11X_TAGS)
+
+.PHONY: default native all 1.0.x 1.1.x
+.PHONY: show-default show-native show-all show-1.0.x show-1.1.x
+.PHONY: $(ALL_TAGS)
diff --git a/util/manibuilder/README.md b/util/manibuilder/README.md
new file mode 100644
index 00000000..b00d6181
--- /dev/null
+++ b/util/manibuilder/README.md
@@ -0,0 +1,72 @@
+Manibuilder
+===========
+
+Manibuilder is a set of Dockerfiles for manic build testing, held
+together by some make-foo. Most of the Dockerfiles make use of
+*multiarch* images. This way we can test building on many platforms
+supported by *Qemu*. The idea is to test in environments as close
+as possible to those of potential users, i.e. no cross-compiling
+(with some exceptions).
+
+Make targets
+------------
+
+For each supported target OS/version/architecture exists a *tag*
+target, for instance `alpine:amd64-v3.7`. These targets will
+automatically check for existence of their respective *Docker*
+images (sub target <tag>-check-build), and build them if necessary
+(<tag>-build). Finally, flashrom revision `$(TEST_REVISION)` is
+fetched and build tested.
+
+The results will be kept by *Docker* as stopped containers and
+can be accessed with the <tag>-shell target.
+
+There are some additional targets that form sets of the *tag*
+targets:
+
+* default: runs a preselected subset of all supported tags.
+* native: runs all tags native to the host architecture.
+* all: runs all supported tags.
+
+For each of these show-<set> lists the included *tags*.
+
+For preparation of *Qemu* for the *multiarch* images, there is the
+`register` target. It has to be run once per boot, though as it
+uses a privileged *Docker* container, that is kept as a manual step.
+
+Usage example
+-------------
+
+The most common use case may be testing the current upstream
+*master* branch which is the default for `$(TEST_REVISION)`.
+You'll need roughly 20GiB for the *Docker* images. Might look
+like this:
+
+ $ # have to register Qemu first:
+ $ make register
+ [...]
+ $ # run the default target:
+ $ make -j4
+ debian-debootstrap:mips-stretch: 2
+ debian-debootstrap:mips-sid: 2
+ debian-debootstrap:mips-buster: 2
+ ubuntu-debootstrap:powerpc-xenial: 2
+ djgpp:6.1.0: 2
+
+For each *tag* that returns with a non-zero exit code, the *tag*
+and actual exit code is printed. An exit code of `2` is most likely
+as that is what *make* returns on failure. Other exit codes might
+hint towards a problem in the setup. Failing *tags* can then be
+investigated individually with the <tag>-shell target, e.g.:
+
+ $ make debian-debootstrap:mips-sid-shell
+ [...]
+ mani@63536fc102a5:~/flashrom$ make
+ [...]
+ cc -MMD -Os -Wall -Wshadow -Werror -I/usr/include/libusb-1.0 -D'CONFIG_DEFAULT_PROGRAMMER=PROGRAMMER_INVALID' -D'CONFIG_DEFAULT_PROGRAMMER_ARGS="''"' -D'CONFIG_SERPROG=1' -D'CONFIG_PONY_SPI=1' -D'CONFIG_BITBANG_SPI=1' -D'CONFIG_GFXNVIDIA=1' -D'CONFIG_SATASII=1' -D'CONFIG_ATAVIA=1' -D'CONFIG_IT8212=1' -D'CONFIG_FT2232_SPI=1' -D'CONFIG_USBBLASTER_SPI=1' -D'CONFIG_PICKIT2_SPI=1' -D'HAVE_FT232H=1' -D'CONFIG_DUMMY=1' -D'CONFIG_DRKAISER=1' -D'CONFIG_NICINTEL=1' -D'CONFIG_NICINTEL_SPI=1' -D'CONFIG_NICINTEL_EEPROM=1' -D'CONFIG_OGP_SPI=1' -D'CONFIG_BUSPIRATE_SPI=1' -D'CONFIG_DEDIPROG=1' -D'CONFIG_DEVELOPERBOX_SPI=1' -D'CONFIG_LINUX_MTD=1' -D'CONFIG_LINUX_SPI=1' -D'CONFIG_CH341A_SPI=1' -D'CONFIG_DIGILENT_SPI=1' -D'NEED_PCI=1' -D'NEED_RAW_ACCESS=1' -D'NEED_LIBUSB0=1' -D'NEED_LIBUSB1=1' -D'HAVE_UTSNAME=1' -D'HAVE_CLOCK_GETTIME=1' -D'FLASHROM_VERSION="p1.0-141-g9cecc7e"' -o libflashrom.o -c libflashrom.c
+ libflashrom.c:386:12: error: 'flashrom_layout_parse_fmap' defined but not used [-Werror=unused-function]
+ static int flashrom_layout_parse_fmap(struct flashrom_layout **layout,
+ ^~~~~~~~~~~~~~~~~~~~~~~~~~
+ cc1: all warnings being treated as errors
+ make: *** [Makefile:1075: libflashrom.o] Error 1
+ $ # uh-huh, might be a problem with big-endian #if foo
diff --git a/util/manibuilder/anita-wrapper.sh b/util/manibuilder/anita-wrapper.sh
new file mode 100644
index 00000000..3ff9ee17
--- /dev/null
+++ b/util/manibuilder/anita-wrapper.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+cd
+
+[ "${IDENT}" ] || IDENT=$(mktemp -u XXXXXXXX)
+
+CCACHE=.ccache/anita-${IDENT}.img
+
+[ -f ${CCACHE} ] || zcat cache.img.gz >${CCACHE}
+
+if [ $# -eq 0 ]; then
+ exec anita --vmm-args "-hdb ${CCACHE}" --memory-size=${MEM_SIZE} \
+ interact ${INST_IMG}
+else
+ exec anita --vmm-args "-hdb ${CCACHE}" --memory-size=${MEM_SIZE} \
+ --persist --run ". ./init && manitest \"$*\"" \
+ boot ${INST_IMG}
+fi
diff --git a/util/manibuilder/mani-wrapper.sh b/util/manibuilder/mani-wrapper.sh
new file mode 100644
index 00000000..c3f58340
--- /dev/null
+++ b/util/manibuilder/mani-wrapper.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+cd /home/mani/flashrom/
+
+if [ $# -eq 0 ]; then
+ exec "${DEVSHELL}"
+else
+ exec "${DEVSHELL}" -c "$*"
+fi
diff --git a/util/meson.build b/util/meson.build
deleted file mode 100644
index 24278d73..00000000
--- a/util/meson.build
+++ /dev/null
@@ -1 +0,0 @@
-subdir('ich_descriptors_tool')
diff --git a/util/shell.nix b/util/shell.nix
new file mode 100644
index 00000000..987777c3
--- /dev/null
+++ b/util/shell.nix
@@ -0,0 +1,18 @@
+with import <nixpkgs> {};
+
+stdenv.mkDerivation {
+ name = "flashrom";
+
+ buildInputs = [
+ cmocka
+ gcc
+ gnumake
+ libftdi1
+ libjaylink
+ libusb1
+ meson
+ ninja
+ pciutils
+ pkg-config
+ ];
+}
diff --git a/util/ubertest/ubertest.sh b/util/ubertest/ubertest.sh
index e64daf7c..009194d4 100755
--- a/util/ubertest/ubertest.sh
+++ b/util/ubertest/ubertest.sh
@@ -312,7 +312,7 @@ LOGS="logs"
# Setup temporary working directories:
# LOCAL_TMPDIR: Working directory on local host.
# REMOTE_TMPDIR: Working directory on remote host.
-# TMPDIR: The temporary directy in which we do most of the work. This is
+# TMPDIR: The temporary directory in which we do most of the work. This is
# convenient for commands that depend on $DO_REMOTE.
LOCAL_TMPDIR=$(mktemp -d --tmpdir flashrom_test.XXXXXXXX)
if [ $? -ne 0 ] ; then
@@ -827,7 +827,7 @@ partial_write_test()
return $EXIT_SUCCESS
}
-# Before anything else, check to see if Flashrom can succesfully probe
+# Before anything else, check to see if Flashrom can successfully probe
# for and find the flash chips. If not, we will abort.
flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS" "verify_probe"
if [ $? -ne 0 ]; then