diff options
Diffstat (limited to 'util')
45 files changed, 2041 insertions, 1300 deletions
diff --git a/util/docker/flashrom.org/Dockerfile b/util/docker/flashrom.org/Dockerfile new file mode 100644 index 000000000..23f5f5040 --- /dev/null +++ b/util/docker/flashrom.org/Dockerfile @@ -0,0 +1,29 @@ +FROM alpine:3.8 + +COPY makeSphinx.sh /makeSphinx.sh + +ADD https://sourceforge.net/projects/ditaa/files/ditaa/0.9/ditaa0_9.zip/download /tmp/ditaa.zip + +RUN apk add --no-cache python3 make bash git openjdk8-jre ttf-dejavu fontconfig \ + && pip3 install --upgrade --no-cache-dir pip \ + && pip3 install --no-cache-dir \ + sphinx===1.8.3 \ + sphinx_rtd_theme===0.4.2 \ + recommonmark===0.5.0 \ + sphinx_autobuild===0.7.1 \ + sphinxcontrib-ditaa===0.6 \ + && chmod 755 /makeSphinx.sh +RUN cd /tmp \ + && unzip ditaa.zip \ + && mv ditaa0_9.jar /usr/lib +ADD ditaa.sh /usr/bin/ditaa + +VOLUME /data-in /data-out + +# For Sphinx-autobuild +# Port 8000 - HTTP server +# Port 35729 - websockets connection to allow automatic browser reloads after each build +EXPOSE 8000 35729 + +ENTRYPOINT ["/bin/bash", "/makeSphinx.sh"] +CMD [] diff --git a/util/docker/flashrom.org/README.md b/util/docker/flashrom.org/README.md new file mode 100644 index 000000000..313d0d768 --- /dev/null +++ b/util/docker/flashrom.org/README.md @@ -0,0 +1,32 @@ +# doc.coreboot.org + Docker container for generating and developing documentation for doc.coreboot.org + +**NOTE**: All paths are from the base of the coreboot git repo. + +### Build + +```sh + docker build --force-rm -t "doc.flashrom.org" "$PWD/util/docker/flashrom.org/" +``` + +### Generating production HTML + +```sh +# To ensure the output directory is given the correct permissions, make sure to +# created it before running docker the first time. +mkdir -p "$PWD/doc/_build/" + +docker run -it --rm \ + --user "$(id -u):$(id -g)" \ + -v "$PWD/:/data-in/:ro" \ + -v "$PWD/doc/_build/:/data-out/" \ + doc.flashrom.org +``` + +### live reloaded with web server +On the host machine, open a browser to the address http://0.0.0.0:8000 +```sh +docker run -it --rm \ + --net=host -v "$PWD/:/data-in/:ro" \ + doc.flashrom.org livehtml +``` diff --git a/util/docker/flashrom.org/ditaa.sh b/util/docker/flashrom.org/ditaa.sh new file mode 100755 index 000000000..637379f3e --- /dev/null +++ b/util/docker/flashrom.org/ditaa.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +exec java -jar /usr/lib/ditaa0_9.jar $* diff --git a/util/docker/flashrom.org/makeSphinx.sh b/util/docker/flashrom.org/makeSphinx.sh new file mode 100755 index 000000000..5b6ea0386 --- /dev/null +++ b/util/docker/flashrom.org/makeSphinx.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +if [ "$1" == "livehtml" ]; then + echo "Starting live documentation build" + cd /data-in/ && sphinx-autobuild -b html doc /tmp/build/html +else + echo "Starting production documentation build" + cd /data-in/ \ + && sphinx-build -b html doc /tmp/build/html \ + && rm -rf /data-out/* \ + && mv /tmp/build/html/* /data-out/ +fi diff --git a/util/flashrom.bash-completion.tmpl b/util/flashrom.bash-completion.tmpl new file mode 100644 index 000000000..afb7ae98c --- /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 000000000..8af59dd8c --- /dev/null +++ b/util/flashrom_tester/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_TEST_THREADS = "1" diff --git a/util/flashrom_tester/Cargo.toml b/util/flashrom_tester/Cargo.toml index 0898d3c4f..e6ed9c044 100644 --- a/util/flashrom_tester/Cargo.toml +++ b/util/flashrom_tester/Cargo.toml @@ -3,6 +3,8 @@ 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" @@ -14,18 +16,22 @@ name = "flashrom_tester" required-features = ["cli"] [dependencies] -built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] } +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"] } -nix = "0.14.1" rand = "0.6.4" serde_json = "1" -sys-info = "0.5.7" +sys-info = "0.9" [build-dependencies] -built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] } +built = { version = "0.5", features = ["chrono"] } + +[dev-dependencies] +gag = "1" [features] # Features required to build the CLI binary but not the library diff --git a/util/flashrom_tester/flashrom/Cargo.toml b/util/flashrom_tester/flashrom/Cargo.toml index 27216cbde..4d4fc2fea 100644 --- a/util/flashrom_tester/flashrom/Cargo.toml +++ b/util/flashrom_tester/flashrom/Cargo.toml @@ -3,7 +3,10 @@ name = "flashrom" version = "1.0.0" authors = ["Edward O'Callaghan <quasisec@chromium.org>", "Peter Marheine <pmarheine@chromium.org>"] +description = "Flashrom abstraction for the flashrom_tester tool." +license = "GPL-2.0-only" edition = "2018" [dependencies] -log = "0.4"
\ No newline at end of file +log = "0.4" +libflashrom = { path = "../../../bindings/rust/libflashrom" } diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs index 3fd2ac04d..1f13a8ede 100644 --- a/util/flashrom_tester/flashrom/src/cmd.rs +++ b/util/flashrom_tester/flashrom/src/cmd.rs @@ -33,11 +33,57 @@ // Software Foundation. // -use crate::{FlashChip, FlashromError, FlashromOpt}; +use crate::{FlashChip, FlashromError}; -use std::process::Command; +use std::{ + ffi::{OsStr, OsString}, + path::Path, + process::Command, +}; -#[derive(PartialEq, Debug)] +#[derive(Default)] +pub struct FlashromOpt<'a> { + pub wp_opt: WPOpt, + pub io_opt: Option<IOOpt<'a>>, + + pub flash_name: bool, // --flash-name + pub verbose: bool, // -V +} + +#[derive(Default)] +pub struct WPOpt { + pub range: Option<(i64, i64)>, // --wp-range x0 x1 + pub status: bool, // --wp-status + pub list: bool, // --wp-list + pub enable: bool, // --wp-enable + pub disable: bool, // --wp-disable +} + +pub enum OperationArgs<'a> { + /// The file is the whole chip. + EntireChip(&'a Path), + /// File is the size of the full chip, limited to a single named region. + /// + /// The required path is the file to use, and the optional path is a layout file + /// specifying how to locate regions (if unspecified, flashrom will attempt + /// to discover the layout itself). + FullFileRegion(&'a str, &'a Path, Option<&'a Path>), + /// File is the size of the single named region only. + /// + /// The required path is the file to use, and the optional path is a layout file + /// specifying how to locate regions (if unspecified, flashrom will attempt + /// to discover the layout itself). + RegionFileRegion(&'a str, &'a Path, Option<&'a Path>), // The file contains only the region +} + +pub enum IOOpt<'a> { + Read(OperationArgs<'a>), // -r <file> + Write(OperationArgs<'a>), // -w <file> + Verify(OperationArgs<'a>), // -v <file> + Erase, // -E +} + +#[derive(PartialEq, Eq, Debug)] pub struct FlashromCmd { pub path: String, pub fc: FlashChip, @@ -54,33 +100,204 @@ fn flashrom_extract_size(stdout: &str) -> Result<i64, FlashromError> { .last() .map(str::parse::<i64>) { - None => return Err("Found no purely-numeric lines in flashrom output".into()), + None => Err("Found no purely-numeric lines in flashrom output".into()), Some(Err(e)) => { - return Err(format!( - "Failed to parse flashrom size output as integer: {}", - e - )) + Err(format!("Failed to parse flashrom size output as integer: {}", e).into()) } Some(Ok(sz)) => Ok(sz), } } +impl FlashromCmd { + fn dispatch( + &self, + fropt: FlashromOpt, + debug_name: &str, + ) -> Result<(String, String), FlashromError> { + let params = flashrom_decode_opts(fropt); + flashrom_dispatch(self.path.as_str(), ¶ms, self.fc, debug_name) + } +} + impl crate::Flashrom for FlashromCmd { fn get_size(&self) -> Result<i64, FlashromError> { - let (stdout, _) = flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc)?; - let sz = String::from_utf8_lossy(&stdout); + let (stdout, _) = + flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc, "get_size")?; + flashrom_extract_size(&stdout) + } + + fn name(&self) -> Result<(String, String), FlashromError> { + let opts = FlashromOpt { + flash_name: true, + ..Default::default() + }; + + let (stdout, _) = self.dispatch(opts, "name")?; + match extract_flash_name(&stdout) { + None => Err("Didn't find chip vendor/name in flashrom output".into()), + Some((vendor, name)) => Ok((vendor.into(), name.into())), + } + } - flashrom_extract_size(&sz) + fn write_from_file_region( + &self, + path: &Path, + region: &str, + layout: &Path, + ) -> Result<bool, FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Write(OperationArgs::FullFileRegion( + region, + path, + Some(layout), + ))), + ..Default::default() + }; + + self.dispatch(opts, "write_file_with_layout")?; + Ok(true) } - fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let params = flashrom_decode_opts(fropt); - flashrom_dispatch(self.path.as_str(), ¶ms, self.fc) + fn wp_range(&self, range: (i64, i64), en: bool) -> Result<bool, FlashromError> { + let opts = FlashromOpt { + wp_opt: WPOpt { + enable: en, + disable: !en, + range: Some(range), + ..Default::default() + }, + ..Default::default() + }; + + self.dispatch(opts, "wp_range")?; + Ok(true) + } + + fn wp_list(&self) -> Result<String, FlashromError> { + let opts = FlashromOpt { + wp_opt: WPOpt { + list: true, + ..Default::default() + }, + ..Default::default() + }; + + let (stdout, _) = self.dispatch(opts, "wp_list")?; + if stdout.is_empty() { + return Err( + "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list" + .into(), + ); + } + Ok(stdout) + } + + fn wp_status(&self, en: bool) -> Result<bool, FlashromError> { + let status = if en { "en" } else { "dis" }; + info!("See if chip write protect is {}abled", status); + + let opts = FlashromOpt { + wp_opt: WPOpt { + status: true, + ..Default::default() + }, + ..Default::default() + }; + + let (stdout, _) = self.dispatch(opts, "wp_status")?; + let s = std::format!("write protect is {}abled", status); + Ok(stdout.contains(&s)) + } + + fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> { + let range = if en { + let rom_sz: i64 = self.get_size()?; + (0, rom_sz) // (start, len) + } else { + (0, 0) + }; + self.wp_range(range, en)?; + let status = if en { "en" } else { "dis" }; + match self.wp_status(true) { + Ok(_ret) => { + info!("Successfully {}abled write-protect", status); + Ok(true) + } + Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e).into()), + } + } + + fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Read(OperationArgs::EntireChip(path))), + ..Default::default() + }; + + self.dispatch(opts, "read_into_file")?; + Ok(()) + } + + fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Read(OperationArgs::RegionFileRegion( + region, path, None, + ))), + ..Default::default() + }; + + self.dispatch(opts, "read_region_into_file")?; + Ok(()) + } + + fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Write(OperationArgs::EntireChip(path))), + ..Default::default() + }; + + self.dispatch(opts, "write_from_file")?; + Ok(()) + } + + fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Verify(OperationArgs::EntireChip(path))), + ..Default::default() + }; + + self.dispatch(opts, "verify_from_file")?; + Ok(()) + } + + fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Verify(OperationArgs::RegionFileRegion( + region, path, None, + ))), + ..Default::default() + }; + + self.dispatch(opts, "verify_region_from_file")?; + Ok(()) + } + + fn erase(&self) -> Result<(), FlashromError> { + let opts = FlashromOpt { + io_opt: Some(IOOpt::Erase), + ..Default::default() + }; + + self.dispatch(opts, "erase")?; + Ok(()) + } + + fn can_control_hw_wp(&self) -> bool { + self.fc.can_control_hw_wp() } } -fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> { - let mut params = Vec::<String>::new(); +fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<OsString> { + let mut params = Vec::<OsString>::new(); // ------------ WARNING !!! ------------ // each param must NOT contain spaces! @@ -89,134 +306,144 @@ fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> { // wp_opt if opts.wp_opt.range.is_some() { let (x0, x1) = opts.wp_opt.range.unwrap(); - params.push("--wp-range".to_string()); - params.push(hex_string(x0)); - params.push(hex_string(x1)); + params.push("--wp-range".into()); + params.push(hex_range_string(x0, x1).into()); } if opts.wp_opt.status { - params.push("--wp-status".to_string()); + params.push("--wp-status".into()); } else if opts.wp_opt.list { - params.push("--wp-list".to_string()); + params.push("--wp-list".into()); } else if opts.wp_opt.enable { - params.push("--wp-enable".to_string()); + params.push("--wp-enable".into()); } else if opts.wp_opt.disable { - params.push("--wp-disable".to_string()); + params.push("--wp-disable".into()); } // io_opt - if opts.io_opt.read.is_some() { - params.push("-r".to_string()); - params.push(opts.io_opt.read.unwrap().to_string()); - } else if opts.io_opt.write.is_some() { - params.push("-w".to_string()); - params.push(opts.io_opt.write.unwrap().to_string()); - } else if opts.io_opt.verify.is_some() { - params.push("-v".to_string()); - params.push(opts.io_opt.verify.unwrap().to_string()); - } else if opts.io_opt.erase { - params.push("-E".to_string()); - } - - // misc_opt - if opts.layout.is_some() { - params.push("-l".to_string()); - params.push(opts.layout.unwrap().to_string()); + fn add_operation_args(opts: OperationArgs, params: &mut Vec<OsString>) { + let (file, region, layout) = match opts { + OperationArgs::EntireChip(file) => (Some(file), None, None), + OperationArgs::FullFileRegion(region, file, layout) => { + (Some(file), Some(region.to_string()), layout) + } + OperationArgs::RegionFileRegion(region, file, layout) => ( + None, + Some(format!("{region}:{}", file.to_string_lossy())), + layout, + ), + }; + if let Some(file) = file { + params.push(file.into()) + } + if let Some(region) = region { + params.push("--include".into()); + params.push(region.into()) + } + if let Some(layout) = layout { + params.push("--layout".into()); + params.push(layout.into()) + } } - if opts.image.is_some() { - params.push("-i".to_string()); - params.push(opts.image.unwrap().to_string()); + if let Some(io) = opts.io_opt { + match io { + IOOpt::Read(args) => { + params.push("-r".into()); + add_operation_args(args, &mut params); + } + IOOpt::Write(args) => { + params.push("-w".into()); + add_operation_args(args, &mut params); + } + IOOpt::Verify(args) => { + params.push("-v".into()); + add_operation_args(args, &mut params); + } + IOOpt::Erase => params.push("-E".into()), + } } + // misc_opt if opts.flash_name { - params.push("--flash-name".to_string()); - } - if opts.ignore_fmap { - params.push("--ignore-fmap".to_string()); + params.push("--flash-name".into()); } if opts.verbose { - params.push("-V".to_string()); + params.push("-V".into()); } params } -fn flashrom_dispatch<S: AsRef<str>>( +fn flashrom_dispatch<S: AsRef<OsStr>>( path: &str, params: &[S], fc: FlashChip, -) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { + debug_name: &str, +) -> Result<(String, String), FlashromError> { // from man page: // ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] ' - let mut args: Vec<&str> = vec!["-p", FlashChip::to(fc)]; + let mut args: Vec<&OsStr> = vec![OsStr::new("-p"), OsStr::new(FlashChip::to(fc))]; args.extend(params.iter().map(S::as_ref)); info!("flashrom_dispatch() running: {} {:?}", path, args); let output = match Command::new(path).args(&args).output() { Ok(x) => x, - Err(e) => return Err(format!("Failed to run flashrom: {}", e)), + Err(e) => return Err(format!("Failed to run flashrom: {}", e).into()), }; + + let stdout = String::from_utf8_lossy(output.stdout.as_slice()); + let stderr = String::from_utf8_lossy(output.stderr.as_slice()); + debug!("{}()'stdout: {}.", debug_name, stdout); + debug!("{}()'stderr: {}.", debug_name, stderr); + if !output.status.success() { // There is two cases on failure; // i. ) A bad exit code, // ii.) A SIG killed us. match output.status.code() { Some(code) => { - return Err(format!( - "{}\nExited with error code: {}", - String::from_utf8_lossy(&output.stderr), - code - )); + return Err(format!("{}\nExited with error code: {}", stderr, code).into()); } None => return Err("Process terminated by a signal".into()), } } - Ok((output.stdout, output.stderr)) -} - -pub fn dut_ctrl_toggle_wp(en: bool) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let args = if en { - ["fw_wp_en:off", "fw_wp:on"] - } else { - ["fw_wp_en:on", "fw_wp:off"] - }; - dut_ctrl(&args) + Ok((stdout.into(), stderr.into())) } -pub fn dut_ctrl_servo_type() -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let args = ["servo_type"]; - dut_ctrl(&args) +fn hex_range_string(s: i64, l: i64) -> String { + format!("{:#08X},{:#08X}", s, l) } -fn dut_ctrl(args: &[&str]) -> Result<(Vec<u8>, Vec<u8>), FlashromError> { - let output = match Command::new("dut-control").args(args).output() { - Ok(x) => x, - Err(e) => return Err(format!("Failed to run dut-control: {}", e)), - }; - if !output.status.success() { - // There is two cases on failure; - // i. ) A bad exit code, - // ii.) A SIG killed us. - match output.status.code() { - Some(code) => { - return Err(format!("Exited with error code: {}", code).into()); - } - None => return Err("Process terminated by a signal".into()), +/// Get a flash vendor and name from the first matching line of flashrom output. +/// +/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name. +/// This is usually the last line of output. +fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> { + for line in stdout.lines() { + if !line.starts_with("vendor=\"") { + continue; } - } - Ok((output.stdout, output.stderr)) -} + let tail = line.trim_start_matches("vendor=\""); + let mut split = tail.splitn(2, "\" name=\""); + let vendor = split.next(); + let name = split.next().map(|s| s.trim_end_matches('"')); -fn hex_string(v: i64) -> String { - format!("{:#08X}", v).to_string() + match (vendor, name) { + (Some(v), Some(n)) => return Some((v, n)), + _ => continue, + } + } + None } #[cfg(test)] mod tests { + use std::path::Path; + use super::flashrom_decode_opts; - use crate::{FlashromOpt, IOOpt, WPOpt}; + use super::{FlashromOpt, IOOpt, WPOpt}; #[test] fn decode_wp_opt() { @@ -237,7 +464,7 @@ mod tests { status: true, ..Default::default() }, - &["--wp-range", "0x000000", "0x0004D2", "--wp-status"], + &["--wp-range", "0x000000,0x0004D2", "--wp-status"], ); test_wp_opt( WPOpt { @@ -267,7 +494,7 @@ mod tests { fn test_io_opt(opts: IOOpt, expected: &[&str]) { assert_eq!( flashrom_decode_opts(FlashromOpt { - io_opt: opts, + io_opt: Some(opts), ..Default::default() }), expected @@ -275,62 +502,48 @@ mod tests { } test_io_opt( - IOOpt { - read: Some("foo.bin"), - ..Default::default() - }, + IOOpt::Read(crate::cmd::OperationArgs::EntireChip(Path::new("foo.bin"))), &["-r", "foo.bin"], ); test_io_opt( - IOOpt { - write: Some("bar.bin"), - ..Default::default() - }, + IOOpt::Write(crate::cmd::OperationArgs::EntireChip(Path::new("bar.bin"))), &["-w", "bar.bin"], ); test_io_opt( - IOOpt { - verify: Some("/tmp/baz.bin"), - ..Default::default() - }, - &["-v", "/tmp/baz.bin"], + IOOpt::Verify(crate::cmd::OperationArgs::EntireChip(Path::new("baz.bin"))), + &["-v", "baz.bin"], ); + test_io_opt(IOOpt::Erase, &["-E"]); test_io_opt( - IOOpt { - erase: true, - ..Default::default() - }, - &["-E"], + IOOpt::Read(crate::cmd::OperationArgs::FullFileRegion( + "RO", + Path::new("foo.bin"), + Some(Path::new("baz.bin")), + )), + &["-r", "foo.bin", "--include", "RO", "--layout", "baz.bin"], ); + + test_io_opt( + IOOpt::Read(crate::cmd::OperationArgs::RegionFileRegion( + "foo", + Path::new("bar.bin"), + None, + )), + &["-r", "--include", "foo:bar.bin"], + ) } #[test] fn decode_misc() { //use Default::default; - assert_eq!( - flashrom_decode_opts(FlashromOpt { - layout: Some("TestLayout"), - ..Default::default() - }), - &["-l", "TestLayout"] - ); - - assert_eq!( - flashrom_decode_opts(FlashromOpt { - image: Some("TestImage"), - ..Default::default() - }), - &["-i", "TestImage"] - ); assert_eq!( flashrom_decode_opts(FlashromOpt { flash_name: true, - ignore_fmap: true, verbose: true, ..Default::default() }), - &["--flash-name", "--ignore-fmap", "-V"] + &["--flash-name", "-V"] ); } @@ -352,4 +565,26 @@ mod tests { Err("Found no purely-numeric lines in flashrom output".into()) ); } + + #[test] + fn extract_flash_name() { + use super::extract_flash_name; + + assert_eq!( + extract_flash_name( + "coreboot table found at 0x7cc13000\n\ + Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\ + vendor=\"Winbond\" name=\"W25Q64DW\"\n" + ), + Some(("Winbond", "W25Q64DW")) + ); + + assert_eq!( + extract_flash_name( + "vendor name is TEST\n\ + Something failed!" + ), + None + ) + } } diff --git a/util/flashrom_tester/flashrom/src/flashromlib.rs b/util/flashrom_tester/flashrom/src/flashromlib.rs new file mode 100644 index 000000000..5e1747b1b --- /dev/null +++ b/util/flashrom_tester/flashrom/src/flashromlib.rs @@ -0,0 +1,184 @@ +// Copyright 2022, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Alternatively, this software may be distributed under the terms of the +// GNU General Public License ("GPL") version 2 as published by the Free +// Software Foundation. +// + +use libflashrom::{Chip, Programmer}; + +use std::{cell::RefCell, convert::TryFrom, fs, path::Path}; + +use crate::{FlashChip, FlashromError}; + +#[derive(Debug)] +pub struct FlashromLib { + // RefCell required here to keep Flashrom trait immutable. + // Cant make Flashrom methods mut because WriteProtectState + // and TestEnv both keep a reference. + pub flashrom: RefCell<Chip>, + pub fc: FlashChip, +} + +impl FlashromLib { + pub fn new(fc: FlashChip, log_level: libflashrom::flashrom_log_level) -> FlashromLib { + libflashrom::set_log_level(Some(log_level)); + let (programmer, options) = FlashChip::to_split(fc); + let flashrom = Chip::new(Programmer::new(programmer, options).unwrap(), None).unwrap(); + FlashromLib { + flashrom: RefCell::new(flashrom), + fc, + } + } +} + +impl crate::Flashrom for FlashromLib { + fn get_size(&self) -> Result<i64, FlashromError> { + Ok(self.flashrom.borrow().get_size() as i64) + } + + fn name(&self) -> Result<(String, String), FlashromError> { + Ok(("not".to_string(), "implemented".to_string())) + } + + fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError> { + let mut cfg = libflashrom::WriteProtectCfg::new()?; + let start = usize::try_from(range.0).unwrap(); + let len = usize::try_from(range.1).unwrap(); + cfg.set_range::<std::ops::Range<usize>>(start..(start + len)); + cfg.set_mode(if wp_enable { + libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_HARDWARE + } else { + libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED + }); + self.flashrom.borrow_mut().set_wp(&cfg)?; + Ok(true) + } + + fn wp_list(&self) -> Result<String, FlashromError> { + let ranges = self.flashrom.borrow_mut().get_wp_ranges()?; + Ok(format!("{:?}", ranges)) + } + + fn wp_status(&self, en: bool) -> Result<bool, FlashromError> { + let ret = self + .flashrom + .borrow_mut() + .get_wp() + .map_err(|e| format!("{:?}", e))? + .get_mode(); + if en { + Ok(ret != libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED) + } else { + Ok(ret == libflashrom::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED) + } + } + + fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> { + let range = if en { (0, self.get_size()?) } else { (0, 0) }; + self.wp_range(range, en) + } + + fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> { + let buf = self.flashrom.borrow_mut().image_read(None)?; + fs::write(path, buf).map_err(|error| error.to_string())?; + Ok(()) + } + + fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?; + layout.include_region(region)?; + let range = layout.get_region_range(region)?; + let buf = self.flashrom.borrow_mut().image_read(None)?; + fs::write(path, &buf[range]).map_err(|error| error.to_string())?; + Ok(()) + } + + fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let mut buf = fs::read(path).map_err(|error| error.to_string())?; + self.flashrom.borrow_mut().image_write(&mut buf, None)?; + Ok(()) + } + + fn write_from_file_region( + &self, + path: &Path, + region: &str, + layout: &Path, + ) -> Result<bool, FlashromError> { + let buf = fs::read(layout).map_err(|error| error.to_string())?; + let buf = String::from_utf8(buf).unwrap(); + let mut layout: libflashrom::Layout = buf + .parse() + .map_err(|e: Box<dyn std::error::Error>| e.to_string())?; + layout.include_region(region)?; + let mut buf = fs::read(path).map_err(|error| error.to_string())?; + self.flashrom + .borrow_mut() + .image_write(&mut buf, Some(layout))?; + Ok(true) + } + + fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> { + let buf = fs::read(path).map_err(|error| error.to_string())?; + self.flashrom.borrow_mut().image_verify(&buf, None)?; + Ok(()) + } + + fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> { + let mut layout = self.flashrom.borrow_mut().layout_read_fmap_from_rom()?; + layout.include_region(region)?; + let range = layout.get_region_range(region)?; + let region_data = fs::read(path).map_err(|error| error.to_string())?; + if region_data.len() != range.len() { + return Err(format!( + "verify region range ({}) does not match provided file size ({})", + range.len(), + region_data.len() + ) + .into()); + } + let mut buf = vec![0; self.get_size()? as usize]; + buf[range].copy_from_slice(®ion_data); + self.flashrom + .borrow_mut() + .image_verify(&buf, Some(layout))?; + Ok(()) + } + + fn erase(&self) -> Result<(), FlashromError> { + self.flashrom.borrow_mut().erase()?; + Ok(()) + } + + fn can_control_hw_wp(&self) -> bool { + self.fc.can_control_hw_wp() + } +} diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs index 734e3ff4a..41393e841 100644 --- a/util/flashrom_tester/flashrom/src/lib.rs +++ b/util/flashrom_tester/flashrom/src/lib.rs @@ -37,36 +37,41 @@ extern crate log; mod cmd; +mod flashromlib; -pub use cmd::{dut_ctrl_toggle_wp, FlashromCmd}; +use std::{error, fmt, path::Path}; -#[derive(Copy, Clone, PartialEq, Debug)] +pub use cmd::FlashromCmd; +pub use flashromlib::FlashromLib; + +pub use libflashrom::{ + flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR, + FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum FlashChip { - EC, HOST, - SERVO, - DEDIPROG, } impl FlashChip { pub fn from(s: &str) -> Result<FlashChip, &str> { - let r = match s { - "ec" => Ok(FlashChip::EC), + match s { "host" => Ok(FlashChip::HOST), - "servo" => Ok(FlashChip::SERVO), - "dediprog" => Ok(FlashChip::DEDIPROG), _ => Err("cannot convert str to enum"), - }; - return r; + } } pub fn to(fc: FlashChip) -> &'static str { - let r = match fc { - FlashChip::EC => "ec", + match fc { FlashChip::HOST => "host", - FlashChip::SERVO => "ft2231_spi:type=servo-v2", - FlashChip::DEDIPROG => "dediprog", - }; - return r; + } + } + + /// Return the programmer string and optional programmer options + pub fn to_split(fc: FlashChip) -> (&'static str, Option<&'static str>) { + let programmer = FlashChip::to(fc); + let mut bits = programmer.splitn(2, ':'); + (bits.next().unwrap(), bits.next()) } /// Return whether the hardware write protect signal can be controlled. @@ -75,307 +80,86 @@ impl FlashChip { /// disabled. pub fn can_control_hw_wp(&self) -> bool { match self { - FlashChip::HOST | FlashChip::EC => true, - FlashChip::SERVO | FlashChip::DEDIPROG => false, + FlashChip::HOST => true, } } } -pub type FlashromError = String; - -#[derive(Default)] -pub struct FlashromOpt<'a> { - pub wp_opt: WPOpt, - pub io_opt: IOOpt<'a>, - - pub layout: Option<&'a str>, // -l <file> - pub image: Option<&'a str>, // -i <name> - - pub flash_name: bool, // --flash-name - pub ignore_fmap: bool, // --ignore-fmap - pub verbose: bool, // -V -} - -#[derive(Default)] -pub struct WPOpt { - pub range: Option<(i64, i64)>, // --wp-range x0 x1 - pub status: bool, // --wp-status - pub list: bool, // --wp-list - pub enable: bool, // --wp-enable - pub disable: bool, // --wp-disable +#[derive(Debug, PartialEq, Eq)] +pub struct FlashromError { + msg: String, } -#[derive(Default)] -pub struct IOOpt<'a> { - pub read: Option<&'a str>, // -r <file> - pub write: Option<&'a str>, // -w <file> - pub verify: Option<&'a str>, // -v <file> - pub erase: bool, // -E -} - -pub trait Flashrom { - fn get_size(&self) -> Result<i64, FlashromError>; - fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError>; -} - -pub fn name(cmd: &cmd::FlashromCmd) -> Result<(String, String), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - ..Default::default() - }, - - flash_name: true, - - ..Default::default() - }; - - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); - debug!("name()'stdout: {:#?}.", output); - debug!("name()'stderr: {:#?}.", eoutput); - - match extract_flash_name(&output) { - None => Err("Didn't find chip vendor/name in flashrom output".into()), - Some((vendor, name)) => Ok((vendor.into(), name.into())), - } -} - -/// Get a flash vendor and name from the first matching line of flashrom output. -/// -/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name. -/// This is usually the last line of output. -fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> { - for line in stdout.lines() { - if !line.starts_with("vendor=\"") { - continue; - } - - let tail = line.trim_start_matches("vendor=\""); - let mut split = tail.splitn(2, "\" name=\""); - let vendor = split.next(); - let name = split.next().map(|s| s.trim_end_matches('"')); - - match (vendor, name) { - (Some(v), Some(n)) => return Some((v, n)), - _ => continue, - } +impl fmt::Display for FlashromError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.msg) } - None -} - -pub struct ROMWriteSpecifics<'a> { - pub layout_file: Option<&'a str>, - pub write_file: Option<&'a str>, - pub name_file: Option<&'a str>, -} - -pub fn write_file_with_layout( - cmd: &cmd::FlashromCmd, - rws: &ROMWriteSpecifics, -) -> Result<bool, FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - write: rws.write_file, - ..Default::default() - }, - - layout: rws.layout_file, - image: rws.name_file, - - ignore_fmap: true, - - ..Default::default() - }; - - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); - debug!("write_file_with_layout()'stdout:\n{}.", output); - debug!("write_file_with_layout()'stderr:\n{}.", eoutput); - Ok(true) } -pub fn wp_range( - cmd: &cmd::FlashromCmd, - range: (i64, i64), - wp_enable: bool, -) -> Result<bool, FlashromError> { - let opts = FlashromOpt { - wp_opt: WPOpt { - range: Some(range), - enable: wp_enable, - ..Default::default() - }, - ..Default::default() - }; +impl error::Error for FlashromError {} - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); - debug!("wp_range()'stdout:\n{}.", output); - debug!("wp_range()'stderr:\n{}.", eoutput); - Ok(true) -} - -pub fn wp_list(cmd: &cmd::FlashromCmd) -> Result<String, FlashromError> { - let opts = FlashromOpt { - wp_opt: WPOpt { - list: true, - ..Default::default() - }, - ..Default::default() - }; - - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - if output.len() == 0 { - return Err( - "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list".into(), - ); +impl<T> From<T> for FlashromError +where + T: Into<String>, +{ + fn from(msg: T) -> Self { + FlashromError { msg: msg.into() } } - Ok(output.to_string()) } -pub fn wp_status(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> { - let status = if en { "en" } else { "dis" }; - info!("See if chip write protect is {}abled", status); - - let opts = FlashromOpt { - wp_opt: WPOpt { - status: true, - ..Default::default() - }, - ..Default::default() - }; - - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - - debug!("wp_status():\n{}", output); - - let s = std::format!("write protect is {}abled", status); - Ok(output.contains(&s)) -} - -pub fn wp_toggle(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> { - let status = if en { "en" } else { "dis" }; - - // For MTD, --wp-range and --wp-enable must be used simultaneously. - let range = if en { - let rom_sz: i64 = cmd.get_size()?; - Some((0, rom_sz)) // (start, len) - } else { - None - }; - - let opts = FlashromOpt { - wp_opt: WPOpt { - range: range, - enable: en, - disable: !en, - ..Default::default() - }, - ..Default::default() - }; - - let (stdout, stderr) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - let eoutput = String::from_utf8_lossy(stderr.as_slice()); +pub trait Flashrom { + /// Returns the size of the flash in bytes. + fn get_size(&self) -> Result<i64, FlashromError>; - debug!("wp_toggle()'stdout:\n{}.", output); - debug!("wp_toggle()'stderr:\n{}.", eoutput); + /// Returns the vendor name and the flash name. + fn name(&self) -> Result<(String, String), FlashromError>; - match wp_status(&cmd, true) { - Ok(_ret) => { - info!("Successfully {}abled write-protect", status); - Ok(true) - } - Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e)), - } -} + /// Set write protect status and range. + fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError>; -pub fn read(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - read: Some(path), - ..Default::default() - }, - ..Default::default() - }; + /// Read the write protect regions for the flash. + fn wp_list(&self) -> Result<String, FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("read():\n{}", output); - Ok(()) -} + /// Return true if the flash write protect status matches `en`. + fn wp_status(&self, en: bool) -> Result<bool, FlashromError>; -pub fn write(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - write: Some(path), - ..Default::default() - }, - ..Default::default() - }; + /// Set write protect status. + /// If en=true sets wp_range to the whole chip (0,getsize()). + /// If en=false sets wp_range to (0,0). + /// This is due to the MTD driver, which requires wp enable to use a range + /// length != 0 and wp disable to have the range 0,0. + fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("write():\n{}", output); - Ok(()) -} + /// Read the whole flash to the file specified by `path`. + fn read_into_file(&self, path: &Path) -> Result<(), FlashromError>; -pub fn verify(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - verify: Some(path), - ..Default::default() - }, - ..Default::default() - }; + /// Read only a region of the flash into the file specified by `path`. Note + /// the first byte written to the file is the first byte from the region. + fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("verify():\n{}", output); - Ok(()) -} + /// Write the whole flash to the file specified by `path`. + fn write_from_file(&self, path: &Path) -> Result<(), FlashromError>; -pub fn erase(cmd: &cmd::FlashromCmd) -> Result<(), FlashromError> { - let opts = FlashromOpt { - io_opt: IOOpt { - erase: true, - ..Default::default() - }, - ..Default::default() - }; + /// Write only a region of the flash. + /// `path` is a file of the size of the whole flash. + /// The `region` name corresponds to a region name in the `layout` file, not the flash. + fn write_from_file_region( + &self, + path: &Path, + region: &str, + layout: &Path, + ) -> Result<bool, FlashromError>; - let (stdout, _) = cmd.dispatch(opts)?; - let output = String::from_utf8_lossy(stdout.as_slice()); - debug!("erase():\n{}", output); - Ok(()) -} + /// Verify the whole flash against the file specified by `path`. + fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError>; -#[cfg(test)] -mod tests { - #[test] - fn extract_flash_name() { - use super::extract_flash_name; + /// Verify only the region against the file specified by `path`. + /// Note the first byte in the file is matched against the first byte of the region. + fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>; - assert_eq!( - extract_flash_name( - "coreboot table found at 0x7cc13000\n\ - Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\ - vendor=\"Winbond\" name=\"W25Q64DW\"\n" - ), - Some(("Winbond", "W25Q64DW")) - ); + /// Erase the whole flash. + fn erase(&self) -> Result<(), FlashromError>; - assert_eq!( - extract_flash_name( - "vendor name is TEST\n\ - Something failed!" - ), - None - ) - } + /// Return true if the hardware write protect of this flash can be controlled. + fn can_control_hw_wp(&self) -> bool; } diff --git a/util/flashrom_tester/src/cros_sysinfo.rs b/util/flashrom_tester/src/cros_sysinfo.rs index ddb080265..37e1ec659 100644 --- a/util/flashrom_tester/src/cros_sysinfo.rs +++ b/util/flashrom_tester/src/cros_sysinfo.rs @@ -34,7 +34,7 @@ // use std::ffi::OsStr; -use std::fmt::Debug; +use std::fs; use std::io::Result as IoResult; use std::process::{Command, Stdio}; @@ -60,21 +60,11 @@ pub fn bios_info() -> IoResult<String> { dmidecode_dispatch(&["-q", "-t0"]) } -pub fn eventlog_list() -> Result<String, std::io::Error> { - mosys_dispatch(&["eventlog", "list"]) -} - -fn mosys_dispatch<S: AsRef<OsStr> + Debug>(args: &[S]) -> IoResult<String> { - info!("mosys_dispatch() running: /usr/sbin/mosys {:?}", args); - - let output = Command::new("/usr/sbin/mosys") - .args(args) - .stdin(Stdio::null()) - .output()?; - if !output.status.success() { - return Err(utils::translate_command_error(&output)); +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()); + } } - - let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); - Ok(stdout) + Err(std::io::ErrorKind::NotFound.into()) } diff --git a/util/flashrom_tester/src/logger.rs b/util/flashrom_tester/src/logger.rs index e1c00f5de..c9c364066 100644 --- a/util/flashrom_tester/src/logger.rs +++ b/util/flashrom_tester/src/logger.rs @@ -35,105 +35,81 @@ use flashrom_tester::types; use std::io::Write; -use std::path::PathBuf; -use std::sync::Mutex; -struct Logger<W: Write + Send> { +struct Logger { level: log::LevelFilter, - target: LogTarget<W>, + color: types::Color, } -enum LogTarget<W> -where - W: Write, -{ - Terminal, - Write(Mutex<W>), -} - -impl<W: Write + Send> log::Log for Logger<W> { +impl log::Log for Logger { fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= self.level } fn log(&self, record: &log::Record) { - fn log_internal<W: Write>(mut w: W, record: &log::Record) -> std::io::Result<()> { - let now = chrono::Local::now(); - write!(w, "{}{} ", types::MAGENTA, now.format("%Y-%m-%dT%H:%M:%S"))?; - write!( - w, - "{}[ {} ]{} ", - types::YELLOW, - record.level(), - types::RESET - )?; - writeln!(w, "{}", record.args()) - } - // Write errors deliberately ignored - let _ = match self.target { - LogTarget::Terminal => { - let stdout = std::io::stdout(); - let mut lock = stdout.lock(); - log_internal(&mut lock, record) - } - LogTarget::Write(ref mutex) => { - let mut lock = mutex.lock().unwrap(); - log_internal(&mut *lock, record) - } - }; + 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 _ = match self.target { - LogTarget::Terminal => std::io::stdout().flush(), - LogTarget::Write(ref w) => w.lock().unwrap().flush(), - }; + let _ = std::io::stdout().flush(); } } -pub fn init(to_file: Option<PathBuf>, debug: bool) { +pub fn init(debug: bool) { let mut logger = Logger { level: log::LevelFilter::Info, - target: LogTarget::Terminal, + color: if atty::is(atty::Stream::Stdout) { + types::COLOR + } else { + types::NOCOLOR + }, }; if debug { logger.level = log::LevelFilter::Debug; } - if let Some(path) = to_file { - logger.target = LogTarget::Write(Mutex::new( - std::fs::File::create(path).expect("Unable to open log file for writing"), - )); - } - log::set_max_level(logger.level); log::set_boxed_logger(Box::new(logger)).unwrap(); } #[cfg(test)] mod tests { - use super::{LogTarget, Logger}; + use std::io::Read; + + use super::Logger; + use flashrom_tester::types; use log::{Level, LevelFilter, Log, Record}; - use std::sync::Mutex; fn run_records(records: &[Record]) -> String { - let mut buf = Vec::<u8>::new(); + let buf = gag::BufferRedirect::stdout().unwrap(); { - let lock = Mutex::new(&mut buf); let logger = Logger { level: LevelFilter::Info, - target: LogTarget::Write(lock), + color: types::COLOR, }; for record in records { if logger.enabled(record.metadata()) { - logger.log(&record); + logger.log(record); } } } - String::from_utf8(buf).unwrap() + let mut ret = String::new(); + buf.into_inner().read_to_string(&mut ret).unwrap(); + ret } /// Log messages have the expected format @@ -146,9 +122,10 @@ mod tests { 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[24..], - " \x1b[33m[ INFO ]\x1b[0m Test message at INFO\n" + buf.split_once("Z ").unwrap().1, + "\x1b[33m[ INFO ]\x1b[0m Test message at INFO\n" ); } diff --git a/util/flashrom_tester/src/main.rs b/util/flashrom_tester/src/main.rs index e589ee1e6..b8a2581ac 100644 --- a/util/flashrom_tester/src/main.rs +++ b/util/flashrom_tester/src/main.rs @@ -39,9 +39,8 @@ extern crate log; mod logger; use clap::{App, Arg}; -use flashrom::FlashChip; +use flashrom::{FlashChip, Flashrom, FlashromCmd, FlashromLib}; use flashrom_tester::{tester, tests}; -use std::path::PathBuf; use std::sync::atomic::AtomicBool; pub mod built_info { @@ -65,11 +64,25 @@ fn main() { built_info::BUILT_TIME_UTC, built_info::RUSTC_VERSION, )) - .arg(Arg::with_name("flashrom_binary").required(true)) + .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", "ec", "servo"]), + .possible_values(&["host"]), ) .arg( Arg::with_name("print-layout") @@ -78,13 +91,6 @@ fn main() { .help("Print the layout file's contents before running tests"), ) .arg( - Arg::with_name("log-file") - .short("o") - .long("log-file") - .takes_value(true) - .help("Write logs to a file rather than stdout"), - ) - .arg( Arg::with_name("log_debug") .short("d") .long("debug") @@ -107,15 +113,13 @@ fn main() { ) .get_matches(); - logger::init( - matches.value_of_os("log-file").map(PathBuf::from), - matches.is_present("log_debug"), - ); + logger::init(matches.is_present("log_debug")); debug!("Args parsed and logging initialized OK"); - let flashrom_path = matches - .value_of("flashrom_binary") - .expect("flashrom_binary should be required"); + 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") @@ -123,6 +127,25 @@ fn main() { ) .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") @@ -132,12 +155,13 @@ fn main() { let test_names = matches.values_of("test_name"); if let Err(e) = tests::generic( - flashrom_path, + 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); @@ -152,12 +176,11 @@ fn main() { /// Once a signal is trapped, the default behavior is restored (terminating /// the process) for future signals. fn handle_sigint() -> &'static AtomicBool { - use nix::libc::c_int; - use nix::sys::signal::{self, SigHandler, Signal}; + use libc::c_int; use std::sync::atomic::Ordering; unsafe { - let _ = signal::signal(Signal::SIGINT, SigHandler::Handler(sigint_handler)); + let _ = libc::signal(libc::SIGINT, sigint_handler as libc::sighandler_t); } static TERMINATE_FLAG: AtomicBool = AtomicBool::new(false); @@ -169,10 +192,17 @@ 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 - let _ = nix::unistd::write(STDERR_FILENO, MESSAGE); + // 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 _ = signal::signal(Signal::SIGINT, SigHandler::SigDfl); + let _ = libc::signal(libc::SIGINT, libc::SIG_DFL); } TERMINATE_FLAG.store(true, Ordering::Release); } diff --git a/util/flashrom_tester/src/rand_util.rs b/util/flashrom_tester/src/rand_util.rs index 51411d0d5..a040c89a3 100644 --- a/util/flashrom_tester/src/rand_util.rs +++ b/util/flashrom_tester/src/rand_util.rs @@ -36,15 +36,14 @@ 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: &str, size: usize) -> std::io::Result<()> { +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::with_capacity(size); - // Pad out array to be filled in by Rng::fill(). - a.resize(size, 0b0); + let mut a: Vec<u8> = vec![0; size]; thread_rng().fill(a.as_mut_slice()); buf.write_all(a.as_slice())?; @@ -60,8 +59,8 @@ mod tests { fn gen_rand_testdata() { use super::gen_rand_testdata; - let path0 = "/tmp/idk_test00"; - let path1 = "/tmp/idk_test01"; + let path0 = Path::new("/tmp/idk_test00"); + let path1 = Path::new("/tmp/idk_test01"); let sz = 1024; gen_rand_testdata(path0, sz).unwrap(); diff --git a/util/flashrom_tester/src/tester.rs b/util/flashrom_tester/src/tester.rs index 3150a4354..4629c2eb7 100644 --- a/util/flashrom_tester/src/tester.rs +++ b/util/flashrom_tester/src/tester.rs @@ -36,11 +36,14 @@ use super::rand_util; use super::types; use super::utils::{self, LayoutSizes}; -use flashrom::{FlashChip, Flashrom, FlashromCmd}; +use flashrom::FlashromError; +use flashrom::{FlashChip, Flashrom}; use serde_json::json; -use std::mem::MaybeUninit; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; // type-signature comes from the return type of lib.rs workers. type TestError = Box<dyn std::error::Error>; @@ -52,33 +55,38 @@ pub struct TestEnv<'a> { /// /// Where possible, prefer to use methods on the TestEnv rather than delegating /// to the raw flashrom functions. - pub cmd: &'a FlashromCmd, + pub cmd: &'a dyn Flashrom, layout: LayoutSizes, - pub wp: WriteProtectState<'a, 'static>, + pub wp: WriteProtectState<'a>, /// The path to a file containing the flash contents at test start. - // TODO(pmarheine) migrate this to a PathBuf for clarity - original_flash_contents: String, + original_flash_contents: PathBuf, /// The path to a file containing flash-sized random data - // TODO(pmarheine) make this a PathBuf too - random_data: String, + 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 FlashromCmd) -> Result<Self, String> { + 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: chip_type, - cmd: cmd, + chip_type, + cmd, layout: utils::get_layout_sizes(rom_sz)?, - wp: WriteProtectState::from_hardware(cmd)?, + 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"); - flashrom::read(&out.cmd, &out.original_flash_contents)?; - flashrom::verify(&out.cmd, &out.original_flash_contents)?; + 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) @@ -88,19 +96,10 @@ impl<'a> TestEnv<'a> { } pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult { - let use_dut_control = self.chip_type == FlashChip::SERVO; - if use_dut_control && flashrom::dut_ctrl_toggle_wp(false).is_err() { - error!("failed to dispatch dut_ctrl_toggle_wp()!"); - } - let name = test.get_name(); info!("Beginning test: {}", name); let out = test.run(self); info!("Completed test: {}; result {:?}", name, out); - - if use_dut_control && flashrom::dut_ctrl_toggle_wp(true).is_err() { - error!("failed to dispatch dut_ctrl_toggle_wp()!"); - } out } @@ -112,7 +111,7 @@ impl<'a> TestEnv<'a> { /// Return the path to a file that contains random data and is the same size /// as the flash chip. - pub fn random_data_file(&self) -> &str { + pub fn random_data_file(&self) -> &Path { &self.random_data } @@ -123,31 +122,36 @@ impl<'a> TestEnv<'a> { /// Return true if the current Flash contents are the same as the golden image /// that was present at the start of testing. pub fn is_golden(&self) -> bool { - flashrom::verify(&self.cmd, &self.original_flash_contents).is_ok() + 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<(), String> { + pub fn ensure_golden(&mut self) -> Result<(), FlashromError> { self.wp.set_hw(false)?.set_sw(false)?; - flashrom::write(&self.cmd, &self.original_flash_contents) + self.cmd.write_from_file(&self.original_flash_contents)?; + Ok(()) } /// Attempt to erase the flash. - pub fn erase(&self) -> Result<(), String> { - flashrom::erase(self.cmd) + 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: &str) -> Result<(), String> { - flashrom::verify(self.cmd, contents_path) + pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> { + self.cmd.verify_from_file(contents_path)?; + Ok(()) } } -impl Drop for TestEnv<'_> { +impl<'a> Drop for TestEnv<'a> { fn drop(&mut self) { info!("Verifying flash remains unmodified"); if !self.is_golden() { @@ -159,70 +163,41 @@ impl Drop for TestEnv<'_> { } } +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` or `push`. When it goes out of scope, the write protects will be returned +/// `set`. When it goes out of scope, the write protects will be returned /// to the state they had then it was created. -/// -/// The lifetime `'p` on this struct is the parent state it derives from; `'static` -/// implies it is derived from hardware, while anything else is part of a stack -/// created by `push`ing states. An initial state is always static, and the stack -/// forms a lifetime chain `'static -> 'p -> 'p1 -> ... -> 'pn`. -pub struct WriteProtectState<'a, 'p> { - /// The parent state this derives from. - /// - /// If it's a root (gotten via `from_hardware`), then this is Hardware and the - /// liveness flag will be reset on drop. - initial: InitialState<'p>, - // Tuples are (hardware, software) - current: (bool, bool), - cmd: &'a FlashromCmd, -} - -enum InitialState<'p> { - Hardware(bool, bool), - Previous(&'p WriteProtectState<'p, 'p>), +pub struct WriteProtectState<'a> { + current: WriteProtect, + initial: WriteProtect, + cmd: &'a dyn Flashrom, + fc: FlashChip, } -impl InitialState<'_> { - fn get_target(&self) -> (bool, bool) { - match self { - InitialState::Hardware(hw, sw) => (*hw, *sw), - InitialState::Previous(s) => s.current, - } - } -} - -impl<'a> WriteProtectState<'a, 'static> { +impl<'a> WriteProtectState<'a> { /// Initialize a state from the current state of the hardware. - /// - /// Panics if there is already a live state derived from hardware. In such a situation the - /// new state must be derived from the live one, or the live one must be dropped first. - pub fn from_hardware(cmd: &'a FlashromCmd) -> Result<Self, String> { - let mut lock = Self::get_liveness_lock() - .lock() - .expect("Somebody panicked during WriteProtectState init from hardware"); - if *lock { - drop(lock); // Don't poison the lock - panic!("Attempted to create a new WriteProtectState when one is already live"); - } - + 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 hardware write protect: HW={} SW={}", hw, sw); + info!("Initial write protect state: HW={} SW={}", hw, sw); - *lock = true; Ok(WriteProtectState { - initial: InitialState::Hardware(hw, sw), - current: (hw, sw), + current: WriteProtect { hw, sw }, + initial: WriteProtect { hw, sw }, cmd, + fc, }) } /// Get the actual hardware write protect state. - fn get_hw(cmd: &FlashromCmd) -> Result<bool, String> { - if cmd.fc.can_control_hw_wp() { + fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> { + if cmd.can_control_hw_wp() { super::utils::get_hardware_wp() } else { Ok(false) @@ -230,98 +205,78 @@ impl<'a> WriteProtectState<'a, 'static> { } /// Get the actual software write protect state. - fn get_sw(cmd: &FlashromCmd) -> Result<bool, String> { - flashrom::wp_status(cmd, true) + fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> { + let b = cmd.wp_status(true)?; + Ok(b) } -} -impl<'a, 'p> WriteProtectState<'a, 'p> { /// Return true if the current programmer supports setting the hardware /// write protect. /// /// If false, calls to set_hw() will do nothing. pub fn can_control_hw_wp(&self) -> bool { - self.cmd.fc.can_control_hw_wp() + self.cmd.can_control_hw_wp() } - /// Set the software write protect. + /// 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!("request={}, current={}", enable, self.current.1); - if self.current.1 != enable { - flashrom::wp_toggle(self.cmd, /* en= */ enable)?; - self.current.1 = enable; + 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())?; } - Ok(self) - } - - /// Set the hardware write protect. - pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> { - if self.current.0 != enable { - if self.can_control_hw_wp() { - super::utils::toggle_hw_wp(/* dis= */ !enable)?; - self.current.0 = enable; - } else if enable { - info!( - "Ignoring attempt to enable hardware WP with {:?} programmer", - self.cmd.fc - ); - } + 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) } - Ok(self) } - /// Stack a new write protect state on top of the current one. - /// - /// This is useful if you need to temporarily make a change to write protection: - /// - /// ```no_run - /// # fn main() -> Result<(), String> { - /// # let cmd: flashrom::FlashromCmd = unimplemented!(); - /// let wp = flashrom_tester::tester::WriteProtectState::from_hardware(&cmd)?; - /// { - /// let mut wp = wp.push(); - /// wp.set_sw(false)?; - /// // Do something with software write protect disabled - /// } - /// // Now software write protect returns to its original state, even if - /// // set_sw() failed. - /// # Ok(()) - /// # } - /// ``` - /// - /// This returns a new state which restores the original when it is dropped- the new state - /// refers to the old, so the compiler enforces that states are disposed of in the reverse - /// order of their creation and correctly restore the original state. - pub fn push<'p1>(&'p1 self) -> WriteProtectState<'a, 'p1> { - WriteProtectState { - initial: InitialState::Previous(self), - current: self.current, - cmd: self.cmd, + // 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) } } - fn get_liveness_lock() -> &'static Mutex<bool> { - static INIT: std::sync::Once = std::sync::Once::new(); - /// Value becomes true when there is a live WriteProtectState derived `from_hardware`, - /// blocking duplicate initialization. - /// - /// This is required because hardware access is not synchronized; it's possible to leave the - /// hardware in an unintended state by creating a state handle from it, modifying the state, - /// creating another handle from the hardware then dropping the first handle- then on drop - /// of the second handle it will restore the state to the modified one rather than the initial. - /// - /// This flag ensures that a duplicate root state cannot be created. - /// - /// This is a Mutex<bool> rather than AtomicBool because acquiring the flag needs to perform - /// several operations that may themselves fail- acquisitions must be fully synchronized. - static mut LIVE_FROM_HARDWARE: MaybeUninit<Mutex<bool>> = MaybeUninit::uninit(); - - unsafe { - INIT.call_once(|| { - LIVE_FROM_HARDWARE.as_mut_ptr().write(Mutex::new(false)); - }); - &*LIVE_FROM_HARDWARE.as_ptr() + /// 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. @@ -329,91 +284,30 @@ impl<'a, 'p> WriteProtectState<'a, 'p> { /// This behaves exactly like allowing a state to go out of scope, but it can return /// errors from that process rather than panicking. pub fn close(mut self) -> Result<(), String> { - unsafe { - let out = self.drop_internal(); - // We just ran drop, don't do it again - std::mem::forget(self); - out - } + let out = self.drop_internal(); + // We just ran drop, don't do it again + std::mem::forget(self); + out } - /// Internal Drop impl. - /// - /// This is unsafe because it effectively consumes self when clearing the - /// liveness lock. Callers must be able to guarantee that self will be forgotten - /// if the state was constructed from hardware in order to uphold the liveness - /// invariant (that only a single state constructed from hardware exists at any - /// time). - unsafe fn drop_internal(&mut self) -> Result<(), String> { - let lock = match self.initial { - InitialState::Hardware(_, _) => Some( - Self::get_liveness_lock() - .lock() - .expect("Somebody panicked during WriteProtectState drop from hardware"), - ), - _ => None, - }; - let (hw, sw) = self.initial.get_target(); - - fn enable_str(enable: bool) -> &'static str { - if enable { - "en" - } else { - "dis" - } - } - + /// 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 sw != self.current.1 { - // Is the hw wp currently enabled? - if self.current.0 { - super::utils::toggle_hw_wp(/* dis= */ true).map_err(|e| { - format!( - "Failed to {}able hardware write protect: {}", - enable_str(false), - e - ) - })?; - } - flashrom::wp_toggle(self.cmd, /* en= */ sw).map_err(|e| { - format!( - "Failed to {}able software write protect: {}", - enable_str(sw), - e - ) - })?; - } - - assert!( - self.cmd.fc.can_control_hw_wp() || (!self.current.0 && !hw), - "HW WP must be disabled if it cannot be controlled" - ); - if hw != self.current.0 { - super::utils::toggle_hw_wp(/* dis= */ !hw).map_err(|e| { - format!( - "Failed to {}able hardware write protect: {}", - enable_str(hw), - e - ) - })?; + if self.set_sw(self.initial.sw).is_err() { + self.set_hw(false)?; + self.set_sw(self.initial.sw)?; } + self.set_hw(self.initial.hw)?; - if let Some(mut lock) = lock { - // Initial state was constructed via from_hardware, now we can clear the liveness - // lock since reset is complete. - *lock = false; - } Ok(()) } } -impl<'a, 'p> Drop for WriteProtectState<'a, 'p> { - /// Sets both write protects to the state they had when this state was created. - /// - /// Panics on error because there is no mechanism to report errors in Drop. +impl<'a> Drop for WriteProtectState<'a> { fn drop(&mut self) { - unsafe { self.drop_internal() }.expect("Error while dropping WriteProtectState") + self.drop_internal() + .expect("Error while dropping WriteProtectState") } } @@ -452,7 +346,7 @@ impl<T: TestCase + ?Sized> TestCase for &T { } #[allow(dead_code)] -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum TestConclusion { Pass, Fail, @@ -463,6 +357,7 @@ pub enum TestConclusion { pub struct ReportMetaData { pub chip_name: String, pub os_release: String, + pub cros_release: String, pub system_info: String, pub bios_info: String, } @@ -477,17 +372,38 @@ fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, } } +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: &FlashromCmd, + 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).expect("Failed to set up test environment"); + 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 { @@ -504,7 +420,7 @@ where results } -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum OutputFormat { Pretty, Json, @@ -533,6 +449,11 @@ pub fn collate_all_test_runs( ) { match format { OutputFormat::Pretty => { + let color = if atty::is(atty::Stream::Stdout) { + types::COLOR + } else { + types::NOCOLOR + }; println!(); println!(" ============================="); println!(" ===== AVL qual RESULTS ===="); @@ -540,6 +461,7 @@ pub fn collate_all_test_runs( 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); @@ -551,8 +473,8 @@ pub fn collate_all_test_runs( if *result != TestConclusion::Pass { println!( " {} {}", - style!(format!(" <+> {} test:", name), types::BOLD), - style_dbg!(result, types::RED) + style!(format!(" <+> {} test:", name), color.bold, color), + style_dbg!(result, color.red, color) ); match error { None => {} @@ -561,8 +483,8 @@ pub fn collate_all_test_runs( } else { println!( " {} {}", - style!(format!(" <+> {} test:", name), types::BOLD), - style_dbg!(result, types::GREEN) + style!(format!(" <+> {} test:", name), color.bold, color), + style_dbg!(result, color.green, color) ); } } diff --git a/util/flashrom_tester/src/tests.rs b/util/flashrom_tester/src/tests.rs index 9ef98e5c7..721a789d5 100644 --- a/util/flashrom_tester/src/tests.rs +++ b/util/flashrom_tester/src/tests.rs @@ -36,13 +36,15 @@ use super::cros_sysinfo; use super::tester::{self, OutputFormat, TestCase, TestEnv, TestResult}; use super::utils::{self, LayoutNames}; -use flashrom::{FlashChip, Flashrom, FlashromCmd}; +use flashrom::{FlashChip, Flashrom}; use std::collections::{HashMap, HashSet}; -use std::fs::File; -use std::io::{BufRead, Write}; +use std::convert::TryInto; +use std::fs::{self, File}; +use std::io::BufRead; use std::sync::atomic::AtomicBool; -const LAYOUT_FILE: &'static str = "/tmp/layout.file"; +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. /// @@ -77,50 +79,29 @@ fn filter_tests<'n, 't: 'n, T: TestCase>( /// 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>>( - path: &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>> { - let p = path.to_string(); - let cmd = FlashromCmd { path: p, fc }; - utils::ac_power_warning(); - info!("Calculate ROM partition sizes & Create the layout file."); - let rom_sz: i64 = cmd.get_size()?; - let layout_sizes = utils::get_layout_sizes(rom_sz)?; - { - let mut f = File::create(LAYOUT_FILE)?; - let mut buf: Vec<u8> = vec![]; - utils::construct_layout_file(&mut buf, &layout_sizes)?; - - f.write_all(&buf)?; - if print_layout { - info!( - "Dumping layout file as requested:\n{}", - String::from_utf8_lossy(&buf) - ); - } - } - - info!( - "Record crossystem information.\n{}", - utils::collect_crosssystem()? - ); + 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), - &("Toggle_WP", wp_toggle_test), + &("WP_Region_List", wp_region_list_test), &("Erase_and_Write", erase_write_test), &("Fail_to_verify", verify_fail_test), - &("Lock", lock_test), + &("HWWP_Locks_SWWP", hwwp_locks_swwp_test), &("Lock_top_quad", partial_lock_test(LayoutNames::TopQuad)), &( "Lock_bottom_quad", @@ -135,59 +116,60 @@ pub fn generic<'a, TN: Iterator<Item = &'a str>>( // Limit the tests to only those requested, unless none are requested // in which case all tests are included. - let mut filter_names: Option<HashSet<String>> = if let Some(names) = test_names { - Some(names.map(|s| s.to_lowercase()).collect()) - } else { - None - }; + let 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); + 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 chip_name = flashrom::name(&cmd) - .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1)) - .unwrap_or("<Unknown chip>".into()); - let os_rel = sys_info::os_release().unwrap_or("<Unknown OS>".to_string()); + let 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: chip_name, - os_release: os_rel, - system_info: system_info, - bios_info: bios_info, + 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 { - // Success means we got something back, which is good enough. - flashrom::name(env.cmd)?; + env.cmd.name()?; Ok(()) } -fn wp_toggle_test(env: &mut TestEnv) -> TestResult { - // NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms. - // However, we will warn when it does fail. - // List the write-protected regions of flash. - match flashrom::wp_list(env.cmd) { +/// 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), }; - // Fails if unable to set either one - env.wp.set_hw(false)?; - env.wp.set_sw(false)?; 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"); @@ -214,47 +196,53 @@ fn erase_write_test(env: &mut TestEnv) -> TestResult { Ok(()) } -fn lock_test(env: &mut TestEnv) -> TestResult { +/// 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 is off. - // Then enable again for another go. - env.wp.push().set_sw(false)?; + // 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)?; - // Clearing should fail when hardware is enabled if env.wp.set_sw(false).is_ok() { return Err("Software WP was reset despite hardware WP being enabled".into()); } Ok(()) } +/// 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 { - // Check that the elog contains *something*, as an indication that Coreboot - // is actually able to write to the Flash. Because this invokes mosys on the - // host, it doesn't make sense to run for other chips. if env.chip_type() != FlashChip::HOST { info!("Skipping ELOG sanity check for non-host chip"); return Ok(()); } - // mosys reads the flash, it should be back in the golden state + // flash should be back in the golden state env.ensure_golden()?; - // Output is one event per line, drop empty lines in the interest of being defensive. - let event_count = cros_sysinfo::eventlog_list()? - .lines() - .filter(|l| !l.is_empty()) - .count(); - - if event_count == 0 { - Err("ELOG contained no events".into()) - } else { - Ok(()) + + 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); @@ -280,24 +268,27 @@ fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult { } } +/// 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 (name, start, len) = utils::layout_section(env.layout(), section); - // Disable software WP so we can do range protection, but hardware WP - // must remain enabled for (most) range protection to do anything. - env.wp.set_hw(false)?.set_sw(false)?; - flashrom::wp_range(env.cmd, (start, len), true)?; + 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)?; - let rws = flashrom::ROMWriteSpecifics { - layout_file: Some(LAYOUT_FILE), - write_file: Some(env.random_data_file()), - name_file: Some(name), - }; - if flashrom::write_file_with_layout(env.cmd, &rws).is_ok() { + // 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(), @@ -306,12 +297,32 @@ fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResul 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 { - // Comparing the flash contents to random data says they're not the same. + 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(()), diff --git a/util/flashrom_tester/src/types.rs b/util/flashrom_tester/src/types.rs index b22ded2b4..9cefb27e9 100644 --- a/util/flashrom_tester/src/types.rs +++ b/util/flashrom_tester/src/types.rs @@ -33,21 +33,40 @@ // Software Foundation. // -pub const BOLD: &str = "\x1b[1m"; +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 RESET: &str = "\x1b[0m"; -pub const MAGENTA: &str = "\x1b[35m"; -pub const YELLOW: &str = "\x1b[33m"; -pub const GREEN: &str = "\x1b[92m"; -pub const RED: &str = "\x1b[31m"; +pub const NOCOLOR: Color = Color { + bold: "", + reset: "", + magenta: "", + yellow: "", + green: "", + red: "", +}; macro_rules! style_dbg { - ($s: expr, $c: expr) => { - format!("{}{:?}{}", $c, $s, types::RESET) + ($s: expr, $c: expr, $col: expr) => { + format!("{}{:?}{}", $c, $s, $col.reset) }; } macro_rules! style { - ($s: expr, $c: expr) => { - format!("{}{}{}", $c, $s, types::RESET) + ($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 index d17480bcb..4e8dd7cce 100644 --- a/util/flashrom_tester/src/utils.rs +++ b/util/flashrom_tester/src/utils.rs @@ -44,6 +44,18 @@ pub enum LayoutNames { 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, @@ -88,20 +100,20 @@ pub fn construct_layout_file<F: Write>(mut target: F, ls: &LayoutSizes) -> std:: } pub fn toggle_hw_wp(dis: bool) -> Result<(), String> { - // The easist way to toggle the harware write-protect is + // 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 { "" }; - info!("Prompt for hardware WP {}able", s); - eprintln!(" > {}connect the battery (and/or open the WP screw)", s); - pause(); - let wp = get_hardware_wp()?; - if wp && dis { - eprintln!("Hardware write protect is still ENABLED!"); - return toggle_hw_wp(dis); - } - if !wp && !dis { - eprintln!("Hardware write protect is still DISABLED!"); - return toggle_hw_wp(dis); + // 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(()) } @@ -114,21 +126,36 @@ pub fn ac_power_warning() { } fn pause() { - let mut stdout = std::io::stdout(); - // We want the cursor to stay at the end of the line, so we print without a newline - // and flush manually. - stdout.write(b"Press any key to continue...").unwrap(); - stdout.flush().unwrap(); - std::io::stdin().read(&mut [0]).unwrap(); + // 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) = parse_crosssystem(&collect_crosssystem()?)?; - Ok(wp) + 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() -> Result<String, String> { - let cmd = match Command::new("crossystem").output() { +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)), }; @@ -140,39 +167,6 @@ pub fn collect_crosssystem() -> Result<String, String> { Ok(String::from_utf8_lossy(&cmd.stdout).into_owned()) } -fn parse_crosssystem(s: &str) -> Result<(Vec<&str>, bool), &'static str> { - // grep -v 'fwid +=' | grep -v 'hwid +=' - let sysinfo = s - .split_terminator("\n") - .filter(|s| !s.contains("fwid +=") && !s.contains("hwid +=")); - - let state_line = match sysinfo.clone().filter(|s| s.starts_with("wpsw_cur")).next() { - None => return Err("No wpsw_cur in system info"), - Some(line) => line, - }; - let wp_s_val = state_line - .trim_start_matches("wpsw_cur") - .trim_start_matches(' ') - .trim_start_matches('=') - .trim_start_matches(' ') - .get(..1) - .unwrap() - .parse::<u32>(); - - match wp_s_val { - Ok(v) => { - if v == 1 { - return Ok((sysinfo.collect(), true)); - } else if v == 0 { - return Ok((sysinfo.collect(), false)); - } else { - return Err("Unknown state value"); - } - } - Err(_) => return Err("Cannot parse state value"), - } -} - pub fn translate_command_error(output: &std::process::Output) -> std::io::Error { use std::io::{Error, ErrorKind}; // There is two cases on failure; @@ -244,55 +238,4 @@ mod tests { } ); } - - #[test] - fn parse_crosssystem() { - use super::parse_crosssystem; - - assert_eq!( - parse_crosssystem("This is not the tool you are looking for").err(), - Some("No wpsw_cur in system info") - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = ERROR").err(), - Some("Cannot parse state value") - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = 3").err(), - Some("Unknown state value") - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = 0"), - Ok((vec!["wpsw_cur = 0"], false)) - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = 1"), - Ok((vec!["wpsw_cur = 1"], true)) - ); - - assert_eq!( - parse_crosssystem("wpsw_cur=1"), - Ok((vec!["wpsw_cur=1"], true)) - ); - - assert_eq!( - parse_crosssystem( - "fwid += 123wpsw_cur\n\ - hwid += aaaaa\n\ - wpsw_boot = 0 # [RO/int]\n\ - wpsw_cur = 1 # [RO/int]\n" - ), - Ok(( - vec![ - "wpsw_boot = 0 # [RO/int]", - "wpsw_cur = 1 # [RO/int]" - ], - true - )) - ); - } } diff --git a/util/z60_flashrom.rules b/util/flashrom_udev.rules index 59de68be7..9fd333082 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 deleted file mode 100755 index f7d6bf3b2..000000000 --- a/util/getrevision.sh +++ /dev/null @@ -1,238 +0,0 @@ -#!/bin/sh -# NB: Supposed to be POSIX compatible but at least the usage of 'local' is not. -# -# This file is part of the flashrom project. -# -# Copyright (C) 2005 coresystems GmbH <stepan@coresystems.de> -# Copyright (C) 2009,2010 Carl-Daniel Hailfinger -# Copyright (C) 2010 Chromium OS Authors -# Copyright (C) 2013-2016 Stefan Tauner -# -# 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. -# - -EXIT_SUCCESS=0 -EXIT_FAILURE=1 - -# Make sure we don't get translated output -export LC_ALL=C -# nor local times or dates -export TZ=UTC0 - -# Helper functions -# First argument is the path to inspect (usually optional; without -# it the whole repository will be considered) -git_has_local_changes() { - git update-index -q --refresh >/dev/null - ! git diff-index --quiet HEAD -- "$1" -} - -git_last_commit() { - # git rev-parse --short HEAD would suffice if repository as a whole is of interest (no $1) - git log --pretty=format:"%h" -1 -- "$1" -} - -git_is_file_tracked() { - git ls-files --error-unmatch -- "$1" >/dev/null 2>&1 -} - -is_file_tracked() { - git_is_file_tracked "$1" -} - -# Tries to find a remote source for the changes committed locally. -# This includes the URL of the remote repository including the last commit and a suitable branch name. -# Takes one optional argument: the path to inspect -git_url() { - last_commit=$(git_last_commit "$1") - # get all remote branches containing the last commit (excluding origin/HEAD) - branches=$(git branch -r --contains $last_commit | sed '/\//!d;/.*->.*/d;s/[\t ]*//') - if [ -z "$branches" ] ; then - echo "No remote branch contains a suitable commit">&2 - return - fi - - # find "nearest" branch - local mindiff=9000 - local target= - for branch in $branches ; do - curdiff=$(git rev-list --count $last_commit..$branch) - if [ $curdiff -ge $mindiff ] ; then - continue - fi - mindiff=$curdiff - target=$branch - done - - echo "$(git ls-remote --exit-code --get-url ${target%/*}) ${target#*/}" -} - -# Returns a string indicating where others can get the current source code (excluding uncommitted changes). -# Takes one optional argument: the path to inspect -scm_url() { - local url= - - if git_is_file_tracked "$1" ; then - url="$(git_url "$1")" - else - return ${EXIT_FAILURE} - fi - - echo "${url}" -} - -# Retrieve timestamp since last modification. If the sources are pristine, -# then the timestamp will match that of the SCM's most recent modification -# date. -timestamp() { - local t - - # date syntaxes are manifold: - # gnu date [-d input]... [+FORMAT] - # netbsd date [-ajnu] [-d date] [-r seconds] [+format] [[[[[[CC]yy]mm]dd]HH]MM[.SS]] - # freebsd date [-jnu] [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...] - # dragonflybsd date [-jnu] [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...] - # openbsd date [-aju] [-d dst] [-r seconds] [+format] [[[[[[cc]yy]mm]dd]HH]MM[.SS]] [...] - if git_is_file_tracked "$2" ; then - # are there local changes? - if git_has_local_changes "$2" ; then - t=$(date -u "${1}") - else - # No local changes, get date of the last commit - case $(uname) in - # Most BSD dates do not support parsing date values from user input with -d but all of - # them support parsing epoch seconds with -r. Thanks to git we can easily use that: - NetBSD|OpenBSD|DragonFly|FreeBSD) - t=$(date -u -r "$(git log --pretty=format:%ct -1 -- $2)" "$1" 2>/dev/null);; - *) - t=$(date -d "$(git log --pretty=format:%cD -1 -- $2)" -u "$1" 2>/dev/null);; - esac - fi - else - t=$(date -u "$1") - fi - - if [ -z "$t" ]; then - echo "Warning: Could not determine timestamp." 2>/dev/null - fi - echo "${t}" -} - -revision() { - local r - if git_is_file_tracked "$1" ; then - r=$(git describe --always $(git_last_commit "$1")) - if git_has_local_changes "$1" ; then - r="$r-dirty" - fi - else - r="unknown" - fi - - echo "${r}" -} - -is_tracked() { - is_file_tracked "$1" -} - -show_help() { - echo "Usage: - ${0} <command> [path] - -Commands - -h or --help - this message - -c or --check - test if path is under version control at all - -r or --revision - return unique revision information including an indicator for - uncommitted changes - -U or --url - URL associated with the latest commit - -d or --date - date of most recent modification - -t or --timestamp - timestamp of most recent modification -" - return -} - -check_action() { - if [ -n "$action" ]; then - echo "Error: Multiple actions given.">&2 - exit ${EXIT_FAILURE} - fi -} - -main() { - local query_path= - local action= - - # Argument parser loop - while [ $# -gt 0 ]; - do - case ${1} in - -h|--help) - action=show_help; - shift;; - -U|--url) - check_action $1 - action=scm_url - shift;; - -d|--date) - check_action $1 - action="timestamp +%Y-%m-%d" # refrain from suffixing 'Z' to indicate it's UTC - shift;; - -r|--revision) - check_action $1 - action=revision - shift;; - -t|--timestamp) - check_action $1 - action="timestamp +%Y-%m-%dT%H:%M:%SZ" # There is only one valid time format! ISO 8601 - shift;; - -c|--check) - check_action $1 - action=is_tracked - shift;; - -*) - show_help; - echo "Error: Invalid option: ${1}" - exit ${EXIT_FAILURE};; - *) - if [ -z "$query_path" ] ; then - if [ ! -e "$1" ] ; then - echo "Error: Path \"${1}\" does not exist.">&2 - exit ${EXIT_FAILURE} - fi - query_path=$1 - else - echo "Warning: Ignoring overabundant parameter: \"${1}\"">&2 - fi - shift;; - esac; - done - - # default to current directory (usually equals the whole repository) - if [ -z "$query_path" ] ; then - query_path=. - fi - if [ -z "$action" ] ; then - show_help - echo "Error: No actions specified" - exit ${EXIT_FAILURE} - fi - - $action "$query_path" -} - -main $@ diff --git a/util/getversion.sh b/util/getversion.sh deleted file mode 100755 index d3810d29b..000000000 --- a/util/getversion.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/sh -# -# This file is part of the flashrom project. -# -# 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. -# - -version() { - if [ -r versioninfo.inc ]; then - v=$(sed -n 's/^VERSION = //p' versioninfo.inc) - else - v=$($(dirname ${0})/getrevision.sh --revision) - if [ $? -ne 0 ]; then - v='unknown' - fi - fi - - echo ${v} -} - -mandate() { - if [ -r versioninfo.inc ]; then - d=$(sed -n 's/^MAN_DATE = //p' versioninfo.inc) - else - d=$($(dirname ${0})/getrevision.sh --date flashrom.8.tmpl) - if [ $? -ne 0 ]; then - d='unknown' - fi - fi - - echo ${d} -} - -show_help() { - echo "Usage: - ${0} <command> - -Commands - -h or --help - this message - -v or --version - return current/release flashrom version - -m or --man-date - return current/release date of the manual page -" -} - -if [ $# -ne 1 ]; then - show_help - echo "Error: Only exactly one command allowed.">&2 - exit 1 -fi - -case $1 in - -h|--help) show_help;; - -m|--man-date) mandate;; - -v|--version) version;; - *) - show_help - echo "Error: Invalid option: $1" - exit 1 - ;; -esac diff --git a/util/git-hooks/commit-msg b/util/git-hooks/commit-msg index d43eb4529..e2f5de512 100755 --- a/util/git-hooks/commit-msg +++ b/util/git-hooks/commit-msg @@ -196,6 +196,7 @@ _gen_ChangeId() { test_signoff() { if ! grep -qi '^[[:space:]]*\+Signed-off-by:' "$MSG"; then + cat "$MSG" printf "\nError: No Signed-off-by line in the commit message.\n" printf "See: ${DEV_GUIDELINES_URL}\n" exit 1 @@ -206,6 +207,7 @@ test_signoff() { test_duplicate_signoffs_acks() { test "" = "$(grep -i '^(Signed-off-by|Acked-by): ' "$MSG" | sort | uniq -c | sed -e '/^[[:space:]]*1[[:space:]]/d')" || { + cat "$MSG" echo "Duplicate Signed-off-by or Acked-by lines." >&2 exit 1 } diff --git a/util/ich_descriptors_tool/Makefile b/util/ich_descriptors_tool/Makefile index c32e30be5..aa1b696c3 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 32eea12c9..a5a59ad29 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]); @@ -228,8 +233,21 @@ int main(int argc, char *argv[]) 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); @@ -251,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 b5bf09ec9..80f4a521b 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 000000000..7abdab861 --- /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 000000000..ef62a45a2 --- /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 000000000..674b54512 --- /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 000000000..52befce00 --- /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 000000000..aa28fc488 --- /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 000000000..c9af0bf3a --- /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 000000000..7bd5c7bd6 --- /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 000000000..79a939a16 --- /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 000000000..b6de7eb26 --- /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 000000000..62cc4615b --- /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 000000000..98ed30c96 --- /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 000000000..ba8c82d64 --- /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 000000000..dff38797d --- /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 000000000..b00d61818 --- /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 000000000..3ff9ee17b --- /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 000000000..c3f583401 --- /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 24278d73f..000000000 --- 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 000000000..987777c35 --- /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 e64daf7c3..009194d45 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 |