summaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/docker/flashrom.org/Dockerfile29
-rw-r--r--util/docker/flashrom.org/README.md32
-rwxr-xr-xutil/docker/flashrom.org/ditaa.sh2
-rwxr-xr-xutil/docker/flashrom.org/makeSphinx.sh12
-rw-r--r--util/flashrom.bash-completion.tmpl77
-rw-r--r--util/flashrom_tester/.cargo/config.toml2
-rw-r--r--util/flashrom_tester/.gitignore2
-rw-r--r--util/flashrom_tester/Cargo.toml39
-rw-r--r--util/flashrom_tester/build.rs5
-rw-r--r--util/flashrom_tester/flashrom/Cargo.toml12
-rw-r--r--util/flashrom_tester/flashrom/src/cmd.rs590
-rw-r--r--util/flashrom_tester/flashrom/src/flashromlib.rs184
-rw-r--r--util/flashrom_tester/flashrom/src/lib.rs165
-rwxr-xr-xutil/flashrom_tester/flashrom_remote.sh2
-rw-r--r--util/flashrom_tester/src/cros_sysinfo.rs70
-rw-r--r--util/flashrom_tester/src/lib.rs46
-rw-r--r--util/flashrom_tester/src/logger.rs149
-rw-r--r--util/flashrom_tester/src/main.rs211
-rw-r--r--util/flashrom_tester/src/rand_util.rs80
-rw-r--r--util/flashrom_tester/src/tester.rs567
-rw-r--r--util/flashrom_tester/src/tests.rs398
-rw-r--r--util/flashrom_tester/src/types.rs72
-rw-r--r--util/flashrom_tester/src/utils.rs241
-rw-r--r--util/flashrom_udev.rules (renamed from util/z60_flashrom.rules)4
-rwxr-xr-xutil/getrevision.sh238
-rwxr-xr-xutil/getversion.sh71
-rwxr-xr-xutil/git-hooks/commit-msg2
-rw-r--r--util/ich_descriptors_tool/Makefile23
-rw-r--r--util/ich_descriptors_tool/ich_descriptors_tool.c30
-rw-r--r--util/ich_descriptors_tool/meson.build5
-rw-r--r--util/lint/helper_functions.sh45
-rwxr-xr-xutil/lint/lint-extended-020-signed-off-by23
-rw-r--r--util/manibuilder/Dockerfile.alpine24
-rw-r--r--util/manibuilder/Dockerfile.anita65
-rw-r--r--util/manibuilder/Dockerfile.centos18
-rw-r--r--util/manibuilder/Dockerfile.debian-debootstrap21
-rw-r--r--util/manibuilder/Dockerfile.djgpp30
-rw-r--r--util/manibuilder/Dockerfile.fedora19
-rw-r--r--util/manibuilder/Dockerfile.qemu-user-static3
-rw-r--r--util/manibuilder/Dockerfile.ubuntu-debootstrap34
-rw-r--r--util/manibuilder/Makefile93
-rw-r--r--util/manibuilder/Makefile.anita52
-rw-r--r--util/manibuilder/Makefile.targets223
-rw-r--r--util/manibuilder/README.md72
-rw-r--r--util/manibuilder/anita-wrapper.sh18
-rw-r--r--util/manibuilder/mani-wrapper.sh9
-rw-r--r--util/meson.build1
-rw-r--r--util/shell.nix18
-rw-r--r--util/ubertest/README.md175
-rw-r--r--util/ubertest/cmd.sh137
-rw-r--r--util/ubertest/drawing_external_programmer_external_rom.svg172
-rw-r--r--util/ubertest/drawing_external_programmer_local_dut.svg166
-rw-r--r--util/ubertest/drawing_external_programmer_remote_dut.svg202
-rw-r--r--util/ubertest/drawing_local_programmer_is_dut.svg126
-rw-r--r--util/ubertest/drawing_remote_host_is_remote_dut.svg173
-rwxr-xr-xutil/ubertest/ubertest.sh935
56 files changed, 5887 insertions, 327 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/.gitignore b/util/flashrom_tester/.gitignore
new file mode 100644
index 000000000..1e7caa9ea
--- /dev/null
+++ b/util/flashrom_tester/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target/
diff --git a/util/flashrom_tester/Cargo.toml b/util/flashrom_tester/Cargo.toml
new file mode 100644
index 000000000..e6ed9c044
--- /dev/null
+++ b/util/flashrom_tester/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "flashrom_tester"
+version = "1.6.0"
+authors = ["Edward O'Callaghan <quasisec@chromium.org>",
+ "Peter Marheine <pmarheine@chromium.org>"]
+description = "A tool to verify flashrom and flash chip behaviour."
+license = "GPL-2.0-only"
+edition = "2018"
+build = "build.rs"
+
+[lib]
+name = "flashrom_tester"
+
+[[bin]]
+name = "flashrom_tester"
+required-features = ["cli"]
+
+[dependencies]
+atty = "0.2"
+built = { version = "0.5", features = ["chrono"] }
+chrono = { version = "0.4", optional = true }
+clap = { version = "2.33", default-features = false, optional = true }
+flashrom = { path = "flashrom/" }
+libc = "0.2"
+log = { version = "0.4", features = ["std"] }
+rand = "0.6.4"
+serde_json = "1"
+sys-info = "0.9"
+
+[build-dependencies]
+built = { version = "0.5", features = ["chrono"] }
+
+[dev-dependencies]
+gag = "1"
+
+[features]
+# Features required to build the CLI binary but not the library
+cli = ["chrono", "clap"]
+default = ["cli"]
diff --git a/util/flashrom_tester/build.rs b/util/flashrom_tester/build.rs
new file mode 100644
index 000000000..3800c173e
--- /dev/null
+++ b/util/flashrom_tester/build.rs
@@ -0,0 +1,5 @@
+extern crate built;
+
+fn main() {
+ built::write_built_file().expect("Failed to acquire build-time information");
+}
diff --git a/util/flashrom_tester/flashrom/Cargo.toml b/util/flashrom_tester/flashrom/Cargo.toml
new file mode 100644
index 000000000..4d4fc2fea
--- /dev/null
+++ b/util/flashrom_tester/flashrom/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "flashrom"
+version = "1.0.0"
+authors = ["Edward O'Callaghan <quasisec@chromium.org>",
+ "Peter Marheine <pmarheine@chromium.org>"]
+description = "Flashrom abstraction for the flashrom_tester tool."
+license = "GPL-2.0-only"
+edition = "2018"
+
+[dependencies]
+log = "0.4"
+libflashrom = { path = "../../../bindings/rust/libflashrom" }
diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs
new file mode 100644
index 000000000..1f13a8ede
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/cmd.rs
@@ -0,0 +1,590 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use crate::{FlashChip, FlashromError};
+
+use std::{
+ ffi::{OsStr, OsString},
+ path::Path,
+ process::Command,
+};
+
+#[derive(Default)]
+pub struct FlashromOpt<'a> {
+ pub wp_opt: WPOpt,
+ pub io_opt: Option<IOOpt<'a>>,
+
+ pub flash_name: bool, // --flash-name
+ pub verbose: bool, // -V
+}
+
+#[derive(Default)]
+pub struct WPOpt {
+ pub range: Option<(i64, i64)>, // --wp-range x0 x1
+ pub status: bool, // --wp-status
+ pub list: bool, // --wp-list
+ pub enable: bool, // --wp-enable
+ pub disable: bool, // --wp-disable
+}
+
+pub enum OperationArgs<'a> {
+ /// The file is the whole chip.
+ EntireChip(&'a Path),
+ /// File is the size of the full chip, limited to a single named region.
+ ///
+ /// The required path is the file to use, and the optional path is a layout file
+ /// specifying how to locate regions (if unspecified, flashrom will attempt
+ /// to discover the layout itself).
+ FullFileRegion(&'a str, &'a Path, Option<&'a Path>),
+ /// File is the size of the single named region only.
+ ///
+ /// The required path is the file to use, and the optional path is a layout file
+ /// specifying how to locate regions (if unspecified, flashrom will attempt
+ /// to discover the layout itself).
+ RegionFileRegion(&'a str, &'a Path, Option<&'a Path>), // The file contains only the region
+}
+
+pub enum IOOpt<'a> {
+ Read(OperationArgs<'a>), // -r <file>
+ Write(OperationArgs<'a>), // -w <file>
+ Verify(OperationArgs<'a>), // -v <file>
+ Erase, // -E
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct FlashromCmd {
+ pub path: String,
+ pub fc: FlashChip,
+}
+
+/// Attempt to determine the Flash size given stdout from `flashrom --flash-size`
+fn flashrom_extract_size(stdout: &str) -> Result<i64, FlashromError> {
+ // Search for the last line of output that contains only digits, assuming
+ // that's the actual size. flashrom sadly tends to write additional messages
+ // to stdout.
+ match stdout
+ .lines()
+ .filter(|line| line.chars().all(|c| c.is_ascii_digit()))
+ .last()
+ .map(str::parse::<i64>)
+ {
+ None => Err("Found no purely-numeric lines in flashrom output".into()),
+ Some(Err(e)) => {
+ Err(format!("Failed to parse flashrom size output as integer: {}", e).into())
+ }
+ Some(Ok(sz)) => Ok(sz),
+ }
+}
+
+impl FlashromCmd {
+ fn dispatch(
+ &self,
+ fropt: FlashromOpt,
+ debug_name: &str,
+ ) -> Result<(String, String), FlashromError> {
+ let params = flashrom_decode_opts(fropt);
+ flashrom_dispatch(self.path.as_str(), &params, self.fc, debug_name)
+ }
+}
+
+impl crate::Flashrom for FlashromCmd {
+ fn get_size(&self) -> Result<i64, FlashromError> {
+ let (stdout, _) =
+ flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc, "get_size")?;
+ flashrom_extract_size(&stdout)
+ }
+
+ fn name(&self) -> Result<(String, String), FlashromError> {
+ let opts = FlashromOpt {
+ flash_name: true,
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "name")?;
+ match extract_flash_name(&stdout) {
+ None => Err("Didn't find chip vendor/name in flashrom output".into()),
+ Some((vendor, name)) => Ok((vendor.into(), name.into())),
+ }
+ }
+
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Write(OperationArgs::FullFileRegion(
+ region,
+ path,
+ Some(layout),
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "write_file_with_layout")?;
+ Ok(true)
+ }
+
+ fn wp_range(&self, range: (i64, i64), en: bool) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ enable: en,
+ disable: !en,
+ range: Some(range),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "wp_range")?;
+ Ok(true)
+ }
+
+ fn wp_list(&self) -> Result<String, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ list: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "wp_list")?;
+ if stdout.is_empty() {
+ return Err(
+ "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list"
+ .into(),
+ );
+ }
+ Ok(stdout)
+ }
+
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError> {
+ let status = if en { "en" } else { "dis" };
+ info!("See if chip write protect is {}abled", status);
+
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ status: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = self.dispatch(opts, "wp_status")?;
+ let s = std::format!("write protect is {}abled", status);
+ Ok(stdout.contains(&s))
+ }
+
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError> {
+ let range = if en {
+ let rom_sz: i64 = self.get_size()?;
+ (0, rom_sz) // (start, len)
+ } else {
+ (0, 0)
+ };
+ self.wp_range(range, en)?;
+ let status = if en { "en" } else { "dis" };
+ match self.wp_status(true) {
+ Ok(_ret) => {
+ info!("Successfully {}abled write-protect", status);
+ Ok(true)
+ }
+ Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e).into()),
+ }
+ }
+
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Read(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "read_into_file")?;
+ Ok(())
+ }
+
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Read(OperationArgs::RegionFileRegion(
+ region, path, None,
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "read_region_into_file")?;
+ Ok(())
+ }
+
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Write(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "write_from_file")?;
+ Ok(())
+ }
+
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Verify(OperationArgs::EntireChip(path))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "verify_from_file")?;
+ Ok(())
+ }
+
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Verify(OperationArgs::RegionFileRegion(
+ region, path, None,
+ ))),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "verify_region_from_file")?;
+ Ok(())
+ }
+
+ fn erase(&self) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: Some(IOOpt::Erase),
+ ..Default::default()
+ };
+
+ self.dispatch(opts, "erase")?;
+ Ok(())
+ }
+
+ fn can_control_hw_wp(&self) -> bool {
+ self.fc.can_control_hw_wp()
+ }
+}
+
+fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<OsString> {
+ let mut params = Vec::<OsString>::new();
+
+ // ------------ WARNING !!! ------------
+ // each param must NOT contain spaces!
+ // -------------------------------------
+
+ // wp_opt
+ if opts.wp_opt.range.is_some() {
+ let (x0, x1) = opts.wp_opt.range.unwrap();
+ params.push("--wp-range".into());
+ params.push(hex_range_string(x0, x1).into());
+ }
+ if opts.wp_opt.status {
+ params.push("--wp-status".into());
+ } else if opts.wp_opt.list {
+ params.push("--wp-list".into());
+ } else if opts.wp_opt.enable {
+ params.push("--wp-enable".into());
+ } else if opts.wp_opt.disable {
+ params.push("--wp-disable".into());
+ }
+
+ // io_opt
+ fn add_operation_args(opts: OperationArgs, params: &mut Vec<OsString>) {
+ let (file, region, layout) = match opts {
+ OperationArgs::EntireChip(file) => (Some(file), None, None),
+ OperationArgs::FullFileRegion(region, file, layout) => {
+ (Some(file), Some(region.to_string()), layout)
+ }
+ OperationArgs::RegionFileRegion(region, file, layout) => (
+ None,
+ Some(format!("{region}:{}", file.to_string_lossy())),
+ layout,
+ ),
+ };
+ if let Some(file) = file {
+ params.push(file.into())
+ }
+ if let Some(region) = region {
+ params.push("--include".into());
+ params.push(region.into())
+ }
+ if let Some(layout) = layout {
+ params.push("--layout".into());
+ params.push(layout.into())
+ }
+ }
+ if let Some(io) = opts.io_opt {
+ match io {
+ IOOpt::Read(args) => {
+ params.push("-r".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Write(args) => {
+ params.push("-w".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Verify(args) => {
+ params.push("-v".into());
+ add_operation_args(args, &mut params);
+ }
+ IOOpt::Erase => params.push("-E".into()),
+ }
+ }
+
+ // misc_opt
+ if opts.flash_name {
+ params.push("--flash-name".into());
+ }
+ if opts.verbose {
+ params.push("-V".into());
+ }
+
+ params
+}
+
+fn flashrom_dispatch<S: AsRef<OsStr>>(
+ path: &str,
+ params: &[S],
+ fc: FlashChip,
+ debug_name: &str,
+) -> Result<(String, String), FlashromError> {
+ // from man page:
+ // ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] '
+ let mut args: Vec<&OsStr> = vec![OsStr::new("-p"), OsStr::new(FlashChip::to(fc))];
+ args.extend(params.iter().map(S::as_ref));
+
+ info!("flashrom_dispatch() running: {} {:?}", path, args);
+
+ let output = match Command::new(path).args(&args).output() {
+ Ok(x) => x,
+ Err(e) => return Err(format!("Failed to run flashrom: {}", e).into()),
+ };
+
+ let stdout = String::from_utf8_lossy(output.stdout.as_slice());
+ let stderr = String::from_utf8_lossy(output.stderr.as_slice());
+ debug!("{}()'stdout: {}.", debug_name, stdout);
+ debug!("{}()'stderr: {}.", debug_name, stderr);
+
+ if !output.status.success() {
+ // There is two cases on failure;
+ // i. ) A bad exit code,
+ // ii.) A SIG killed us.
+ match output.status.code() {
+ Some(code) => {
+ return Err(format!("{}\nExited with error code: {}", stderr, code).into());
+ }
+ None => return Err("Process terminated by a signal".into()),
+ }
+ }
+
+ Ok((stdout.into(), stderr.into()))
+}
+
+fn hex_range_string(s: i64, l: i64) -> String {
+ format!("{:#08X},{:#08X}", s, l)
+}
+
+/// Get a flash vendor and name from the first matching line of flashrom output.
+///
+/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name.
+/// This is usually the last line of output.
+fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> {
+ for line in stdout.lines() {
+ if !line.starts_with("vendor=\"") {
+ continue;
+ }
+
+ let tail = line.trim_start_matches("vendor=\"");
+ let mut split = tail.splitn(2, "\" name=\"");
+ let vendor = split.next();
+ let name = split.next().map(|s| s.trim_end_matches('"'));
+
+ match (vendor, name) {
+ (Some(v), Some(n)) => return Some((v, n)),
+ _ => continue,
+ }
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::Path;
+
+ use super::flashrom_decode_opts;
+ use super::{FlashromOpt, IOOpt, WPOpt};
+
+ #[test]
+ fn decode_wp_opt() {
+ fn test_wp_opt(wpo: WPOpt, expected: &[&str]) {
+ assert_eq!(
+ flashrom_decode_opts(FlashromOpt {
+ wp_opt: wpo,
+ ..Default::default()
+ }),
+ expected
+ );
+ }
+
+ test_wp_opt(Default::default(), &[]);
+ test_wp_opt(
+ WPOpt {
+ range: Some((0, 1234)),
+ status: true,
+ ..Default::default()
+ },
+ &["--wp-range", "0x000000,0x0004D2", "--wp-status"],
+ );
+ test_wp_opt(
+ WPOpt {
+ list: true,
+ ..Default::default()
+ },
+ &["--wp-list"],
+ );
+ test_wp_opt(
+ WPOpt {
+ enable: true,
+ ..Default::default()
+ },
+ &["--wp-enable"],
+ );
+ test_wp_opt(
+ WPOpt {
+ disable: true,
+ ..Default::default()
+ },
+ &["--wp-disable"],
+ );
+ }
+
+ #[test]
+ fn decode_io_opt() {
+ fn test_io_opt(opts: IOOpt, expected: &[&str]) {
+ assert_eq!(
+ flashrom_decode_opts(FlashromOpt {
+ io_opt: Some(opts),
+ ..Default::default()
+ }),
+ expected
+ );
+ }
+
+ test_io_opt(
+ IOOpt::Read(crate::cmd::OperationArgs::EntireChip(Path::new("foo.bin"))),
+ &["-r", "foo.bin"],
+ );
+ test_io_opt(
+ IOOpt::Write(crate::cmd::OperationArgs::EntireChip(Path::new("bar.bin"))),
+ &["-w", "bar.bin"],
+ );
+ test_io_opt(
+ IOOpt::Verify(crate::cmd::OperationArgs::EntireChip(Path::new("baz.bin"))),
+ &["-v", "baz.bin"],
+ );
+ test_io_opt(IOOpt::Erase, &["-E"]);
+ test_io_opt(
+ IOOpt::Read(crate::cmd::OperationArgs::FullFileRegion(
+ "RO",
+ Path::new("foo.bin"),
+ Some(Path::new("baz.bin")),
+ )),
+ &["-r", "foo.bin", "--include", "RO", "--layout", "baz.bin"],
+ );
+
+ test_io_opt(
+ IOOpt::Read(crate::cmd::OperationArgs::RegionFileRegion(
+ "foo",
+ Path::new("bar.bin"),
+ None,
+ )),
+ &["-r", "--include", "foo:bar.bin"],
+ )
+ }
+
+ #[test]
+ fn decode_misc() {
+ //use Default::default;
+
+ assert_eq!(
+ flashrom_decode_opts(FlashromOpt {
+ flash_name: true,
+ verbose: true,
+ ..Default::default()
+ }),
+ &["--flash-name", "-V"]
+ );
+ }
+
+ #[test]
+ fn flashrom_extract_size() {
+ use super::flashrom_extract_size;
+
+ assert_eq!(
+ flashrom_extract_size(
+ "coreboot table found at 0x7cc13000.\n\
+ Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\
+ 8388608\n"
+ ),
+ Ok(8388608)
+ );
+
+ assert_eq!(
+ flashrom_extract_size("There was a catastrophic error."),
+ Err("Found no purely-numeric lines in flashrom output".into())
+ );
+ }
+
+ #[test]
+ fn extract_flash_name() {
+ use super::extract_flash_name;
+
+ assert_eq!(
+ extract_flash_name(
+ "coreboot table found at 0x7cc13000\n\
+ Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\
+ vendor=\"Winbond\" name=\"W25Q64DW\"\n"
+ ),
+ Some(("Winbond", "W25Q64DW"))
+ );
+
+ assert_eq!(
+ extract_flash_name(
+ "vendor name is TEST\n\
+ Something failed!"
+ ),
+ None
+ )
+ }
+}
diff --git a/util/flashrom_tester/flashrom/src/flashromlib.rs b/util/flashrom_tester/flashrom/src/flashromlib.rs
new file mode 100644
index 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(&region_data);
+ self.flashrom
+ .borrow_mut()
+ .image_verify(&buf, Some(layout))?;
+ Ok(())
+ }
+
+ fn erase(&self) -> Result<(), FlashromError> {
+ self.flashrom.borrow_mut().erase()?;
+ Ok(())
+ }
+
+ fn can_control_hw_wp(&self) -> bool {
+ self.fc.can_control_hw_wp()
+ }
+}
diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs
new file mode 100644
index 000000000..41393e841
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/lib.rs
@@ -0,0 +1,165 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+#[macro_use]
+extern crate log;
+
+mod cmd;
+mod flashromlib;
+
+use std::{error, fmt, path::Path};
+
+pub use cmd::FlashromCmd;
+pub use flashromlib::FlashromLib;
+
+pub use libflashrom::{
+ flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR,
+ FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN,
+};
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum FlashChip {
+ HOST,
+}
+
+impl FlashChip {
+ pub fn from(s: &str) -> Result<FlashChip, &str> {
+ match s {
+ "host" => Ok(FlashChip::HOST),
+ _ => Err("cannot convert str to enum"),
+ }
+ }
+ pub fn to(fc: FlashChip) -> &'static str {
+ match fc {
+ FlashChip::HOST => "host",
+ }
+ }
+
+ /// Return the programmer string and optional programmer options
+ pub fn to_split(fc: FlashChip) -> (&'static str, Option<&'static str>) {
+ let programmer = FlashChip::to(fc);
+ let mut bits = programmer.splitn(2, ':');
+ (bits.next().unwrap(), bits.next())
+ }
+
+ /// Return whether the hardware write protect signal can be controlled.
+ ///
+ /// Servo and dediprog adapters are assumed to always have hardware write protect
+ /// disabled.
+ pub fn can_control_hw_wp(&self) -> bool {
+ match self {
+ FlashChip::HOST => true,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct FlashromError {
+ msg: String,
+}
+
+impl fmt::Display for FlashromError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.msg)
+ }
+}
+
+impl error::Error for FlashromError {}
+
+impl<T> From<T> for FlashromError
+where
+ T: Into<String>,
+{
+ fn from(msg: T) -> Self {
+ FlashromError { msg: msg.into() }
+ }
+}
+
+pub trait Flashrom {
+ /// Returns the size of the flash in bytes.
+ fn get_size(&self) -> Result<i64, FlashromError>;
+
+ /// Returns the vendor name and the flash name.
+ fn name(&self) -> Result<(String, String), FlashromError>;
+
+ /// Set write protect status and range.
+ fn wp_range(&self, range: (i64, i64), wp_enable: bool) -> Result<bool, FlashromError>;
+
+ /// Read the write protect regions for the flash.
+ fn wp_list(&self) -> Result<String, FlashromError>;
+
+ /// Return true if the flash write protect status matches `en`.
+ fn wp_status(&self, en: bool) -> Result<bool, FlashromError>;
+
+ /// Set write protect status.
+ /// If en=true sets wp_range to the whole chip (0,getsize()).
+ /// If en=false sets wp_range to (0,0).
+ /// This is due to the MTD driver, which requires wp enable to use a range
+ /// length != 0 and wp disable to have the range 0,0.
+ fn wp_toggle(&self, en: bool) -> Result<bool, FlashromError>;
+
+ /// Read the whole flash to the file specified by `path`.
+ fn read_into_file(&self, path: &Path) -> Result<(), FlashromError>;
+
+ /// Read only a region of the flash into the file specified by `path`. Note
+ /// the first byte written to the file is the first byte from the region.
+ fn read_region_into_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>;
+
+ /// Write the whole flash to the file specified by `path`.
+ fn write_from_file(&self, path: &Path) -> Result<(), FlashromError>;
+
+ /// Write only a region of the flash.
+ /// `path` is a file of the size of the whole flash.
+ /// The `region` name corresponds to a region name in the `layout` file, not the flash.
+ fn write_from_file_region(
+ &self,
+ path: &Path,
+ region: &str,
+ layout: &Path,
+ ) -> Result<bool, FlashromError>;
+
+ /// Verify the whole flash against the file specified by `path`.
+ fn verify_from_file(&self, path: &Path) -> Result<(), FlashromError>;
+
+ /// Verify only the region against the file specified by `path`.
+ /// Note the first byte in the file is matched against the first byte of the region.
+ fn verify_region_from_file(&self, path: &Path, region: &str) -> Result<(), FlashromError>;
+
+ /// Erase the whole flash.
+ fn erase(&self) -> Result<(), FlashromError>;
+
+ /// Return true if the hardware write protect of this flash can be controlled.
+ fn can_control_hw_wp(&self) -> bool;
+}
diff --git a/util/flashrom_tester/flashrom_remote.sh b/util/flashrom_tester/flashrom_remote.sh
new file mode 100755
index 000000000..8dba94301
--- /dev/null
+++ b/util/flashrom_tester/flashrom_remote.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+/usr/bin/ssh localhost -p 60024 -C /usr/sbin/flashrom "$@"
diff --git a/util/flashrom_tester/src/cros_sysinfo.rs b/util/flashrom_tester/src/cros_sysinfo.rs
new file mode 100644
index 000000000..37e1ec659
--- /dev/null
+++ b/util/flashrom_tester/src/cros_sysinfo.rs
@@ -0,0 +1,70 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use std::ffi::OsStr;
+use std::fs;
+use std::io::Result as IoResult;
+use std::process::{Command, Stdio};
+
+use super::utils;
+
+fn dmidecode_dispatch<S: AsRef<OsStr>>(args: &[S]) -> IoResult<String> {
+ let output = Command::new("/usr/sbin/dmidecode")
+ .args(args)
+ .stdin(Stdio::null())
+ .output()?;
+
+ if !output.status.success() {
+ return Err(utils::translate_command_error(&output));
+ }
+ Ok(String::from_utf8_lossy(&output.stdout).into_owned())
+}
+
+pub fn system_info() -> IoResult<String> {
+ dmidecode_dispatch(&["-q", "-t1"])
+}
+
+pub fn bios_info() -> IoResult<String> {
+ dmidecode_dispatch(&["-q", "-t0"])
+}
+
+pub fn release_description() -> IoResult<String> {
+ for l in fs::read_to_string("/etc/lsb-release")?.lines() {
+ if l.starts_with("CHROMEOS_RELEASE_DESCRIPTION") {
+ return Ok(l.to_string());
+ }
+ }
+ Err(std::io::ErrorKind::NotFound.into())
+}
diff --git a/util/flashrom_tester/src/lib.rs b/util/flashrom_tester/src/lib.rs
new file mode 100644
index 000000000..d8f1cb6aa
--- /dev/null
+++ b/util/flashrom_tester/src/lib.rs
@@ -0,0 +1,46 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+pub mod types;
+
+pub mod cros_sysinfo;
+pub mod rand_util;
+pub mod tester;
+pub mod tests;
+pub mod utils;
diff --git a/util/flashrom_tester/src/logger.rs b/util/flashrom_tester/src/logger.rs
new file mode 100644
index 000000000..c9c364066
--- /dev/null
+++ b/util/flashrom_tester/src/logger.rs
@@ -0,0 +1,149 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use flashrom_tester::types;
+use std::io::Write;
+
+struct Logger {
+ level: log::LevelFilter,
+ color: types::Color,
+}
+
+impl log::Log for Logger {
+ fn enabled(&self, metadata: &log::Metadata) -> bool {
+ metadata.level() <= self.level
+ }
+
+ fn log(&self, record: &log::Record) {
+ // Write errors deliberately ignored
+ let stdout = std::io::stdout();
+ let mut lock = stdout.lock();
+ let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
+ let _ = write!(lock, "{}{} ", self.color.magenta, now);
+ let _ = write!(
+ lock,
+ "{}[ {} ]{} ",
+ self.color.yellow,
+ record.level(),
+ self.color.reset
+ );
+ let _ = writeln!(lock, "{}", record.args());
+ }
+
+ fn flush(&self) {
+ // Flush errors deliberately ignored
+ let _ = std::io::stdout().flush();
+ }
+}
+
+pub fn init(debug: bool) {
+ let mut logger = Logger {
+ level: log::LevelFilter::Info,
+ color: if atty::is(atty::Stream::Stdout) {
+ types::COLOR
+ } else {
+ types::NOCOLOR
+ },
+ };
+
+ if debug {
+ logger.level = log::LevelFilter::Debug;
+ }
+ log::set_max_level(logger.level);
+ log::set_boxed_logger(Box::new(logger)).unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+ use std::io::Read;
+
+ use super::Logger;
+ use flashrom_tester::types;
+ use log::{Level, LevelFilter, Log, Record};
+
+ fn run_records(records: &[Record]) -> String {
+ let buf = gag::BufferRedirect::stdout().unwrap();
+ {
+ let logger = Logger {
+ level: LevelFilter::Info,
+ color: types::COLOR,
+ };
+
+ for record in records {
+ if logger.enabled(record.metadata()) {
+ logger.log(record);
+ }
+ }
+ }
+ let mut ret = String::new();
+ buf.into_inner().read_to_string(&mut ret).unwrap();
+ ret
+ }
+
+ /// Log messages have the expected format
+ #[test]
+ fn format() {
+ let buf = run_records(&[Record::builder()
+ .args(format_args!("Test message at INFO"))
+ .level(Level::Info)
+ .build()]);
+
+ assert_eq!(&buf[..5], "\x1b[35m");
+ // Time is difficult to test, assume it's formatted okay
+ // Split on the UTC timezone char
+ assert_eq!(
+ buf.split_once("Z ").unwrap().1,
+ "\x1b[33m[ INFO ]\x1b[0m Test message at INFO\n"
+ );
+ }
+
+ #[test]
+ fn level_filter() {
+ let buf = run_records(&[
+ Record::builder()
+ .args(format_args!("Test message at DEBUG"))
+ .level(Level::Debug)
+ .build(),
+ Record::builder()
+ .args(format_args!("Hello, world!"))
+ .level(Level::Error)
+ .build(),
+ ]);
+
+ // There is one line because the Debug record wasn't written.
+ println!("{}", buf);
+ assert_eq!(buf.lines().count(), 1);
+ }
+}
diff --git a/util/flashrom_tester/src/main.rs b/util/flashrom_tester/src/main.rs
new file mode 100644
index 000000000..b8a2581ac
--- /dev/null
+++ b/util/flashrom_tester/src/main.rs
@@ -0,0 +1,211 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+#[macro_use]
+extern crate log;
+
+mod logger;
+
+use clap::{App, Arg};
+use flashrom::{FlashChip, Flashrom, FlashromCmd, FlashromLib};
+use flashrom_tester::{tester, tests};
+use std::sync::atomic::AtomicBool;
+
+pub mod built_info {
+ include!(concat!(env!("OUT_DIR"), "/built.rs"));
+}
+
+fn main() {
+ let matches = App::new("flashrom_tester")
+ .long_version(&*format!(
+ "{}-{}\n\
+ Target: {}\n\
+ Profile: {}\n\
+ Features: {:?}\n\
+ Build time: {}\n\
+ Compiler: {}",
+ built_info::PKG_VERSION,
+ option_env!("VCSID").unwrap_or("<unknown>"),
+ built_info::TARGET,
+ built_info::PROFILE,
+ built_info::FEATURES,
+ built_info::BUILT_TIME_UTC,
+ built_info::RUSTC_VERSION,
+ ))
+ .arg(
+ Arg::with_name("libflashrom")
+ .long("libflashrom")
+ .takes_value(false)
+ .help("Test the flashrom library instead of a binary"),
+ )
+ .arg(
+ Arg::with_name("flashrom_binary")
+ .long("flashrom_binary")
+ .short("b")
+ .takes_value(true)
+ .required_unless("libflashrom")
+ .conflicts_with("libflashrom")
+ .help("Path to flashrom binary to test"),
+ )
+ .arg(
+ Arg::with_name("ccd_target_type")
+ .required(true)
+ .possible_values(&["host"]),
+ )
+ .arg(
+ Arg::with_name("print-layout")
+ .short("l")
+ .long("print-layout")
+ .help("Print the layout file's contents before running tests"),
+ )
+ .arg(
+ Arg::with_name("log_debug")
+ .short("d")
+ .long("debug")
+ .help("Write detailed logs, for debugging"),
+ )
+ .arg(
+ Arg::with_name("output-format")
+ .short("f")
+ .long("output-format")
+ .help("Set the test report format")
+ .takes_value(true)
+ .case_insensitive(true)
+ .possible_values(&["pretty", "json"])
+ .default_value("pretty"),
+ )
+ .arg(
+ Arg::with_name("test_name")
+ .multiple(true)
+ .help("Names of individual tests to run (run all if unspecified)"),
+ )
+ .get_matches();
+
+ logger::init(matches.is_present("log_debug"));
+ debug!("Args parsed and logging initialized OK");
+
+ debug!("Collecting crossystem info");
+ let crossystem =
+ flashrom_tester::utils::collect_crosssystem(&[]).expect("could not run crossystem");
+
+ let ccd_type = FlashChip::from(
+ matches
+ .value_of("ccd_target_type")
+ .expect("ccd_target_type should be required"),
+ )
+ .expect("ccd_target_type should admit only known types");
+
+ let cmd: Box<dyn Flashrom> = if matches.is_present("libflashrom") {
+ Box::new(FlashromLib::new(
+ ccd_type,
+ if matches.is_present("log_debug") {
+ flashrom::FLASHROM_MSG_DEBUG
+ } else {
+ flashrom::FLASHROM_MSG_WARN
+ },
+ ))
+ } else {
+ Box::new(FlashromCmd {
+ path: matches
+ .value_of("flashrom_binary")
+ .expect("flashrom_binary is required")
+ .to_string(),
+ fc: ccd_type,
+ })
+ };
+
+ let print_layout = matches.is_present("print-layout");
+ let output_format = matches
+ .value_of("output-format")
+ .expect("output-format should have a default value")
+ .parse::<tester::OutputFormat>()
+ .expect("output-format is not a parseable OutputFormat");
+ let test_names = matches.values_of("test_name");
+
+ if let Err(e) = tests::generic(
+ cmd.as_ref(),
+ ccd_type,
+ print_layout,
+ output_format,
+ test_names,
+ Some(handle_sigint()),
+ crossystem,
+ ) {
+ eprintln!("Failed to run tests: {:?}", e);
+ std::process::exit(1);
+ }
+}
+
+/// Catch exactly one SIGINT, printing a message in response and setting a flag.
+///
+/// The returned value is false by default, becoming true after a SIGINT is
+/// trapped.
+///
+/// Once a signal is trapped, the default behavior is restored (terminating
+/// the process) for future signals.
+fn handle_sigint() -> &'static AtomicBool {
+ use libc::c_int;
+ use std::sync::atomic::Ordering;
+
+ unsafe {
+ let _ = libc::signal(libc::SIGINT, sigint_handler as libc::sighandler_t);
+ }
+ static TERMINATE_FLAG: AtomicBool = AtomicBool::new(false);
+
+ extern "C" fn sigint_handler(_: c_int) {
+ const STDERR_FILENO: c_int = 2;
+ static MESSAGE: &[u8] = b"
+WARNING: terminating tests prematurely may leave Flash in an inconsistent state,
+rendering your machine unbootable. Testing will end on completion of the current
+test, or press ^C again to exit immediately (possibly bricking your machine).
+";
+
+ // Use raw write() because signal-safety is a very hard problem. Safe because this doesn't
+ // modify any memory.
+ let _ = unsafe {
+ libc::write(
+ STDERR_FILENO,
+ MESSAGE.as_ptr() as *const libc::c_void,
+ MESSAGE.len() as libc::size_t,
+ )
+ };
+ unsafe {
+ let _ = libc::signal(libc::SIGINT, libc::SIG_DFL);
+ }
+ TERMINATE_FLAG.store(true, Ordering::Release);
+ }
+
+ &TERMINATE_FLAG
+}
diff --git a/util/flashrom_tester/src/rand_util.rs b/util/flashrom_tester/src/rand_util.rs
new file mode 100644
index 000000000..a040c89a3
--- /dev/null
+++ b/util/flashrom_tester/src/rand_util.rs
@@ -0,0 +1,80 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::BufWriter;
+use std::path::Path;
+
+use rand::prelude::*;
+
+pub fn gen_rand_testdata(path: &Path, size: usize) -> std::io::Result<()> {
+ let mut buf = BufWriter::new(File::create(path)?);
+
+ let mut a: Vec<u8> = vec![0; size];
+ thread_rng().fill(a.as_mut_slice());
+
+ buf.write_all(a.as_slice())?;
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn gen_rand_testdata() {
+ use super::gen_rand_testdata;
+
+ let path0 = Path::new("/tmp/idk_test00");
+ let path1 = Path::new("/tmp/idk_test01");
+ let sz = 1024;
+
+ gen_rand_testdata(path0, sz).unwrap();
+ gen_rand_testdata(path1, sz).unwrap();
+
+ let mut buf0 = Vec::new();
+ let mut buf1 = Vec::new();
+
+ let mut f = File::open(path0).unwrap();
+ let mut g = File::open(path1).unwrap();
+
+ f.read_to_end(&mut buf0).unwrap();
+ g.read_to_end(&mut buf1).unwrap();
+
+ assert_ne!(buf0, buf1);
+ }
+}
diff --git a/util/flashrom_tester/src/tester.rs b/util/flashrom_tester/src/tester.rs
new file mode 100644
index 000000000..4629c2eb7
--- /dev/null
+++ b/util/flashrom_tester/src/tester.rs
@@ -0,0 +1,567 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use super::rand_util;
+use super::types;
+use super::utils::{self, LayoutSizes};
+use flashrom::FlashromError;
+use flashrom::{FlashChip, Flashrom};
+use serde_json::json;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+// type-signature comes from the return type of lib.rs workers.
+type TestError = Box<dyn std::error::Error>;
+pub type TestResult = Result<(), TestError>;
+
+pub struct TestEnv<'a> {
+ chip_type: FlashChip,
+ /// Flashrom instantiation information.
+ ///
+ /// Where possible, prefer to use methods on the TestEnv rather than delegating
+ /// to the raw flashrom functions.
+ pub cmd: &'a dyn Flashrom,
+ layout: LayoutSizes,
+
+ pub wp: WriteProtectState<'a>,
+ /// The path to a file containing the flash contents at test start.
+ original_flash_contents: PathBuf,
+ /// The path to a file containing flash-sized random data
+ random_data: PathBuf,
+ /// The path to a file containing layout data.
+ pub layout_file: PathBuf,
+}
+
+impl<'a> TestEnv<'a> {
+ pub fn create(
+ chip_type: FlashChip,
+ cmd: &'a dyn Flashrom,
+ print_layout: bool,
+ ) -> Result<Self, FlashromError> {
+ let rom_sz = cmd.get_size()?;
+ let out = TestEnv {
+ chip_type,
+ cmd,
+ layout: utils::get_layout_sizes(rom_sz)?,
+ wp: WriteProtectState::from_hardware(cmd, chip_type)?,
+ original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(),
+ random_data: "/tmp/random_content.bin".into(),
+ layout_file: create_layout_file(rom_sz, Path::new("/tmp/"), print_layout),
+ };
+
+ info!("Stashing golden image for verification/recovery on completion");
+ out.cmd.read_into_file(&out.original_flash_contents)?;
+ out.cmd.verify_from_file(&out.original_flash_contents)?;
+
+ info!("Generating random flash-sized data");
+ rand_util::gen_rand_testdata(&out.random_data, rom_sz as usize)
+ .map_err(|io_err| format!("I/O error writing random data file: {:#}", io_err))?;
+
+ Ok(out)
+ }
+
+ pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult {
+ let name = test.get_name();
+ info!("Beginning test: {}", name);
+ let out = test.run(self);
+ info!("Completed test: {}; result {:?}", name, out);
+ out
+ }
+
+ pub fn chip_type(&self) -> FlashChip {
+ // This field is not public because it should be immutable to tests,
+ // so this getter enforces that it is copied.
+ self.chip_type
+ }
+
+ /// Return the path to a file that contains random data and is the same size
+ /// as the flash chip.
+ pub fn random_data_file(&self) -> &Path {
+ &self.random_data
+ }
+
+ pub fn layout(&self) -> &LayoutSizes {
+ &self.layout
+ }
+
+ /// Return true if the current Flash contents are the same as the golden image
+ /// that was present at the start of testing.
+ pub fn is_golden(&self) -> bool {
+ self.cmd
+ .verify_from_file(&self.original_flash_contents)
+ .is_ok()
+ }
+
+ /// Do whatever is necessary to make the current Flash contents the same as they
+ /// were at the start of testing.
+ pub fn ensure_golden(&mut self) -> Result<(), FlashromError> {
+ self.wp.set_hw(false)?.set_sw(false)?;
+ self.cmd.write_from_file(&self.original_flash_contents)?;
+ Ok(())
+ }
+
+ /// Attempt to erase the flash.
+ pub fn erase(&self) -> Result<(), FlashromError> {
+ self.cmd.erase()?;
+ Ok(())
+ }
+
+ /// Verify that the current Flash contents are the same as the file at the given
+ /// path.
+ ///
+ /// Returns Err if they are not the same.
+ pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> {
+ self.cmd.verify_from_file(contents_path)?;
+ Ok(())
+ }
+}
+
+impl<'a> Drop for TestEnv<'a> {
+ fn drop(&mut self) {
+ info!("Verifying flash remains unmodified");
+ if !self.is_golden() {
+ warn!("ROM seems to be in a different state at finish; restoring original");
+ if let Err(e) = self.ensure_golden() {
+ error!("Failed to write back golden image: {:?}", e);
+ }
+ }
+ }
+}
+
+struct WriteProtect {
+ hw: bool,
+ sw: bool,
+}
+
+/// RAII handle for setting write protect in either hardware or software.
+///
+/// Given an instance, the state of either write protect can be modified by calling
+/// `set`. When it goes out of scope, the write protects will be returned
+/// to the state they had then it was created.
+pub struct WriteProtectState<'a> {
+ current: WriteProtect,
+ initial: WriteProtect,
+ cmd: &'a dyn Flashrom,
+ fc: FlashChip,
+}
+
+impl<'a> WriteProtectState<'a> {
+ /// Initialize a state from the current state of the hardware.
+ pub fn from_hardware(cmd: &'a dyn Flashrom, fc: FlashChip) -> Result<Self, FlashromError> {
+ let hw = Self::get_hw(cmd)?;
+ let sw = Self::get_sw(cmd)?;
+ info!("Initial write protect state: HW={} SW={}", hw, sw);
+
+ Ok(WriteProtectState {
+ current: WriteProtect { hw, sw },
+ initial: WriteProtect { hw, sw },
+ cmd,
+ fc,
+ })
+ }
+
+ /// Get the actual hardware write protect state.
+ fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> {
+ if cmd.can_control_hw_wp() {
+ super::utils::get_hardware_wp()
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// Get the actual software write protect state.
+ fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> {
+ let b = cmd.wp_status(true)?;
+ Ok(b)
+ }
+
+ /// Return true if the current programmer supports setting the hardware
+ /// write protect.
+ ///
+ /// If false, calls to set_hw() will do nothing.
+ pub fn can_control_hw_wp(&self) -> bool {
+ self.cmd.can_control_hw_wp()
+ }
+
+ /// Set the software write protect and check that the state is as expected.
+ pub fn set_sw(&mut self, enable: bool) -> Result<&mut Self, String> {
+ info!("set_sw request={}, current={}", enable, self.current.sw);
+ if self.current.sw != enable {
+ self.cmd
+ .wp_toggle(/* en= */ enable)
+ .map_err(|e| e.to_string())?;
+ }
+ if Self::get_sw(self.cmd).map_err(|e| e.to_string())? != enable {
+ Err(format!(
+ "Software write protect did not change state to {} when requested",
+ enable
+ ))
+ } else {
+ self.current.sw = enable;
+ Ok(self)
+ }
+ }
+
+ // Set software write protect with a custom range
+ pub fn set_range(&mut self, range: (i64, i64), enable: bool) -> Result<&mut Self, String> {
+ info!("set_range request={}, current={}", enable, self.current.sw);
+ self.cmd
+ .wp_range(range, enable)
+ .map_err(|e| e.to_string())?;
+ let actual_state = Self::get_sw(self.cmd).map_err(|e| e.to_string())?;
+ if actual_state != enable {
+ Err(format!(
+ "set_range request={}, real={}",
+ enable, actual_state
+ ))
+ } else {
+ self.current.sw = enable;
+ Ok(self)
+ }
+ }
+
+ /// Set the hardware write protect if supported and check that the state is as expected.
+ pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> {
+ info!("set_hw request={}, current={}", enable, self.current.hw);
+ if self.can_control_hw_wp() {
+ if self.current.hw != enable {
+ super::utils::toggle_hw_wp(/* dis= */ !enable)?;
+ }
+ // toggle_hw_wp does check this, but we might not have called toggle_hw_wp so check again.
+ if Self::get_hw(self.cmd)? != enable {
+ return Err(format!(
+ "Hardware write protect did not change state to {} when requested",
+ enable
+ ));
+ }
+ } else {
+ info!(
+ "Ignoring attempt to set hardware WP with {:?} programmer",
+ self.fc
+ );
+ }
+ self.current.hw = enable;
+ Ok(self)
+ }
+
+ /// Reset the hardware to what it was when this state was created, reporting errors.
+ ///
+ /// This behaves exactly like allowing a state to go out of scope, but it can return
+ /// errors from that process rather than panicking.
+ pub fn close(mut self) -> Result<(), String> {
+ let out = self.drop_internal();
+ // We just ran drop, don't do it again
+ std::mem::forget(self);
+ out
+ }
+
+ /// Sets both write protects to the state they had when this state was created.
+ fn drop_internal(&mut self) -> Result<(), String> {
+ // Toggle both protects back to their initial states.
+ // Software first because we can't change it once hardware is enabled.
+ if self.set_sw(self.initial.sw).is_err() {
+ self.set_hw(false)?;
+ self.set_sw(self.initial.sw)?;
+ }
+ self.set_hw(self.initial.hw)?;
+
+ Ok(())
+ }
+}
+
+impl<'a> Drop for WriteProtectState<'a> {
+ fn drop(&mut self) {
+ self.drop_internal()
+ .expect("Error while dropping WriteProtectState")
+ }
+}
+
+pub trait TestCase {
+ fn get_name(&self) -> &str;
+ fn expected_result(&self) -> TestConclusion;
+ fn run(&self, env: &mut TestEnv) -> TestResult;
+}
+
+impl<S: AsRef<str>, F: Fn(&mut TestEnv) -> TestResult> TestCase for (S, F) {
+ fn get_name(&self) -> &str {
+ self.0.as_ref()
+ }
+
+ fn expected_result(&self) -> TestConclusion {
+ TestConclusion::Pass
+ }
+
+ fn run(&self, env: &mut TestEnv) -> TestResult {
+ (self.1)(env)
+ }
+}
+
+impl<T: TestCase + ?Sized> TestCase for &T {
+ fn get_name(&self) -> &str {
+ (*self).get_name()
+ }
+
+ fn expected_result(&self) -> TestConclusion {
+ (*self).expected_result()
+ }
+
+ fn run(&self, env: &mut TestEnv) -> TestResult {
+ (*self).run(env)
+ }
+}
+
+#[allow(dead_code)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum TestConclusion {
+ Pass,
+ Fail,
+ UnexpectedPass,
+ UnexpectedFail,
+}
+
+pub struct ReportMetaData {
+ pub chip_name: String,
+ pub os_release: String,
+ pub cros_release: String,
+ pub system_info: String,
+ pub bios_info: String,
+}
+
+fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, Option<TestError>) {
+ use TestConclusion::*;
+
+ match (res, con) {
+ (Ok(_), Fail) => (UnexpectedPass, None),
+ (Err(e), Pass) => (UnexpectedFail, Some(e)),
+ _ => (Pass, None),
+ }
+}
+
+fn create_layout_file(rom_sz: i64, tmp_dir: &Path, print_layout: bool) -> PathBuf {
+ info!("Calculate ROM partition sizes & Create the layout file.");
+ let layout_sizes = utils::get_layout_sizes(rom_sz).expect("Could not partition rom");
+
+ let layout_file = tmp_dir.join("layout.file");
+ let mut f = File::create(&layout_file).expect("Could not create layout file");
+ let mut buf: Vec<u8> = vec![];
+ utils::construct_layout_file(&mut buf, &layout_sizes).expect("Could not construct layout file");
+
+ f.write_all(&buf).expect("Writing layout file failed");
+ if print_layout {
+ info!(
+ "Dumping layout file as requested:\n{}",
+ String::from_utf8_lossy(&buf)
+ );
+ }
+ layout_file
+}
+
+pub fn run_all_tests<T, TS>(
+ chip: FlashChip,
+ cmd: &dyn Flashrom,
+ ts: TS,
+ terminate_flag: Option<&AtomicBool>,
+ print_layout: bool,
+) -> Vec<(String, (TestConclusion, Option<TestError>))>
+where
+ T: TestCase + Copy,
+ TS: IntoIterator<Item = T>,
+{
+ let mut env =
+ TestEnv::create(chip, cmd, print_layout).expect("Failed to set up test environment");
+
+ let mut results = Vec::new();
+ for t in ts {
+ if terminate_flag
+ .map(|b| b.load(Ordering::Acquire))
+ .unwrap_or(false)
+ {
+ break;
+ }
+
+ let result = decode_test_result(env.run_test(t), t.expected_result());
+ results.push((t.get_name().into(), result));
+ }
+ results
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum OutputFormat {
+ Pretty,
+ Json,
+}
+
+impl std::str::FromStr for OutputFormat {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use OutputFormat::*;
+
+ if s.eq_ignore_ascii_case("pretty") {
+ Ok(Pretty)
+ } else if s.eq_ignore_ascii_case("json") {
+ Ok(Json)
+ } else {
+ Err(())
+ }
+ }
+}
+
+pub fn collate_all_test_runs(
+ truns: &[(String, (TestConclusion, Option<TestError>))],
+ meta_data: ReportMetaData,
+ format: OutputFormat,
+) {
+ match format {
+ OutputFormat::Pretty => {
+ let color = if atty::is(atty::Stream::Stdout) {
+ types::COLOR
+ } else {
+ types::NOCOLOR
+ };
+ println!();
+ println!(" =============================");
+ println!(" ===== AVL qual RESULTS ====");
+ println!(" =============================");
+ println!();
+ println!(" %---------------------------%");
+ println!(" os release: {}", meta_data.os_release);
+ println!(" cros release: {}", meta_data.cros_release);
+ println!(" chip name: {}", meta_data.chip_name);
+ println!(" system info: \n{}", meta_data.system_info);
+ println!(" bios info: \n{}", meta_data.bios_info);
+ println!(" %---------------------------%");
+ println!();
+
+ for trun in truns.iter() {
+ let (name, (result, error)) = trun;
+ if *result != TestConclusion::Pass {
+ println!(
+ " {} {}",
+ style!(format!(" <+> {} test:", name), color.bold, color),
+ style_dbg!(result, color.red, color)
+ );
+ match error {
+ None => {}
+ Some(e) => info!(" - {} failure details:\n{}", name, e.to_string()),
+ };
+ } else {
+ println!(
+ " {} {}",
+ style!(format!(" <+> {} test:", name), color.bold, color),
+ style_dbg!(result, color.green, color)
+ );
+ }
+ }
+ println!();
+ }
+ OutputFormat::Json => {
+ use serde_json::{Map, Value};
+
+ let mut all_pass = true;
+ let mut tests = Map::<String, Value>::new();
+ for (name, (result, error)) in truns {
+ let passed = *result == TestConclusion::Pass;
+ all_pass &= passed;
+
+ let error = match error {
+ Some(e) => Value::String(format!("{:#?}", e)),
+ None => Value::Null,
+ };
+
+ assert!(
+ !tests.contains_key(name),
+ "Found multiple tests named {:?}",
+ name
+ );
+ tests.insert(
+ name.into(),
+ json!({
+ "pass": passed,
+ "error": error,
+ }),
+ );
+ }
+
+ let json = json!({
+ "pass": all_pass,
+ "metadata": {
+ "os_release": meta_data.os_release,
+ "chip_name": meta_data.chip_name,
+ "system_info": meta_data.system_info,
+ "bios_info": meta_data.bios_info,
+ },
+ "tests": tests,
+ });
+ println!("{:#}", json);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn decode_test_result() {
+ use super::decode_test_result;
+ use super::TestConclusion::*;
+
+ let (result, err) = decode_test_result(Ok(()), Pass);
+ assert_eq!(result, Pass);
+ assert!(err.is_none());
+
+ let (result, err) = decode_test_result(Ok(()), Fail);
+ assert_eq!(result, UnexpectedPass);
+ assert!(err.is_none());
+
+ let (result, err) = decode_test_result(Err("broken".into()), Pass);
+ assert_eq!(result, UnexpectedFail);
+ assert!(err.is_some());
+
+ let (result, err) = decode_test_result(Err("broken".into()), Fail);
+ assert_eq!(result, Pass);
+ assert!(err.is_none());
+ }
+
+ #[test]
+ fn output_format_round_trip() {
+ use super::OutputFormat::{self, *};
+
+ assert_eq!(format!("{:?}", Pretty).parse::<OutputFormat>(), Ok(Pretty));
+ assert_eq!(format!("{:?}", Json).parse::<OutputFormat>(), Ok(Json));
+ }
+}
diff --git a/util/flashrom_tester/src/tests.rs b/util/flashrom_tester/src/tests.rs
new file mode 100644
index 000000000..721a789d5
--- /dev/null
+++ b/util/flashrom_tester/src/tests.rs
@@ -0,0 +1,398 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use super::cros_sysinfo;
+use super::tester::{self, OutputFormat, TestCase, TestEnv, TestResult};
+use super::utils::{self, LayoutNames};
+use flashrom::{FlashChip, Flashrom};
+use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
+use std::fs::{self, File};
+use std::io::BufRead;
+use std::sync::atomic::AtomicBool;
+
+const ELOG_FILE: &str = "/tmp/elog.file";
+const FW_MAIN_B_PATH: &str = "/tmp/FW_MAIN_B.bin";
+
+/// Iterate over tests, yielding only those tests with names matching filter_names.
+///
+/// If filter_names is None, all tests will be run. None is distinct from Some(∅);
+// Some(∅) runs no tests.
+///
+/// Name comparisons are performed in lower-case: values in filter_names must be
+/// converted to lowercase specifically.
+///
+/// When an entry in filter_names matches a test, it is removed from that set.
+/// This allows the caller to determine if any entries in the original set failed
+/// to match any test, which may be user error.
+fn filter_tests<'n, 't: 'n, T: TestCase>(
+ tests: &'t [T],
+ filter_names: &'n mut Option<HashSet<String>>,
+) -> impl 'n + Iterator<Item = &'t T> {
+ tests.iter().filter(move |test| match filter_names {
+ // Accept all tests if no names are given
+ None => true,
+ Some(ref mut filter_names) => {
+ // Pop a match to the test name from the filter set, retaining the test
+ // if there was a match.
+ filter_names.remove(&test.get_name().to_lowercase())
+ }
+ })
+}
+
+/// Run tests.
+///
+/// Only returns an Error if there was an internal error; test failures are Ok.
+///
+/// test_names is the case-insensitive names of tests to run; if None, then all
+/// tests are run. Provided names that don't match any known test will be logged
+/// as a warning.
+#[allow(clippy::or_fun_call)] // This is used for to_string here and we don't care.
+pub fn generic<'a, TN: Iterator<Item = &'a str>>(
+ cmd: &dyn Flashrom,
+ fc: FlashChip,
+ print_layout: bool,
+ output_format: OutputFormat,
+ test_names: Option<TN>,
+ terminate_flag: Option<&AtomicBool>,
+ crossystem: String,
+) -> Result<(), Box<dyn std::error::Error>> {
+ utils::ac_power_warning();
+
+ info!("Record crossystem information.\n{}", crossystem);
+
+ // Register tests to run:
+ let tests: &[&dyn TestCase] = &[
+ &("Get_device_name", get_device_name_test),
+ &("Coreboot_ELOG_sanity", elog_sanity_test),
+ &("Host_is_ChromeOS", host_is_chrome_test),
+ &("WP_Region_List", wp_region_list_test),
+ &("Erase_and_Write", erase_write_test),
+ &("Fail_to_verify", verify_fail_test),
+ &("HWWP_Locks_SWWP", hwwp_locks_swwp_test),
+ &("Lock_top_quad", partial_lock_test(LayoutNames::TopQuad)),
+ &(
+ "Lock_bottom_quad",
+ partial_lock_test(LayoutNames::BottomQuad),
+ ),
+ &(
+ "Lock_bottom_half",
+ partial_lock_test(LayoutNames::BottomHalf),
+ ),
+ &("Lock_top_half", partial_lock_test(LayoutNames::TopHalf)),
+ ];
+
+ // Limit the tests to only those requested, unless none are requested
+ // in which case all tests are included.
+ let mut filter_names: Option<HashSet<String>> =
+ test_names.map(|names| names.map(|s| s.to_lowercase()).collect());
+ let tests = filter_tests(tests, &mut filter_names);
+
+ let chip_name = cmd
+ .name()
+ .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1))
+ .unwrap_or("<Unknown chip>".into());
+
+ // ------------------------.
+ // Run all the tests and collate the findings:
+ let results = tester::run_all_tests(fc, cmd, tests, terminate_flag, print_layout);
+
+ // Any leftover filtered names were specified to be run but don't exist
+ for leftover in filter_names.iter().flatten() {
+ warn!("No test matches filter name \"{}\"", leftover);
+ }
+
+ let os_release = sys_info::os_release().unwrap_or("<Unknown OS>".to_string());
+ let cros_release = cros_sysinfo::release_description()
+ .unwrap_or("<Unknown or not a ChromeOS release>".to_string());
+ let system_info = cros_sysinfo::system_info().unwrap_or("<Unknown System>".to_string());
+ let bios_info = cros_sysinfo::bios_info().unwrap_or("<Unknown BIOS>".to_string());
+
+ let meta_data = tester::ReportMetaData {
+ chip_name,
+ os_release,
+ cros_release,
+ system_info,
+ bios_info,
+ };
+ tester::collate_all_test_runs(&results, meta_data, output_format);
+ Ok(())
+}
+
+/// Query the programmer and chip name.
+/// Success means we got something back, which is good enough.
+fn get_device_name_test(env: &mut TestEnv) -> TestResult {
+ env.cmd.name()?;
+ Ok(())
+}
+
+/// List the write-protectable regions of flash.
+/// NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms.
+/// However, we will warn when it does fail.
+fn wp_region_list_test(env: &mut TestEnv) -> TestResult {
+ match env.cmd.wp_list() {
+ Ok(list_str) => info!("\n{}", list_str),
+ Err(e) => warn!("{}", e),
+ };
+ Ok(())
+}
+
+/// Verify that enabling hardware and software write protect prevents chip erase.
+fn erase_write_test(env: &mut TestEnv) -> TestResult {
+ if !env.is_golden() {
+ info!("Memory has been modified; reflashing to ensure erasure can be detected");
+ env.ensure_golden()?;
+ }
+
+ // With write protect enabled erase should fail.
+ env.wp.set_sw(true)?.set_hw(true)?;
+ if env.erase().is_ok() {
+ info!("Flashrom returned Ok but this may be incorrect; verifying");
+ if !env.is_golden() {
+ return Err("Hardware write protect asserted however can still erase!".into());
+ }
+ info!("Erase claimed to succeed but verify is Ok; assume erase failed");
+ }
+
+ // With write protect disabled erase should succeed.
+ env.wp.set_hw(false)?.set_sw(false)?;
+ env.erase()?;
+ if env.is_golden() {
+ return Err("Successful erase didn't modify memory".into());
+ }
+
+ Ok(())
+}
+
+/// Verify that enabling hardware write protect prevents disabling software write protect.
+fn hwwp_locks_swwp_test(env: &mut TestEnv) -> TestResult {
+ if !env.wp.can_control_hw_wp() {
+ return Err("Lock test requires ability to control hardware write protect".into());
+ }
+
+ env.wp.set_hw(false)?.set_sw(true)?;
+ // Toggling software WP off should work when hardware WP is off.
+ // Then enable software WP again for the next test.
+ env.wp.set_sw(false)?.set_sw(true)?;
+
+ // Toggling software WP off should not work when hardware WP is on.
+ env.wp.set_hw(true)?;
+ if env.wp.set_sw(false).is_ok() {
+ return Err("Software WP was reset despite hardware WP being enabled".into());
+ }
+ Ok(())
+}
+
+/// Check that the elog contains *something*, as an indication that Coreboot
+/// is actually able to write to the Flash. This only makes sense for chips
+/// running Coreboot, which we assume is just host.
+fn elog_sanity_test(env: &mut TestEnv) -> TestResult {
+ if env.chip_type() != FlashChip::HOST {
+ info!("Skipping ELOG sanity check for non-host chip");
+ return Ok(());
+ }
+ // flash should be back in the golden state
+ env.ensure_golden()?;
+
+ const ELOG_RW_REGION_NAME: &str = "RW_ELOG";
+ env.cmd
+ .read_region_into_file(ELOG_FILE.as_ref(), ELOG_RW_REGION_NAME)?;
+
+ // Just checking for the magic numer
+ // TODO: improve this test to read the events
+ if fs::metadata(ELOG_FILE)?.len() < 4 {
+ return Err("ELOG contained no data".into());
+ }
+ let data = fs::read(ELOG_FILE)?;
+ if u32::from_le_bytes(data[0..4].try_into()?) != 0x474f4c45 {
+ return Err("ELOG had bad magic number".into());
+ }
+ Ok(())
+}
+
+/// Check that we are running ChromiumOS.
+fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult {
+ let release_info = if let Ok(f) = File::open("/etc/os-release") {
+ let buf = std::io::BufReader::new(f);
+ parse_os_release(buf.lines().flatten())
+ } else {
+ info!("Unable to read /etc/os-release to probe system information");
+ HashMap::new()
+ };
+
+ match release_info.get("ID") {
+ Some(id) if id == "chromeos" || id == "chromiumos" => Ok(()),
+ oid => {
+ let id = match oid {
+ Some(s) => s,
+ None => "UNKNOWN",
+ };
+ Err(format!(
+ "Test host os-release \"{}\" should be but is not chromeos",
+ id
+ )
+ .into())
+ }
+ }
+}
+
+/// Verify that software write protect for a range protects only the requested range.
+fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResult {
+ move |env: &mut TestEnv| {
+ // Need a clean image for verification
+ env.ensure_golden()?;
+
+ let (wp_section_name, start, len) = utils::layout_section(env.layout(), section);
+ // Disable hardware WP so we can modify the protected range.
+ env.wp.set_hw(false)?;
+ // Then enable software WP so the range is enforced and enable hardware
+ // WP so that flashrom does not disable software WP during the
+ // operation.
+ env.wp.set_range((start, len), true)?;
+ env.wp.set_hw(true)?;
+
+ // Check that we cannot write to the protected region.
+ if env
+ .cmd
+ .write_from_file_region(env.random_data_file(), wp_section_name, &env.layout_file)
+ .is_ok()
+ {
+ return Err(
+ "Section should be locked, should not have been overwritable with random data"
+ .into(),
+ );
+ }
+ if !env.is_golden() {
+ return Err("Section didn't lock, has been overwritten with random data!".into());
+ }
+
+ // Check that we can write to the non protected region.
+ let (non_wp_section_name, _, _) =
+ utils::layout_section(env.layout(), section.get_non_overlapping_section());
+ env.cmd.write_from_file_region(
+ env.random_data_file(),
+ non_wp_section_name,
+ &env.layout_file,
+ )?;
+
+ Ok(())
+ }
+}
+
+/// Check that flashrom 'verify' will fail if the provided data does not match the chip data.
+fn verify_fail_test(env: &mut TestEnv) -> TestResult {
+ env.ensure_golden()?;
+ // Verify that verify is Ok when the data matches. We verify only a region
+ // and not the whole chip because coprocessors or firmware may have written
+ // some data in other regions.
+ env.cmd
+ .read_region_into_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?;
+ env.cmd
+ .verify_region_from_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?;
+
+ // Verify that verify is false when the data does not match
+ match env.verify(env.random_data_file()) {
+ Ok(_) => Err("Verification says flash is full of random data".into()),
+ Err(_) => Ok(()),
+ }
+}
+
+/// Ad-hoc parsing of os-release(5); mostly according to the spec,
+/// but ignores quotes and escaping.
+fn parse_os_release<I: IntoIterator<Item = String>>(lines: I) -> HashMap<String, String> {
+ fn parse_line(line: String) -> Option<(String, String)> {
+ if line.is_empty() || line.starts_with('#') {
+ return None;
+ }
+
+ let delimiter = match line.find('=') {
+ Some(idx) => idx,
+ None => {
+ warn!("os-release entry seems malformed: {:?}", line);
+ return None;
+ }
+ };
+ Some((
+ line[..delimiter].to_owned(),
+ line[delimiter + 1..].to_owned(),
+ ))
+ }
+
+ lines.into_iter().filter_map(parse_line).collect()
+}
+
+#[test]
+fn test_parse_os_release() {
+ let lines = [
+ "BUILD_ID=12516.0.0",
+ "# this line is a comment followed by an empty line",
+ "",
+ "ID_LIKE=chromiumos",
+ "ID=chromeos",
+ "VERSION=79",
+ "EMPTY_VALUE=",
+ ];
+ let map = parse_os_release(lines.iter().map(|&s| s.to_owned()));
+
+ fn get<'a, 'b>(m: &'a HashMap<String, String>, k: &'b str) -> Option<&'a str> {
+ m.get(k).map(|s| s.as_ref())
+ }
+
+ assert_eq!(get(&map, "ID"), Some("chromeos"));
+ assert_eq!(get(&map, "BUILD_ID"), Some("12516.0.0"));
+ assert_eq!(get(&map, "EMPTY_VALUE"), Some(""));
+ assert_eq!(get(&map, ""), None);
+}
+
+#[test]
+fn test_name_filter() {
+ let test_one = ("Test One", |_: &mut TestEnv| Ok(()));
+ let test_two = ("Test Two", |_: &mut TestEnv| Ok(()));
+ let tests: &[&dyn TestCase] = &[&test_one, &test_two];
+
+ let mut names = None;
+ // All tests pass through
+ assert_eq!(filter_tests(tests, &mut names).count(), 2);
+
+ names = Some(["test two"].iter().map(|s| s.to_string()).collect());
+ // Filtered out test one
+ assert_eq!(filter_tests(tests, &mut names).count(), 1);
+
+ names = Some(["test three"].iter().map(|s| s.to_string()).collect());
+ // No tests emitted
+ assert_eq!(filter_tests(tests, &mut names).count(), 0);
+ // Name got left behind because no test matched it
+ assert_eq!(names.unwrap().len(), 1);
+}
diff --git a/util/flashrom_tester/src/types.rs b/util/flashrom_tester/src/types.rs
new file mode 100644
index 000000000..9cefb27e9
--- /dev/null
+++ b/util/flashrom_tester/src/types.rs
@@ -0,0 +1,72 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+pub struct Color {
+ pub bold: &'static str,
+ pub reset: &'static str,
+ pub magenta: &'static str,
+ pub yellow: &'static str,
+ pub green: &'static str,
+ pub red: &'static str,
+}
+
+pub const COLOR: Color = Color {
+ bold: "\x1b[1m",
+ reset: "\x1b[0m",
+ magenta: "\x1b[35m",
+ yellow: "\x1b[33m",
+ green: "\x1b[92m",
+ red: "\x1b[31m",
+};
+
+pub const NOCOLOR: Color = Color {
+ bold: "",
+ reset: "",
+ magenta: "",
+ yellow: "",
+ green: "",
+ red: "",
+};
+
+macro_rules! style_dbg {
+ ($s: expr, $c: expr, $col: expr) => {
+ format!("{}{:?}{}", $c, $s, $col.reset)
+ };
+}
+macro_rules! style {
+ ($s: expr, $c: expr, $col: expr) => {
+ format!("{}{}{}", $c, $s, $col.reset)
+ };
+}
diff --git a/util/flashrom_tester/src/utils.rs b/util/flashrom_tester/src/utils.rs
new file mode 100644
index 000000000..4e8dd7cce
--- /dev/null
+++ b/util/flashrom_tester/src/utils.rs
@@ -0,0 +1,241 @@
+//
+// Copyright 2019, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Alternatively, this software may be distributed under the terms of the
+// GNU General Public License ("GPL") version 2 as published by the Free
+// Software Foundation.
+//
+
+use std::io::prelude::*;
+use std::process::Command;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum LayoutNames {
+ TopQuad,
+ TopHalf,
+ BottomHalf,
+ BottomQuad,
+}
+
+impl LayoutNames {
+ // Return a section that does not overlap
+ pub fn get_non_overlapping_section(&self) -> LayoutNames {
+ match self {
+ LayoutNames::TopQuad => LayoutNames::BottomQuad,
+ LayoutNames::TopHalf => LayoutNames::BottomHalf,
+ LayoutNames::BottomHalf => LayoutNames::TopHalf,
+ LayoutNames::BottomQuad => LayoutNames::TopQuad,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct LayoutSizes {
+ half_sz: i64,
+ quad_sz: i64,
+ rom_top: i64,
+ bottom_half_top: i64,
+ bottom_quad_top: i64,
+ top_quad_bottom: i64,
+}
+
+pub fn get_layout_sizes(rom_sz: i64) -> Result<LayoutSizes, String> {
+ if rom_sz <= 0 {
+ return Err("invalid rom size provided".into());
+ }
+ if rom_sz & (rom_sz - 1) != 0 {
+ return Err("invalid rom size, not a power of 2".into());
+ }
+ Ok(LayoutSizes {
+ half_sz: rom_sz / 2,
+ quad_sz: rom_sz / 4,
+ rom_top: rom_sz - 1,
+ bottom_half_top: (rom_sz / 2) - 1,
+ bottom_quad_top: (rom_sz / 4) - 1,
+ top_quad_bottom: (rom_sz / 4) * 3,
+ })
+}
+
+pub fn layout_section(ls: &LayoutSizes, ln: LayoutNames) -> (&'static str, i64, i64) {
+ match ln {
+ LayoutNames::TopQuad => ("TOP_QUAD", ls.top_quad_bottom, ls.quad_sz),
+ LayoutNames::TopHalf => ("TOP_HALF", ls.half_sz, ls.half_sz),
+ LayoutNames::BottomHalf => ("BOTTOM_HALF", 0, ls.half_sz),
+ LayoutNames::BottomQuad => ("BOTTOM_QUAD", 0, ls.quad_sz),
+ }
+}
+
+pub fn construct_layout_file<F: Write>(mut target: F, ls: &LayoutSizes) -> std::io::Result<()> {
+ writeln!(target, "000000:{:x} BOTTOM_QUAD", ls.bottom_quad_top)?;
+ writeln!(target, "000000:{:x} BOTTOM_HALF", ls.bottom_half_top)?;
+ writeln!(target, "{:x}:{:x} TOP_HALF", ls.half_sz, ls.rom_top)?;
+ writeln!(target, "{:x}:{:x} TOP_QUAD", ls.top_quad_bottom, ls.rom_top)
+}
+
+pub fn toggle_hw_wp(dis: bool) -> Result<(), String> {
+ // The easist way to toggle the hardware write-protect is
+ // to {dis}connect the battery (and/or open the WP screw).
+ let s = if dis { "dis" } else { "" };
+ // Print a failure message, but not on the first try.
+ let mut fail_msg = None;
+ while dis == get_hardware_wp()? {
+ if let Some(msg) = fail_msg {
+ eprintln!("{msg}");
+ }
+ fail_msg = Some(format!("Hardware write protect is still {}!", !dis));
+ // The following message is read by the tast test. Do not modify.
+ info!("Prompt for hardware WP {}able", s);
+ eprintln!(" > {}connect the battery (and/or open the WP screw)", s);
+ pause();
+ }
+ Ok(())
+}
+
+pub fn ac_power_warning() {
+ info!("*****************************");
+ info!("AC power *must be* connected!");
+ info!("*****************************");
+ pause();
+}
+
+fn pause() {
+ // The following message is read by the tast test. Do not modify.
+ println!("Press enter to continue...");
+ // Rust stdout is always LineBuffered at time of writing.
+ // But this is not guaranteed, so flush anyway.
+ std::io::stdout().flush().unwrap();
+ // This reads one line, there is no guarantee the line came
+ // after the above prompt. But it is good enough.
+ if std::io::stdin().read_line(&mut String::new()).unwrap() == 0 {
+ panic!("stdin closed");
+ }
+}
+
+pub fn get_hardware_wp() -> std::result::Result<bool, String> {
+ let wp_s_val = collect_crosssystem(&["wpsw_cur"])?.parse::<u32>();
+ match wp_s_val {
+ Ok(v) => {
+ if v == 1 {
+ Ok(true)
+ } else if v == 0 {
+ Ok(false)
+ } else {
+ Err("Unknown write protect value".into())
+ }
+ }
+ Err(_) => Err("Cannot parse write protect value".into()),
+ }
+}
+
+pub fn collect_crosssystem(args: &[&str]) -> Result<String, String> {
+ let cmd = match Command::new("crossystem").args(args).output() {
+ Ok(x) => x,
+ Err(e) => return Err(format!("Failed to run crossystem: {}", e)),
+ };
+
+ if !cmd.status.success() {
+ return Err(translate_command_error(&cmd).to_string());
+ };
+
+ Ok(String::from_utf8_lossy(&cmd.stdout).into_owned())
+}
+
+pub fn translate_command_error(output: &std::process::Output) -> std::io::Error {
+ use std::io::{Error, ErrorKind};
+ // There is two cases on failure;
+ // i. ) A bad exit code,
+ // ii.) A SIG killed us.
+ match output.status.code() {
+ Some(code) => {
+ let e = format!(
+ "{}\nExited with error code: {}",
+ String::from_utf8_lossy(&output.stderr),
+ code
+ );
+ Error::new(ErrorKind::Other, e)
+ }
+ None => Error::new(
+ ErrorKind::Other,
+ "Process terminated by a signal".to_string(),
+ ),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn construct_layout_file() {
+ use super::{construct_layout_file, get_layout_sizes};
+
+ let mut buf = Vec::new();
+ construct_layout_file(
+ &mut buf,
+ &get_layout_sizes(0x10000).expect("64k is a valid chip size"),
+ )
+ .expect("no I/O errors expected");
+
+ assert_eq!(
+ &buf[..],
+ &b"000000:3fff BOTTOM_QUAD\n\
+ 000000:7fff BOTTOM_HALF\n\
+ 8000:ffff TOP_HALF\n\
+ c000:ffff TOP_QUAD\n"[..]
+ );
+ }
+
+ #[test]
+ fn get_layout_sizes() {
+ use super::get_layout_sizes;
+
+ assert_eq!(
+ get_layout_sizes(-128).err(),
+ Some("invalid rom size provided".into())
+ );
+
+ assert_eq!(
+ get_layout_sizes(3 << 20).err(),
+ Some("invalid rom size, not a power of 2".into())
+ );
+
+ assert_eq!(
+ get_layout_sizes(64 << 10).unwrap(),
+ LayoutSizes {
+ half_sz: 0x8000,
+ quad_sz: 0x4000,
+ rom_top: 0xFFFF,
+ bottom_half_top: 0x7FFF,
+ bottom_quad_top: 0x3FFF,
+ top_quad_bottom: 0xC000,
+ }
+ );
+ }
+}
diff --git a/util/z60_flashrom.rules b/util/flashrom_udev.rules
index 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 168dd6320..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 $(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 6f13749b2..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]);
@@ -225,8 +230,24 @@ int main(int argc, char *argv[])
else if ((strcmp(csn, "300") == 0) ||
(strcmp(csn, "cannon") == 0))
cs = CHIPSET_300_SERIES_CANNON_POINT;
+ else if ((strcmp(csn, "400") == 0) ||
+ (strcmp(csn, "comet") == 0))
+ cs = CHIPSET_400_SERIES_COMET_POINT;
+ else if ((strcmp(csn, "500") == 0) ||
+ (strcmp(csn, "tiger") == 0))
+ cs = CHIPSET_500_SERIES_TIGER_POINT;
+ else if (strcmp(csn, "600") == 0)
+ cs = CHIPSET_600_SERIES_ALDER_POINT;
else if (strcmp(csn, "apollo") == 0)
cs = CHIPSET_APOLLO_LAKE;
+ else if (strcmp(csn, "gemini") == 0)
+ cs = CHIPSET_GEMINI_LAKE;
+ else if (strcmp(csn, "jasper") == 0)
+ cs = CHIPSET_JASPER_LAKE;
+ else if (strcmp(csn, "elkhart") == 0)
+ cs = CHIPSET_ELKHART_LAKE;
+ else if (strcmp(csn, "meteor") == 0)
+ cs = CHIPSET_METEOR_LAKE;
}
ret = read_ich_descriptors_from_dump(buf, len, &cs, &desc);
@@ -248,7 +269,8 @@ int main(int argc, char *argv[])
prettyprint_ich_descriptors(cs, &desc);
pMAC = (uint8_t *) &buf[ICH_FREG_BASE(desc.region.FLREGs[3]) >> 2];
- if (len >= ICH_FREG_BASE(desc.region.FLREGs[3]) + 6 && pMAC[0] != 0xff)
+ /* The case len < 0 is handled above as error. At this point len is non-negative. */
+ if (((size_t) len) >= ICH_FREG_BASE(desc.region.FLREGs[3]) + 6 && pMAC[0] != 0xff)
printf("The MAC address might be at offset 0x%x: "
"%02x:%02x:%02x:%02x:%02x:%02x\n",
ICH_FREG_BASE(desc.region.FLREGs[3]),
diff --git a/util/ich_descriptors_tool/meson.build b/util/ich_descriptors_tool/meson.build
index 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/README.md b/util/ubertest/README.md
new file mode 100644
index 000000000..727d6e036
--- /dev/null
+++ b/util/ubertest/README.md
@@ -0,0 +1,175 @@
+# Ubertest
+## Intro / Features
+
+Ubertest is a blackbox regression test for flashrom. It requires two flashrom binaries, one of which is assumed to be an older or stable version and the other which is the new version being tested.
+
+Tests can be run on the local host or on a remote machine (via SSH). Additionally, the ubertest script supports using a secondary programmer to check the result of the primary programmer.
+
+Ubertest supports multiple modes of operation which allow regions to be flashed selectively based on presence of a layout file or integrated layout mapping:
+- Clobber mode: The entire ROM can be used for testing.
+- Layout mode: Target a region using a flashrom layout file.
+- Descriptor mode: Target a region using an Intel flash descriptor (Intel systems)
+- Flashmap mode: Target a region using a flashmap
+
+The tests assume that we care about 4KiB and 64KiB sector/block sizes. Some large ROMs available today only support 64KiB block erase operations, however testing on large regions can be impractical for many devices. The `--small-region` option may be used to restrict tests to using a smaller amount of flash memory (currently 16KiB).
+
+### Terminology
+
+- DUT: Device under test
+- Local host: The machine which is running the script. It might also be the DUT.
+- Remote machine: DUT controlled via SSH with primary programmer.
+Programmers
+ - Primary programmer: The programmer which will be tested using the new version of flashrom.
+ - Secondary programmer: An optional programmer which will be used along with the old version of flashrom to verify the result of the primary programmer. This is generally assumed to be an external programmer connected to the local host e.g. via USB or serial. It may also be a secondary internal interface, for example if the DUT has a SPI ROM and both linux_spi and linux_mtd support and the developer wishes to test one against the other.
+
+## Basic Usage
+
+For a full list of options use `-h/--help`.
+
+### Mandatory Options
+
+`-n/--new <path>`: New (unstable) flashrom binary to test. Also see Note 1 below.
+
+`-t/--type <type>`: Type of test to run:
+- Single test: A single test of the specified mode will be run. If this type is selected, then a mode must also be specified.
+- Endurance test: The ROM will be erased/written with random content until the first failure.
+
+`-p/--primary-programmer`: Primary programmer options, e.g. “internal”, “ch341a_spi”, etc.
+
+**Note 1**: When a remote host is used these options refer to files that are on the remote host, not the local host. See Testing on a Remote Host/DUT below.
+
+**Note 2**: The script will default to using ${PATH}/flashrom if no old (presumably stable) flashrom binary is specified using -o/--old.
+
+### Other Options
+
+`-b/--backup-image`: Backup image that will be flashed to the target ROM unconditionally when the script is done. If unspecified, the script will read the ROM first using the stable flashrom binary to obtain a backup image. If remote host is used, backup image will be copied to local host’s temporary directory in case something goes terribly wrong. Also see Note 1 below.
+
+`-o/--old <path>`: Old (stable) flashrom binary to test.
+
+`--custom-hooks`: This allows the user to specify a script containing functions which are run at certain points during ubertest. These functions are intended to adapt the test script to the user’s environment. For example, the user can supply their own `preflash_hook()` and `postflash_hook()` to toggle power enable for the flash chip, instruct other devices with access to the flash chip to suspend access, etc.
+
+`--local-flashrom`: If a remote host and a secondary programmer are used, then a local copy of flashrom is used to control the secondary programmer. This is because the “old” and “new” binaries reside on the remote host, and the secondary programmer is controlled from the local host. This defaults to `sudo which flashrom`.
+
+`-m/--mode <mode>`: Mode to operate in (Mandatory if single test type is chosen):
+- Clobber mode: Any portion of the ROM may be used for testing.
+- Layout mode: Portions specified using a layout file will be erased/written. A layout file must be provided using -l/--layout-file option. The user may specify a region to use for testing with the --layout-region option.
+- Descriptor mode (Intel systems only): Flashrom will read the flash descriptor from the DUT’s firmware ROM to determine which region to write to. The user may specify a region to use for testing with the --descriptor-region option.
+- Flashmap mode: Flashrom will read the flashmap from the DUT’s firmware ROM to determine which region to write to. The user may specify a region to use for testing with the --flashmap-region option.
+
+`--no-clean`: Do not remove temporary files if the script succeeds. Temporary files remain in the event of failure whether or not this option is used. This option is intended for developers for debugging the script.
+
+`--skip-consistency-check`: By default, the script will read the ROM twice as a sanity check for consistent readings. This option is intended for developers for faster iteration, but should not be used in most cases.
+
+`--small-region`: Skip parts of the tests that require a large (>16KiB currently ) region to work with. This is useful for small or densely packed firmware ROMs (especially those on microcontrollers/ECs) that do not have space for the default set of tests which may assume minimum eraseblock size of 64KB.
+
+`-v/--voltage`: Supply voltage (in millivolts) for chip.
+
+## Testing on a Remote Host/DUT
+
+Ubertest supports testing on a remote host via SSH. A remote host is specified by IP address or hostname using the `-r/--remote-host` option. If used, the remote host becomes the DUT. The remote host should accept non-interactive root login from the local host.
+
+Remote testing modifies the meaning of arguments supplied to some options. In particular, paths specified to options such as `-n`, `-o`, `-l`, and `-b` will refer to files on the remote host’s filesystem instead of the local host’s filesystem.
+
+To use a remote host as your DUT, use the following options:
+
+`-r/--remote-host <host>`: The remote host by IP address or hostname.
+
+`--ssh-port <port>`: Specify SSH port to use (default: 22).
+
+## Testing With a Secondary Programmer
+
+Ubertest can be used with up to two programmers. The secondary programmer is used with the old/stable version of flashrom to verify the primary programmer and new version of flashrom at each step.
+
+TODO: Change “secondary programmer” to “external programmer”? That could simplify the script significantly, especially in cases where a remote host + external programmer is used.
+
+To use a secondary programmer, use the following options:
+`-s/--secondary-programmer <parameters>`: Specify the secondary programmer the same way you do with -p/--primary-programmer.
+
+## Test methods / examples
+
+The purpose of ubertest is to automate a few simple tests in a way that can be invoked in a variety of environments (see Configurations / Setup).
+
+The “single” mode tests boils down to:
+- Read the targeted section of the ROM twice to check for consistency.
+- Test 4KiB block write capabilities:
+- Write the lower half of a block and ensuring that data in the upper half is restored to its original state.
+- Write over the boundary of two blocks, ensuring the data outside the targeted region is restored to its original state.
+- Write the upper half of a block, ensuring that data in the lower half restored to its original state
+- Write a full block.
+- Repeat step 2 for 64KiB blocks unless `--small-region` is used.
+
+### Configurations / Setup
+There are a few possible setups (shown below) involving a local machine, a remote machine, an external programmer, and the ROM to be tested. For the example commands in each setup the command takes a set of options, `MY_OPTS`. Depending on the type of test and potentially the mode, `MY_OPTS` will be different. To get the correct `MY_OPTS` for a specific test (and mode), see _Examples (Basic)_ and _Examples (Advanced)_ below. In general, any test type/mode can be run in any configuration.
+
+#### Targeting external ROM connected to an external programmer
+
+`flashrom$ sh util/ubertest/ubertest.sh -p ext_prog $MY_OPTS`
+
+![external programmer, external ROM](drawing_external_programmer_external_rom.svg)
+
+#### Targeting local host’s ROM using internal programmer
+
+`flashrom$ sh util/ubertest/ubertest.sh -p internal $MY_OPTS`
+
+![local programmer is dut](drawing_local_programmer_is_dut.svg)
+
+#### Targeting remote host's ROM using its internal programmer
+
+`flashrom$ sh util/ubertest/ubertest.sh -r remote_host -p internal $MY_OPTS`
+
+![remote host is remote dut](drawing_remote_host_is_remote_dut.svg)
+
+#### Targeting local host’s ROM using external programmer
+
+`flashrom$ sh util/ubertest/ubertest.sh -p internal -s ext_prog $MY_OPTS`
+
+![external programmer targeting local dut](drawing_external_programmer_local_dut.svg)
+
+#### Targeting remote host's ROM using its internal programmer, and checking the result using an external (secondary) programmer
+`flashrom # sh util/ubertest/ubertest.sh -p internal -s ext_prog -r remote_host $MY_OPTS`
+
+In this configuration the "old" and "new" flashrom binaries will be on the remote host. However in order to control the external programmer that will be used to check the result, a local flashrom binary must be supplied using `--local-flashrom`.
+
+![external programmer targeting external dut](drawing_external_programmer_remote_dut.svg)
+
+### Examples (Basic)
+#### Single Test
+
+For each region layout mode, we will assume a different set of common options in `MY_OPTS`. For details on the possible configurations, as well as their corresponding example commands, see Configurations / Setup above.
+
+#### Clobber Mode
+
+`MY_OPTS=”-n new_flashrom -o old_flashrom -t single -m clobber”`
+
+The clobber mode test will clobber (no surprise there!) the ROM with random content. If this operation is successful, then a partial write test will be run on the entire chip in order to test corner cases.
+
+#### Layout Mode
+
+`MY_OPTS=”-n new_flashrom -o old_flashrom -t single -m layout -l layout.txt -layout-region RW”`
+
+#### Descriptor Mode
+
+`MY_OPTS=”-n new_flashrom -o old_flashrom -t single -m descriptor --descriptor-region bios”`
+
+#### Flashmap Mode
+
+`MY_OPTS=”-n new_flashrom -o old_flashrom -t single -m flashmap --flashmap-region RW_SECTION”`
+
+### Examples (Advanced)
+
+For the following test types, we will assume the set of common options `MY_OPTS`. For details on each configuration, as well as the corresponding example command, see Configurations / Setup above.
+
+##### Endurance Test
+`MY_OPTS=”-n new_flashrom -o old_flashrom -t endurance”`
+
+The endurance test will repeatedly run single Clobber Mode tests until the first failure. This is intended to test the capabilities of the flash chip. It is important to note that a full test can take a while (potentially more than a week or two) to complete. The average time for one iteration of the endurance test using a remote host with a SPI ROM operating at 50MHz is around 100 seconds. This process will likely take longer for boards with larger chip sizes, and also longer for configurations that use an external programmer. At 1 iteration every 100 seconds, which is a relatively high speed, around 6048 iterations will be run in a week (probably a little less than that because the time spent between iterations will accumulate to a significant amount).
+
+
+## Document Information
+Authors: David Hendricks <dhendrix@chromium.org>, Souvik Ghosh
+
+Original document short URL: <https://goo.gl/3jNoL7>
+
+Original document long URL: <https://docs.google.com/document/d/1IoBR7rHXJFiahC6dGnNKqaNFfg-q89_NMHuFyiJ7JnA/edit?usp=sharing>
+
+This document is licensed under a Creative Commons Attribution 3.0 Unported License. Feel free to copy, modify, and distribute this document however you see fit with attribution to the Flashrom Community.
diff --git a/util/ubertest/cmd.sh b/util/ubertest/cmd.sh
new file mode 100644
index 000000000..1d1c47a1c
--- /dev/null
+++ b/util/ubertest/cmd.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+#
+# This file is part of the flashrom project. It is derived from
+# board_status.sh in coreboot.
+#
+# Copyright (C) 2016 Google Inc.
+# Copyright (C) 2014 Sage Electronic Engineering, LLC.
+
+USE_CUSTOM_HOOKS=0
+if [ -n "$CUSTOM_HOOKS_FILENAME" ]; then
+ USE_CUSTOM_HOOKS=1
+ . "$CUSTOM_HOOKS_FILENAME"
+ if [ $? -ne 0 ]; then
+ echo "Failed to source custom hooks file."
+ exit $EXIT_FAILURE
+ fi
+
+ if ! custom_hook_sanity_check; then
+ echo "Failed to run sanity check for custom hooks."
+ exit $EXIT_FAILURE
+ fi
+fi
+
+# test a command
+#
+# $1: 0 ($LOCAL) to run command locally,
+# 1 ($REMOTE) to run remotely if remote host defined
+# $2: command to test
+# $3: 0 ($FATAL) Exit with an error if the command fails
+# 1 ($NONFATAL) Don't exit on command test failure
+test_cmd()
+{
+ local rc
+ local cmd__="$(echo $2 | cut -d ' ' -f -1)"
+ local args="$(echo $2 | cut -d ' ' -f 2-)"
+
+ if [ -e "$cmd__" ]; then
+ return
+ fi
+
+ if [ "$1" -eq "$REMOTE" ] && [ -n "$REMOTE_HOST" ]; then
+ ssh $REMOTE_PORT_OPTION root@${REMOTE_HOST} command -v "$cmd__" $args > /dev/null 2>&1
+ rc=$?
+ else
+ command -v "$cmd__" $args >/dev/null 2>&1
+ rc=$?
+ fi
+
+ if [ $rc -eq 0 ]; then
+ return 0
+ fi
+
+ if [ "$3" = "1" ]; then
+ return 1
+ fi
+
+ echo "$2 not found"
+ exit $EXIT_FAILURE
+}
+
+# Same args as cmd() but with the addition of $4 which determines if the
+# command should be totally silenced or not.
+_cmd()
+{
+ local silent=$4
+ local rc=0
+
+ if [ -n "$3" ]; then
+ pipe_location="${3}"
+ else
+ pipe_location="/dev/null"
+ fi
+
+ if [ $1 -eq $REMOTE ] && [ -n "$REMOTE_HOST" ]; then
+ if [ $silent -eq 0 ]; then
+ ssh $REMOTE_PORT_OPTION "root@${REMOTE_HOST}" "$2" > "$pipe_location" 2>/dev/null
+ rc=$?
+ else
+ ssh $REMOTE_PORT_OPTION "root@${REMOTE_HOST}" "$2" >/dev/null 2>&1
+ rc=$?
+ fi
+ else
+ if [ $USE_CUSTOM_HOOKS -eq 1 ]; then
+ preflash_hook $1 "$2" "$3" $4
+ fi
+
+ if [ $silent -eq 0 ]; then
+ $SUDO_CMD $2 > "$pipe_location" 2>/dev/null
+ rc=$?
+ else
+ $SUDO_CMD $2 >/dev/null 2>&1
+ rc=$?
+ fi
+
+ if [ $USE_CUSTOM_HOOKS -eq 1 ]; then
+ postflash_hook $1 "$2" "$3" $4
+ fi
+ fi
+
+ return $rc
+}
+
+# run a command
+#
+# $1: 0 ($LOCAL) to run command locally,
+# 1 ($REMOTE) to run remotely if remote host defined
+# $2: command
+# $3: filename to direct output of command into
+cmd()
+{
+ _cmd $1 "$2" "$3" 0
+
+ if [ $? -eq 0 ]; then
+ return
+ fi
+
+ echo "Failed to run \"$2\", aborting"
+ rm -f "$3" # don't leave an empty file
+ return $EXIT_FAILURE
+}
+
+# run a command silently
+#
+# $1: 0 ($LOCAL) to run command locally,
+# 1 ($REMOTE) to run remotely if remote host defined
+# $2: command
+scmd()
+{
+ _cmd $1 "$2" "" 1
+
+ if [ $? -eq 0 ]; then
+ return
+ fi
+
+ echo "Failed to run \"$2\", aborting"
+ return $EXIT_FAILURE
+}
diff --git a/util/ubertest/drawing_external_programmer_external_rom.svg b/util/ubertest/drawing_external_programmer_external_rom.svg
new file mode 100644
index 000000000..7af31579a
--- /dev/null
+++ b/util/ubertest/drawing_external_programmer_external_rom.svg
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ viewBox="0 0 611.22791 199.54794"
+ stroke-miterlimit="10"
+ id="svg3802"
+ sodipodi:docname="drawing_external_programmer.svg"
+ width="611.22791"
+ height="199.54794"
+ style="fill:none;stroke:none;stroke-linecap:square;stroke-miterlimit:10"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata3808">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs3806" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1295"
+ inkscape:window-height="1058"
+ id="namedview3804"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="1.1125147"
+ inkscape:cx="256.65593"
+ inkscape:cy="88.451203"
+ inkscape:window-x="2121"
+ inkscape:window-y="731"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg3802" />
+ <clipPath
+ id="p.0">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path3761"
+ inkscape:connector-curvature="0"
+ style="clip-rule:nonzero" />
+ </clipPath>
+ <g
+ clip-path="url(#p.0)"
+ id="g3800"
+ transform="translate(-179.7729,-178.43253)">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path3764"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="M 187,188 H 356.98425 V 371.02362 H 187 Z"
+ id="path3766"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 187,188 H 356.98425 V 371.02362 H 187 Z"
+ id="path3768"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 205,188 h 142.99213 v 54.99213 H 205 Z"
+ id="path3770"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 215.35938,214.92 v -13.35938 h 1.78125 v 11.78125 h 6.5625 V 214.92 Z m 9.64134,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 15.61009,1.29687 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 9.32813,2.35938 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 4.15697,4.85938 v -13.35938 h 1.64061 V 214.92 Z m 9.37499,0 v -9.67188 h 1.46875 v 1.35938 q 0.45312,-0.71875 1.20312,-1.14063 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 214.92 h -1.64062 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70313 -0.42187,-0.26562 -0.98437,-0.26562 -1.01563,0 -1.6875,0.6875 -0.67188,0.67187 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85938,1.0625 -0.25,0.70313 -0.25,2.03125 V 214.92 Z m 21.85333,-1.1875 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 10.51633,1.3125 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 3.01563,3.54688 v -13.35938 h 1.64062 v 4.79688 q 1.14063,-1.32813 2.89063,-1.32813 1.07812,0 1.85937,0.42188 0.79688,0.42187 1.14063,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64063 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51562,-0.5625 -0.71875,0 -1.35938,0.39063 -0.64062,0.375 -0.92187,1.01562 -0.26563,0.64063 -0.26563,1.78125 V 214.92 Z m 10.3757,-11.46875 v -1.89063 h 1.64062 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64062 V 214.92 Z m 4.14483,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82813 0.375,0.5 0.51563,1.20312 0.0937,0.45313 0.0937,1.59375 V 214.92 h -1.64063 v -5.89063 q 0,-1 -0.20312,-1.48437 -0.1875,-0.5 -0.67188,-0.79688 -0.48437,-0.29687 -1.14062,-0.29687 -1.04688,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 214.92 Z m 17.00074,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48438,2.3125 -1.07812,0.8125 -2.76562,0.8125 -2.125,0 -3.375,-1.29688 -1.23438,-1.3125 -1.23438,-3.67187 0,-2.45313 1.25,-3.79688 1.26563,-1.34375 3.26563,-1.34375 1.9375,0 3.15625,1.32813 1.23437,1.3125 1.23437,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01563,0.84375 0.90625,0 1.54687,-0.46875 0.64063,-0.48438 1.01563,-1.51563 z m -5.39063,-2.65625 h 5.40625 q -0.10937,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76562,0.75 -0.84375,2.01562 z"
+ id="path3772"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 221,252 h 110.99213 v 68 H 221 Z"
+ id="path3774"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 640.0079,245.49606 h 142.99213 v 60 H 640.0079 Z"
+ id="path3776"
+ inkscape:connector-curvature="0"
+ style="fill:#fff2cc;fill-rule:evenodd" />
+ <path
+ d="m 640.0079,245.49606 h 142.99213 v 60 H 640.0079 Z"
+ id="path3778"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 674.49603,254 h 71.49609 v 42.99213 h -71.49609 z"
+ id="path3780"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="M 684.9648,280.91998 V 267.5606 h 5.92187 q 1.78125,0 2.70313,0.35938 0.9375,0.35937 1.48437,1.28125 0.5625,0.90625 0.5625,2.01562 0,1.40625 -0.92187,2.39063 -0.92188,0.96875 -2.84375,1.23437 0.70312,0.34375 1.07812,0.67188 0.76563,0.70312 1.45313,1.76562 l 2.32812,3.64063 h -2.21875 l -1.76562,-2.78125 q -0.78125,-1.20313 -1.28125,-1.82813 -0.5,-0.64062 -0.90625,-0.89062 -0.39063,-0.26563 -0.79688,-0.35938 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45313 h 3.79688 q 1.21875,0 1.89062,-0.25 0.6875,-0.26562 1.04688,-0.8125 0.35937,-0.54687 0.35937,-1.1875 0,-0.95312 -0.6875,-1.5625 -0.6875,-0.60937 -2.1875,-0.60937 h -4.21875 z m 11.14484,0.95313 q 0,-3.32813 1.78125,-5.20313 1.78125,-1.89062 4.60938,-1.89062 1.84375,0 3.32812,0.89062 1.48438,0.875 2.26563,2.46875 0.78125,1.57813 0.78125,3.57813 0,2.03125 -0.82813,3.64062 -0.8125,1.59375 -2.3125,2.42188 -1.5,0.82812 -3.25,0.82812 -1.875,0 -3.35937,-0.90625 -1.48438,-0.92187 -2.25,-2.5 -0.76563,-1.57812 -0.76563,-3.32812 z m 1.8125,0.0156 q 0,2.42188 1.29688,3.8125 1.29687,1.39063 3.26562,1.39063 2,0 3.28125,-1.40625 1.28125,-1.40625 1.28125,-3.98438 0,-1.625 -0.54687,-2.84375 -0.54688,-1.21875 -1.60938,-1.875 -1.0625,-0.67187 -2.375,-0.67187 -1.89062,0 -3.25,1.29687 -1.34375,1.28125 -1.34375,4.28125 z m 13.18329,6.48438 V 267.5606 h 2.65625 l 3.15625,9.45313 q 0.4375,1.32812 0.64062,1.98437 0.23438,-0.73437 0.70313,-2.14062 l 3.20312,-9.29688 h 2.375 v 13.35938 H 723.9023 V 269.7481 l -3.875,11.17188 h -1.59375 l -3.85938,-11.375 v 11.375 z"
+ id="path3782"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 425,251.84805 v 0 c 0,-7.63937 6.19293,-13.83231 13.83231,-13.83231 h 119.32754 c 3.66852,0 7.18683,1.45733 9.78089,4.0514 2.59405,2.59405 4.05139,6.11235 4.05139,9.78091 v 55.32751 c 0,7.63938 -6.19294,13.83231 -13.83228,13.83231 H 438.83231 C 431.19293,321.00787 425,314.81494 425,307.17556 Z"
+ id="path3784"
+ inkscape:connector-curvature="0"
+ style="fill:#ead1dc;fill-rule:evenodd" />
+ <path
+ d="m 425,251.84805 v 0 c 0,-7.63937 6.19293,-13.83231 13.83231,-13.83231 h 119.32754 c 3.66852,0 7.18683,1.45733 9.78089,4.0514 2.59405,2.59405 4.05139,6.11235 4.05139,9.78091 v 55.32751 c 0,7.63938 -6.19294,13.83231 -13.83228,13.83231 H 438.83231 C 431.19293,321.00787 425,314.81494 425,307.17556 Z"
+ id="path3786"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 465.745,275.43182 v -13.35937 h 9.65625 v 1.57812 h -7.875 v 4.09375 h 7.375 v 1.5625 h -7.375 v 4.54688 h 8.1875 v 1.57812 z m 11.11545,0 3.53125,-5.03125 -3.26563,-4.64062 h 2.04688 l 1.48437,2.26562 q 0.42188,0.64063 0.67188,1.07813 0.40625,-0.59375 0.73437,-1.0625 l 1.64063,-2.28125 h 1.95312 l -3.34375,4.54687 3.59375,5.125 h -2.01562 l -1.98438,-3 -0.51562,-0.8125 -2.54688,3.8125 z m 14,-1.46875 0.23437,1.45313 q -0.6875,0.14062 -1.23437,0.14062 -0.89063,0 -1.39063,-0.28125 -0.48437,-0.28125 -0.6875,-0.73437 -0.20312,-0.46875 -0.20312,-1.9375 v -5.57813 h -1.20313 v -1.26562 h 1.20313 v -2.39063 l 1.625,-0.98437 v 3.375 h 1.65625 v 1.26562 h -1.65625 v 5.67188 q 0,0.6875 0.0781,0.89062 0.0937,0.20313 0.28125,0.32813 0.20313,0.10937 0.57813,0.10937 0.26562,0 0.71875,-0.0625 z m 8.23016,-1.64062 1.6875,0.20312 q -0.40625,1.48438 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29687 -1.23437,-1.3125 -1.23437,-3.67188 0,-2.45312 1.25,-3.79687 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32812 1.23438,1.3125 1.23438,3.70313 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45312 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48437 1.01562,-1.51562 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82813 -0.78125,-0.95312 -2.03125,-0.95312 -1.125,0 -1.90625,0.76562 -0.76563,0.75 -0.84375,2.01563 z m 9.12573,5.76562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48437,-0.32812 1.0625,-0.32812 0.82812,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60938,-0.35937 -1.20313,-0.35937 -0.54687,0 -0.96875,0.32812 -0.42187,0.32813 -0.60937,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 6.2283,0 v -9.67187 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82812 0.375,0.5 0.51563,1.20313 0.0937,0.45312 0.0937,1.59375 v 5.95312 h -1.64063 v -5.89062 q 0,-1 -0.20312,-1.48438 -0.1875,-0.5 -0.67188,-0.79687 -0.48437,-0.29688 -1.14062,-0.29688 -1.04688,0 -1.8125,0.67188 -0.75,0.65625 -0.75,2.51562 v 5.28125 z m 16.68823,-1.1875 q -0.92187,0.76563 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98437 0,-0.71875 0.32813,-1.29688 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35937 1.1875,-0.54687 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23438 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42188 0,-1 -0.46875,-1.42187 -0.625,-0.54688 -1.875,-0.54688 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42188 l -1.60938,-0.21875 q 0.21875,-1.01563 0.71875,-1.64063 0.5,-0.64062 1.45313,-0.98437 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29687 0.78125,0.28125 1.14063,0.73438 0.375,0.4375 0.51562,1.10937 0.0781,0.42188 0.0781,1.51563 v 2.1875 q 0,2.28125 0.10937,2.89062 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51562 -0.32813,-1.1875 z m -0.14062,-3.67187 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14062 -1.4375,0.32812 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42187 1.09375,-1.14062 0.26563,-0.5625 0.26563,-1.64063 z m 4.15692,4.85937 v -13.35937 h 1.64062 v 13.35937 z"
+ id="path3788"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 448.10052,297.43182 v -13.35937 h 5.04688 q 1.32812,0 2.03125,0.125 0.96875,0.17187 1.64062,0.64062 0.67188,0.45313 1.07813,1.28125 0.40625,0.82813 0.40625,1.82813 0,1.70312 -1.09375,2.89062 -1.07813,1.17188 -3.92188,1.17188 h -3.42187 v 5.42187 z m 1.76563,-7 h 3.45312 q 1.71875,0 2.4375,-0.64062 0.71875,-0.64063 0.71875,-1.79688 0,-0.84375 -0.42187,-1.4375 -0.42188,-0.59375 -1.125,-0.78125 -0.4375,-0.125 -1.64063,-0.125 h -3.42187 z m 10.45919,7 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48438,-0.32812 1.0625,-0.32812 0.82813,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60937,-0.35937 -1.20312,-0.35937 -0.54688,0 -0.96875,0.32812 -0.42188,0.32813 -0.60938,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 5.61893,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07812 3.04687,-1.07812 2,0 3.26563,1.3125 1.26562,1.29687 1.26562,3.60937 0,1.85938 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29687 -1.25,-1.3125 -1.25,-3.76563 z m 1.6875,0 q 0,1.85938 0.79688,2.79688 0.8125,0.92187 2.04687,0.92187 1.21875,0 2.03125,-0.92187 0.8125,-0.9375 0.8125,-2.84375 0,-1.79688 -0.8125,-2.71875 -0.8125,-0.92188 -2.03125,-0.92188 -1.23437,0 -2.04687,0.92188 -0.79688,0.90625 -0.79688,2.76562 z m 8.98508,5.64063 1.59375,0.23437 q 0.10937,0.75 0.5625,1.07813 0.60937,0.45312 1.67187,0.45312 1.14063,0 1.75,-0.45312 0.625,-0.45313 0.84375,-1.26563 0.125,-0.5 0.10938,-2.10937 -1.0625,1.26562 -2.67188,1.26562 -2,0 -3.09375,-1.4375 -1.09375,-1.4375 -1.09375,-3.45312 0,-1.39063 0.5,-2.5625 0.51563,-1.17188 1.45313,-1.79688 0.95312,-0.64062 2.25,-0.64062 1.70312,0 2.8125,1.375 v -1.15625 h 1.51562 v 8.35937 q 0,2.26563 -0.46875,3.20313 -0.45312,0.9375 -1.45312,1.48437 -0.98438,0.54688 -2.45313,0.54688 -1.71875,0 -2.79687,-0.78125 -1.0625,-0.76563 -1.03125,-2.34375 z m 1.35937,-5.8125 q 0,1.90625 0.75,2.78125 0.76563,0.875 1.90625,0.875 1.125,0 1.89063,-0.85938 0.76562,-0.875 0.76562,-2.73437 0,-1.78125 -0.79687,-2.67188 -0.78125,-0.90625 -1.89063,-0.90625 -1.09375,0 -1.85937,0.89063 -0.76563,0.875 -0.76563,2.625 z m 9.31323,5.01562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48438,-0.32812 1.0625,-0.32812 0.82813,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60937,-0.35937 -1.20312,-0.35937 -0.54688,0 -0.96875,0.32812 -0.42188,0.32813 -0.60938,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 12.54081,-1.1875 q -0.92188,0.76563 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98437 0,-0.71875 0.32812,-1.29688 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35937 1.1875,-0.54687 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23438 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42188 0,-1 -0.46875,-1.42187 -0.625,-0.54688 -1.875,-0.54688 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42188 l -1.60937,-0.21875 q 0.21875,-1.01563 0.71875,-1.64063 0.5,-0.64062 1.45312,-0.98437 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29687 0.78125,0.28125 1.14062,0.73438 0.375,0.4375 0.51563,1.10937 0.0781,0.42188 0.0781,1.51563 v 2.1875 q 0,2.28125 0.10938,2.89062 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51562 -0.32812,-1.1875 z m -0.14063,-3.67187 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14062 -1.4375,0.32812 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42187 1.09375,-1.14062 0.26562,-0.5625 0.26562,-1.64063 z m 4.20383,4.85937 v -9.67187 h 1.46875 v 1.35937 q 0.45312,-0.71875 1.20312,-1.14062 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45312 0.6875,0.4375 0.96875,1.23438 1.15628,-1.6875 2.9844,-1.6875 1.45313,0 2.21875,0.79687 0.78125,0.79688 0.78125,2.45313 v 6.64062 h -1.64062 v -6.09375 q 0,-0.98437 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70312 -0.42187,-0.26563 -0.98437,-0.26563 -1.01563,0 -1.68753,0.6875 -0.67188,0.67188 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64062 -0.40625,-0.54688 -1.3125,-0.54688 -0.6875,0 -1.28125,0.35938 -0.59375,0.35937 -0.85938,1.0625 -0.25,0.70312 -0.25,2.03125 v 5.01562 z m 15.5408,0 v -9.67187 h 1.46875 v 1.35937 q 0.45312,-0.71875 1.20312,-1.14062 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45312 0.6875,0.4375 0.96875,1.23438 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79687 0.78125,0.79688 0.78125,2.45313 v 6.64062 h -1.64062 v -6.09375 q 0,-0.98437 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70312 -0.42187,-0.26563 -0.98437,-0.26563 -1.01563,0 -1.6875,0.6875 -0.67188,0.67188 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64062 -0.40625,-0.54688 -1.3125,-0.54688 -0.6875,0 -1.28125,0.35938 -0.59375,0.35937 -0.85938,1.0625 -0.25,0.70312 -0.25,2.03125 v 5.01562 z m 22.16583,-3.10937 1.6875,0.20312 q -0.40625,1.48438 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29687 -1.23437,-1.3125 -1.23437,-3.67188 0,-2.45312 1.25,-3.79687 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32812 1.23438,1.3125 1.23438,3.70313 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45312 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48437 1.01562,-1.51562 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82813 -0.78125,-0.95312 -2.03125,-0.95312 -1.125,0 -1.90625,0.76562 -0.76563,0.75 -0.84375,2.01563 z m 9.12567,5.76562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48437,-0.32812 1.0625,-0.32812 0.82812,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60938,-0.35937 -1.20313,-0.35937 -0.54687,0 -0.96875,0.32812 -0.42187,0.32813 -0.60937,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z"
+ id="path3790"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 357.49606,269.74802 h 55.49606 V 264 l 11.49607,11.49607 -11.49607,11.49606 v -5.74805 h -55.49606 z"
+ id="path3792"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="m 357.49606,269.74802 h 55.49606 V 264 l 11.49607,11.49607 -11.49607,11.49606 v -5.74805 h -55.49606 z"
+ id="path3794"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="M 572.50397,269.74802 H 628 V 264 L 639.49604,275.49607 628,286.99213 v -5.74805 h -55.49603 z"
+ id="path3796"
+ inkscape:connector-curvature="0"
+ style="fill:#ead1dc;fill-rule:evenodd" />
+ <path
+ d="M 572.50397,269.74802 H 628 V 264 L 639.49604,275.49607 628,286.99213 v -5.74805 h -55.49603 z"
+ id="path3798"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ </g>
+ <rect
+ id="rect3810"
+ width="611.22791"
+ height="199.54794"
+ x="0"
+ y="0" />
+ <rect
+ id="rect3812"
+ width="611.22791"
+ height="199.54794"
+ x="0"
+ y="0" />
+</svg>
diff --git a/util/ubertest/drawing_external_programmer_local_dut.svg b/util/ubertest/drawing_external_programmer_local_dut.svg
new file mode 100644
index 000000000..79d6fa3b3
--- /dev/null
+++ b/util/ubertest/drawing_external_programmer_local_dut.svg
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ viewBox="0 0 385.03391 185.09322"
+ stroke-miterlimit="10"
+ id="svg70"
+ sodipodi:docname="drawing_local_host_external_programmer_local_dut.svg"
+ width="385.03391"
+ height="185.09322"
+ style="fill:none;stroke:none;stroke-linecap:square;stroke-miterlimit:10"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata76">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs74" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1544"
+ inkscape:window-height="1153"
+ id="namedview72"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="1.5733333"
+ inkscape:cx="236.47285"
+ inkscape:cy="122.94451"
+ inkscape:window-x="1699"
+ inkscape:window-y="740"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg70" />
+ <clipPath
+ id="p.0">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path29"
+ inkscape:connector-curvature="0"
+ style="clip-rule:nonzero" />
+ </clipPath>
+ <g
+ clip-path="url(#p.0)"
+ id="g68"
+ transform="translate(-187,-188)">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path32"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="M 187,188 H 356.98425 V 371.02362 H 187 Z"
+ id="path34"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 187,188 H 356.98425 V 371.02362 H 187 Z"
+ id="path36"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 205,188 h 142.99213 v 54.99213 H 205 Z"
+ id="path38"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 215.35938,214.92 v -13.35938 h 1.78125 v 11.78125 h 6.5625 V 214.92 Z m 9.64134,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 15.61009,1.29687 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 9.32813,2.35938 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 4.15697,4.85938 v -13.35938 h 1.64061 V 214.92 Z m 9.37499,0 v -9.67188 h 1.46875 v 1.35938 q 0.45312,-0.71875 1.20312,-1.14063 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 214.92 h -1.64062 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70313 -0.42187,-0.26562 -0.98437,-0.26562 -1.01563,0 -1.6875,0.6875 -0.67188,0.67187 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85938,1.0625 -0.25,0.70313 -0.25,2.03125 V 214.92 Z m 21.85333,-1.1875 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 10.51633,1.3125 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 3.01563,3.54688 v -13.35938 h 1.64062 v 4.79688 q 1.14063,-1.32813 2.89063,-1.32813 1.07812,0 1.85937,0.42188 0.79688,0.42187 1.14063,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64063 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51562,-0.5625 -0.71875,0 -1.35938,0.39063 -0.64062,0.375 -0.92187,1.01562 -0.26563,0.64063 -0.26563,1.78125 V 214.92 Z m 10.3757,-11.46875 v -1.89063 h 1.64062 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64062 V 214.92 Z m 4.14483,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82813 0.375,0.5 0.51563,1.20312 0.0937,0.45313 0.0937,1.59375 V 214.92 h -1.64063 v -5.89063 q 0,-1 -0.20312,-1.48437 -0.1875,-0.5 -0.67188,-0.79688 -0.48437,-0.29687 -1.14062,-0.29687 -1.04688,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 214.92 Z m 17.00074,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48438,2.3125 -1.07812,0.8125 -2.76562,0.8125 -2.125,0 -3.375,-1.29688 -1.23438,-1.3125 -1.23438,-3.67187 0,-2.45313 1.25,-3.79688 1.26563,-1.34375 3.26563,-1.34375 1.9375,0 3.15625,1.32813 1.23437,1.3125 1.23437,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01563,0.84375 0.90625,0 1.54687,-0.46875 0.64063,-0.48438 1.01563,-1.51563 z m -5.39063,-2.65625 h 5.40625 q -0.10937,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76562,0.75 -0.84375,2.01562 z"
+ id="path40"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 221,252 h 110.99213 v 68 H 221 Z"
+ id="path42"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 200.49606,270 h 142.99213 v 60 H 200.49606 Z"
+ id="path44"
+ inkscape:connector-curvature="0"
+ style="fill:#fff2cc;fill-rule:evenodd" />
+ <path
+ d="m 200.49606,270 h 142.99213 v 60 H 200.49606 Z"
+ id="path46"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="M 234.98425,278.50394 H 309 v 42.99213 h -74.01575 z"
+ id="path48"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 245.453,305.42395 v -13.35938 h 5.92187 q 1.78125,0 2.70313,0.35938 0.9375,0.35937 1.48437,1.28125 0.5625,0.90625 0.5625,2.01562 0,1.40625 -0.92187,2.39063 -0.92188,0.96875 -2.84375,1.23437 0.70312,0.34375 1.07812,0.67188 0.76563,0.70312 1.45313,1.76562 l 2.32812,3.64063 h -2.21875 l -1.76562,-2.78125 q -0.78125,-1.20313 -1.28125,-1.82813 -0.5,-0.64062 -0.90625,-0.89062 -0.39063,-0.26563 -0.79688,-0.35938 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45313 h 3.79688 q 1.21875,0 1.89062,-0.25 0.6875,-0.26562 1.04688,-0.8125 0.35937,-0.54687 0.35937,-1.1875 0,-0.95312 -0.6875,-1.5625 -0.6875,-0.60937 -2.1875,-0.60937 h -4.21875 z m 11.14481,0.95313 q 0,-3.32813 1.78125,-5.20313 1.78125,-1.89062 4.60938,-1.89062 1.84375,0 3.32812,0.89062 1.48438,0.875 2.26563,2.46875 0.78125,1.57813 0.78125,3.57813 0,2.03125 -0.82813,3.64062 -0.8125,1.59375 -2.3125,2.42188 -1.5,0.82812 -3.25,0.82812 -1.875,0 -3.35937,-0.90625 -1.48438,-0.92187 -2.25,-2.5 -0.76563,-1.57812 -0.76563,-3.32812 z m 1.8125,0.0156 q 0,2.42188 1.29688,3.8125 1.29687,1.39063 3.26562,1.39063 2,0 3.28125,-1.40625 1.28125,-1.40625 1.28125,-3.98438 0,-1.625 -0.54687,-2.84375 -0.54688,-1.21875 -1.60938,-1.875 -1.0625,-0.67187 -2.375,-0.67187 -1.89062,0 -3.25,1.29687 -1.34375,1.28125 -1.34375,4.28125 z m 13.18332,6.48438 v -13.35938 h 2.65625 l 3.15625,9.45313 q 0.4375,1.32812 0.64062,1.98437 0.23438,-0.73437 0.70313,-2.14062 l 3.20312,-9.29688 h 2.375 v 13.35938 h -1.70312 v -11.17188 l -3.875,11.17188 h -1.59375 l -3.85938,-11.375 v 11.375 z"
+ id="path50"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 425,251.84805 v 0 c 0,-7.63937 6.19293,-13.83231 13.83231,-13.83231 h 119.32754 c 3.66852,0 7.18683,1.45733 9.78089,4.0514 2.59405,2.59405 4.05139,6.11235 4.05139,9.78091 v 55.32751 c 0,7.63938 -6.19294,13.83231 -13.83228,13.83231 H 438.83231 C 431.19293,321.00787 425,314.81494 425,307.17556 Z"
+ id="path52"
+ inkscape:connector-curvature="0"
+ style="fill:#ead1dc;fill-rule:evenodd" />
+ <path
+ d="m 425,251.84805 v 0 c 0,-7.63937 6.19293,-13.83231 13.83231,-13.83231 h 119.32754 c 3.66852,0 7.18683,1.45733 9.78089,4.0514 2.59405,2.59405 4.05139,6.11235 4.05139,9.78091 v 55.32751 c 0,7.63938 -6.19294,13.83231 -13.83228,13.83231 H 438.83231 C 431.19293,321.00787 425,314.81494 425,307.17556 Z"
+ id="path54"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 465.745,275.43182 v -13.35937 h 9.65625 v 1.57812 h -7.875 v 4.09375 h 7.375 v 1.5625 h -7.375 v 4.54688 h 8.1875 v 1.57812 z m 11.11545,0 3.53125,-5.03125 -3.26563,-4.64062 h 2.04688 l 1.48437,2.26562 q 0.42188,0.64063 0.67188,1.07813 0.40625,-0.59375 0.73437,-1.0625 l 1.64063,-2.28125 h 1.95312 l -3.34375,4.54687 3.59375,5.125 h -2.01562 l -1.98438,-3 -0.51562,-0.8125 -2.54688,3.8125 z m 14,-1.46875 0.23437,1.45313 q -0.6875,0.14062 -1.23437,0.14062 -0.89063,0 -1.39063,-0.28125 -0.48437,-0.28125 -0.6875,-0.73437 -0.20312,-0.46875 -0.20312,-1.9375 v -5.57813 h -1.20313 v -1.26562 h 1.20313 v -2.39063 l 1.625,-0.98437 v 3.375 h 1.65625 v 1.26562 h -1.65625 v 5.67188 q 0,0.6875 0.0781,0.89062 0.0937,0.20313 0.28125,0.32813 0.20313,0.10937 0.57813,0.10937 0.26562,0 0.71875,-0.0625 z m 8.23016,-1.64062 1.6875,0.20312 q -0.40625,1.48438 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29687 -1.23437,-1.3125 -1.23437,-3.67188 0,-2.45312 1.25,-3.79687 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32812 1.23438,1.3125 1.23438,3.70313 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45312 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48437 1.01562,-1.51562 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82813 -0.78125,-0.95312 -2.03125,-0.95312 -1.125,0 -1.90625,0.76562 -0.76563,0.75 -0.84375,2.01563 z m 9.12573,5.76562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48437,-0.32812 1.0625,-0.32812 0.82812,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60938,-0.35937 -1.20313,-0.35937 -0.54687,0 -0.96875,0.32812 -0.42187,0.32813 -0.60937,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 6.2283,0 v -9.67187 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82812 0.375,0.5 0.51563,1.20313 0.0937,0.45312 0.0937,1.59375 v 5.95312 h -1.64063 v -5.89062 q 0,-1 -0.20312,-1.48438 -0.1875,-0.5 -0.67188,-0.79687 -0.48437,-0.29688 -1.14062,-0.29688 -1.04688,0 -1.8125,0.67188 -0.75,0.65625 -0.75,2.51562 v 5.28125 z m 16.68823,-1.1875 q -0.92187,0.76563 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98437 0,-0.71875 0.32813,-1.29688 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35937 1.1875,-0.54687 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23438 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42188 0,-1 -0.46875,-1.42187 -0.625,-0.54688 -1.875,-0.54688 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42188 l -1.60938,-0.21875 q 0.21875,-1.01563 0.71875,-1.64063 0.5,-0.64062 1.45313,-0.98437 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29687 0.78125,0.28125 1.14063,0.73438 0.375,0.4375 0.51562,1.10937 0.0781,0.42188 0.0781,1.51563 v 2.1875 q 0,2.28125 0.10937,2.89062 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51562 -0.32813,-1.1875 z m -0.14062,-3.67187 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14062 -1.4375,0.32812 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42187 1.09375,-1.14062 0.26563,-0.5625 0.26563,-1.64063 z m 4.15692,4.85937 v -13.35937 h 1.64062 v 13.35937 z"
+ id="path56"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 448.10052,297.43182 v -13.35937 h 5.04688 q 1.32812,0 2.03125,0.125 0.96875,0.17187 1.64062,0.64062 0.67188,0.45313 1.07813,1.28125 0.40625,0.82813 0.40625,1.82813 0,1.70312 -1.09375,2.89062 -1.07813,1.17188 -3.92188,1.17188 h -3.42187 v 5.42187 z m 1.76563,-7 h 3.45312 q 1.71875,0 2.4375,-0.64062 0.71875,-0.64063 0.71875,-1.79688 0,-0.84375 -0.42187,-1.4375 -0.42188,-0.59375 -1.125,-0.78125 -0.4375,-0.125 -1.64063,-0.125 h -3.42187 z m 10.45919,7 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48438,-0.32812 1.0625,-0.32812 0.82813,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60937,-0.35937 -1.20312,-0.35937 -0.54688,0 -0.96875,0.32812 -0.42188,0.32813 -0.60938,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 5.61893,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07812 3.04687,-1.07812 2,0 3.26563,1.3125 1.26562,1.29687 1.26562,3.60937 0,1.85938 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29687 -1.25,-1.3125 -1.25,-3.76563 z m 1.6875,0 q 0,1.85938 0.79688,2.79688 0.8125,0.92187 2.04687,0.92187 1.21875,0 2.03125,-0.92187 0.8125,-0.9375 0.8125,-2.84375 0,-1.79688 -0.8125,-2.71875 -0.8125,-0.92188 -2.03125,-0.92188 -1.23437,0 -2.04687,0.92188 -0.79688,0.90625 -0.79688,2.76562 z m 8.98508,5.64063 1.59375,0.23437 q 0.10937,0.75 0.5625,1.07813 0.60937,0.45312 1.67187,0.45312 1.14063,0 1.75,-0.45312 0.625,-0.45313 0.84375,-1.26563 0.125,-0.5 0.10938,-2.10937 -1.0625,1.26562 -2.67188,1.26562 -2,0 -3.09375,-1.4375 -1.09375,-1.4375 -1.09375,-3.45312 0,-1.39063 0.5,-2.5625 0.51563,-1.17188 1.45313,-1.79688 0.95312,-0.64062 2.25,-0.64062 1.70312,0 2.8125,1.375 v -1.15625 h 1.51562 v 8.35937 q 0,2.26563 -0.46875,3.20313 -0.45312,0.9375 -1.45312,1.48437 -0.98438,0.54688 -2.45313,0.54688 -1.71875,0 -2.79687,-0.78125 -1.0625,-0.76563 -1.03125,-2.34375 z m 1.35937,-5.8125 q 0,1.90625 0.75,2.78125 0.76563,0.875 1.90625,0.875 1.125,0 1.89063,-0.85938 0.76562,-0.875 0.76562,-2.73437 0,-1.78125 -0.79687,-2.67188 -0.78125,-0.90625 -1.89063,-0.90625 -1.09375,0 -1.85937,0.89063 -0.76563,0.875 -0.76563,2.625 z m 9.31323,5.01562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48438,-0.32812 1.0625,-0.32812 0.82813,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60937,-0.35937 -1.20312,-0.35937 -0.54688,0 -0.96875,0.32812 -0.42188,0.32813 -0.60938,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 12.54081,-1.1875 q -0.92188,0.76563 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98437 0,-0.71875 0.32812,-1.29688 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35937 1.1875,-0.54687 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23438 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42188 0,-1 -0.46875,-1.42187 -0.625,-0.54688 -1.875,-0.54688 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42188 l -1.60937,-0.21875 q 0.21875,-1.01563 0.71875,-1.64063 0.5,-0.64062 1.45312,-0.98437 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29687 0.78125,0.28125 1.14062,0.73438 0.375,0.4375 0.51563,1.10937 0.0781,0.42188 0.0781,1.51563 v 2.1875 q 0,2.28125 0.10938,2.89062 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51562 -0.32812,-1.1875 z m -0.14063,-3.67187 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14062 -1.4375,0.32812 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42187 1.09375,-1.14062 0.26562,-0.5625 0.26562,-1.64063 z m 4.20383,4.85937 v -9.67187 h 1.46875 v 1.35937 q 0.45312,-0.71875 1.20312,-1.14062 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45312 0.6875,0.4375 0.96875,1.23438 1.15628,-1.6875 2.9844,-1.6875 1.45313,0 2.21875,0.79687 0.78125,0.79688 0.78125,2.45313 v 6.64062 h -1.64062 v -6.09375 q 0,-0.98437 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70312 -0.42187,-0.26563 -0.98437,-0.26563 -1.01563,0 -1.68753,0.6875 -0.67188,0.67188 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64062 -0.40625,-0.54688 -1.3125,-0.54688 -0.6875,0 -1.28125,0.35938 -0.59375,0.35937 -0.85938,1.0625 -0.25,0.70312 -0.25,2.03125 v 5.01562 z m 15.5408,0 v -9.67187 h 1.46875 v 1.35937 q 0.45312,-0.71875 1.20312,-1.14062 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45312 0.6875,0.4375 0.96875,1.23438 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79687 0.78125,0.79688 0.78125,2.45313 v 6.64062 h -1.64062 v -6.09375 q 0,-0.98437 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70312 -0.42187,-0.26563 -0.98437,-0.26563 -1.01563,0 -1.6875,0.6875 -0.67188,0.67188 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64062 -0.40625,-0.54688 -1.3125,-0.54688 -0.6875,0 -1.28125,0.35938 -0.59375,0.35937 -0.85938,1.0625 -0.25,0.70312 -0.25,2.03125 v 5.01562 z m 22.16583,-3.10937 1.6875,0.20312 q -0.40625,1.48438 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29687 -1.23437,-1.3125 -1.23437,-3.67188 0,-2.45312 1.25,-3.79687 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32812 1.23438,1.3125 1.23438,3.70313 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45312 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48437 1.01562,-1.51562 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82813 -0.78125,-0.95312 -2.03125,-0.95312 -1.125,0 -1.90625,0.76562 -0.76563,0.75 -0.84375,2.01563 z m 9.12567,5.76562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48437,-0.32812 1.0625,-0.32812 0.82812,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60938,-0.35937 -1.20313,-0.35937 -0.54687,0 -0.96875,0.32812 -0.42187,0.32813 -0.60937,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z"
+ id="path58"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 358,252.74803 h 55.49606 V 247 l 11.49607,11.49606 -11.49607,11.49606 v -5.74804 H 358 Z"
+ id="path60"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="m 358,252.74803 h 55.49606 V 247 l 11.49607,11.49606 -11.49607,11.49606 v -5.74804 H 358 Z"
+ id="path62"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="M 343.4882,292.49606 354.98426,281 v 5.74801 h 71.02362 v 11.49607 h -71.02362 v 5.74804 z"
+ id="path64"
+ inkscape:connector-curvature="0"
+ style="fill:#ead1dc;fill-rule:evenodd" />
+ <path
+ d="M 343.4882,292.49606 354.98426,281 v 5.74801 h 71.02362 v 11.49607 h -71.02362 v 5.74804 z"
+ id="path66"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ </g>
+ <rect
+ id="rect3919"
+ width="385.03391"
+ height="185.09322"
+ x="0"
+ y="0" />
+</svg>
diff --git a/util/ubertest/drawing_external_programmer_remote_dut.svg b/util/ubertest/drawing_external_programmer_remote_dut.svg
new file mode 100644
index 000000000..b35e9d763
--- /dev/null
+++ b/util/ubertest/drawing_external_programmer_remote_dut.svg
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ viewBox="0 0 561.27228 321.81293"
+ stroke-miterlimit="10"
+ id="svg133"
+ sodipodi:docname="drawing_local_host_external_programmer_remote_dut.svg"
+ width="561.27228"
+ height="321.81293"
+ style="fill:none;stroke:none;stroke-linecap:square;stroke-miterlimit:10"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata139">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs137" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1496"
+ inkscape:window-height="1194"
+ id="namedview135"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="1.1125147"
+ inkscape:cx="347.90062"
+ inkscape:cy="-34.458882"
+ inkscape:window-x="1551"
+ inkscape:window-y="595"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg133" />
+ <clipPath
+ id="p.0">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path78"
+ inkscape:connector-curvature="0"
+ style="clip-rule:nonzero" />
+ </clipPath>
+ <g
+ clip-path="url(#p.0)"
+ id="g131"
+ transform="translate(-80.517029,-99.313019)">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path81"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="M 94,106.50394 H 276.01575 V 283.00788 H 94 Z"
+ id="path83"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 94,106.50394 H 276.01575 V 283.00788 H 94 Z"
+ id="path85"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 120,123 h 142.99213 v 54.99213 H 120 Z"
+ id="path87"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 130.35938,149.92 v -13.35938 h 1.78125 v 11.78125 h 6.5625 V 149.92 Z m 9.64134,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 15.61009,1.29687 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 9.32813,2.35938 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 4.15697,4.85938 v -13.35938 h 1.64063 V 149.92 Z m 9.375,0 v -9.67188 h 1.46875 v 1.35938 q 0.45313,-0.71875 1.20313,-1.14063 0.76562,-0.4375 1.71875,-0.4375 1.07812,0 1.76562,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98438,-1.6875 1.45312,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 149.92 h -1.64063 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57812,-0.70313 -0.42188,-0.26562 -0.98438,-0.26562 -1.01562,0 -1.6875,0.6875 -0.67187,0.67187 -0.67187,2.15625 v 5.625 h -1.64063 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85937,1.0625 -0.25,0.70313 -0.25,2.03125 V 149.92 Z m 21.8533,-1.1875 q -0.92187,0.76562 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98438 0,-0.71875 0.32813,-1.29687 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23437 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42187 l -1.60938,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45313,-0.98438 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29688 0.78125,0.28125 1.14063,0.73437 0.375,0.4375 0.51562,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10937,2.89063 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51563 -0.32813,-1.1875 z m -0.14062,-3.67188 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14063 -1.4375,0.32813 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26563,-0.5625 0.26563,-1.64062 z m 10.51634,1.3125 1.60937,0.21875 q -0.26562,1.65625 -1.35937,2.60938 -1.07813,0.9375 -2.67188,0.9375 -1.98437,0 -3.1875,-1.29688 -1.20312,-1.29687 -1.20312,-3.71875 0,-1.57812 0.51562,-2.75 0.51563,-1.17187 1.57813,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57812,0 2.57812,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23437,-0.96875 -0.8125,-1.45313 -0.57812,-0.5 -1.39062,-0.5 -1.23438,0 -2.01563,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95313,0.875 0.96875,0 1.60937,-0.59375 0.65625,-0.59375 0.82813,-1.82813 z m 3.01562,3.54688 v -13.35938 h 1.64063 v 4.79688 q 1.14062,-1.32813 2.89062,-1.32813 1.07813,0 1.85938,0.42188 0.79687,0.42187 1.14062,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64062 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51563,-0.5625 -0.71875,0 -1.35937,0.39063 -0.64063,0.375 -0.92188,1.01562 -0.26562,0.64063 -0.26562,1.78125 V 149.92 Z m 10.37572,-11.46875 v -1.89063 h 1.64063 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64063 V 149.92 Z m 4.14482,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82813 0.375,0.5 0.51563,1.20312 0.0937,0.45313 0.0937,1.59375 V 149.92 h -1.64063 v -5.89063 q 0,-1 -0.20312,-1.48437 -0.1875,-0.5 -0.67188,-0.79688 -0.48437,-0.29687 -1.14062,-0.29687 -1.04688,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 149.92 Z m 17.00072,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29688 -1.23437,-1.3125 -1.23437,-3.67187 0,-2.45313 1.25,-3.79688 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32813 1.23438,1.3125 1.23438,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48438 1.01562,-1.51563 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76563,0.75 -0.84375,2.01562 z"
+ id="path89"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 426.99213,106.50394 h 206.99213 v 172 H 426.99213 Z"
+ id="path91"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="m 426.99213,106.50394 h 206.99213 v 172 H 426.99213 Z"
+ id="path93"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 440,123 h 173.98425 v 54.99213 H 440 Z"
+ id="path95"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 450.46875,149.92 v -13.35938 h 5.92187 q 1.78125,0 2.70313,0.35938 0.9375,0.35937 1.48437,1.28125 0.5625,0.90625 0.5625,2.01562 0,1.40625 -0.92187,2.39063 -0.92188,0.96875 -2.84375,1.23437 0.70312,0.34375 1.07812,0.67188 0.76563,0.70312 1.45313,1.76562 l 2.32812,3.64063 h -2.21875 L 458.25,147.13875 q -0.78125,-1.20313 -1.28125,-1.82813 -0.5,-0.64062 -0.90625,-0.89062 -0.39063,-0.26563 -0.79688,-0.35938 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45313 h 3.79688 q 1.21875,0 1.89062,-0.25 0.6875,-0.26562 1.04688,-0.8125 0.35937,-0.54687 0.35937,-1.1875 0,-0.95312 -0.6875,-1.5625 -0.6875,-0.60937 -2.1875,-0.60937 h -4.21875 z m 18.09797,4.34375 1.6875,0.20313 q -0.40625,1.48437 -1.48438,2.3125 -1.07812,0.8125 -2.76562,0.8125 -2.125,0 -3.375,-1.29688 -1.23438,-1.3125 -1.23438,-3.67187 0,-2.45313 1.25,-3.79688 1.26563,-1.34375 3.26563,-1.34375 1.9375,0 3.15625,1.32813 1.23437,1.3125 1.23437,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01563,0.84375 0.90625,0 1.54687,-0.46875 0.64063,-0.48438 1.01563,-1.51563 z m -5.39063,-2.65625 h 5.40625 q -0.10937,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76562,0.75 -0.84375,2.01562 z m 9.14133,5.76563 v -9.67188 h 1.46875 v 1.35938 q 0.45312,-0.71875 1.20312,-1.14063 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 149.92 h -1.64062 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70313 -0.42187,-0.26562 -0.98437,-0.26562 -1.01563,0 -1.6875,0.6875 -0.67188,0.67187 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85938,1.0625 -0.25,0.70313 -0.25,2.03125 V 149.92 Z m 14.93142,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 12.87574,3.375 0.23437,1.45312 q -0.6875,0.14063 -1.23437,0.14063 -0.89063,0 -1.39063,-0.28125 -0.48437,-0.28125 -0.6875,-0.73438 -0.20312,-0.46875 -0.20312,-1.9375 v -5.57812 h -1.20313 v -1.26563 h 1.20313 v -2.39062 l 1.625,-0.98438 v 3.375 h 1.65625 v 1.26563 h -1.65625 v 5.67187 q 0,0.6875 0.0781,0.89063 0.0937,0.20312 0.28125,0.32812 0.20313,0.10938 0.57813,0.10938 0.26562,0 0.71875,-0.0625 z m 8.23016,-1.64063 1.6875,0.20313 q -0.40625,1.48437 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29688 -1.23437,-1.3125 -1.23437,-3.67187 0,-2.45313 1.25,-3.79688 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32813 1.23438,1.3125 1.23438,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48438 1.01562,-1.51563 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76563,0.75 -0.84375,2.01562 z m 14.32464,5.76563 v -9.67188 h 1.46875 v 1.35938 q 0.45313,-0.71875 1.20313,-1.14063 0.76562,-0.4375 1.71875,-0.4375 1.07812,0 1.76562,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98438,-1.6875 1.45312,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 149.92 h -1.64063 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57812,-0.70313 -0.42188,-0.26562 -0.98438,-0.26562 -1.01562,0 -1.6875,0.6875 -0.67187,0.67187 -0.67187,2.15625 v 5.625 h -1.64063 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85937,1.0625 -0.25,0.70313 -0.25,2.03125 V 149.92 Z m 21.85333,-1.1875 q -0.92187,0.76562 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98438 0,-0.71875 0.32813,-1.29687 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23437 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42187 l -1.60938,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45313,-0.98438 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29688 0.78125,0.28125 1.14063,0.73437 0.375,0.4375 0.51562,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10937,2.89063 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51563 -0.32813,-1.1875 z m -0.14062,-3.67188 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14063 -1.4375,0.32813 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26563,-0.5625 0.26563,-1.64062 z m 10.5163,1.3125 1.60937,0.21875 q -0.26562,1.65625 -1.35937,2.60938 -1.07813,0.9375 -2.67188,0.9375 -1.98437,0 -3.1875,-1.29688 -1.20312,-1.29687 -1.20312,-3.71875 0,-1.57812 0.51562,-2.75 0.51563,-1.17187 1.57813,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57812,0 2.57812,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23437,-0.96875 -0.8125,-1.45313 -0.57812,-0.5 -1.39062,-0.5 -1.23438,0 -2.01563,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95313,0.875 0.96875,0 1.60937,-0.59375 0.65625,-0.59375 0.82813,-1.82813 z m 3.01562,3.54688 v -13.35938 h 1.64063 v 4.79688 q 1.14062,-1.32813 2.89062,-1.32813 1.07813,0 1.85938,0.42188 0.79687,0.42187 1.14062,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64062 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51563,-0.5625 -0.71875,0 -1.35937,0.39063 -0.64063,0.375 -0.92188,1.01562 -0.26562,0.64063 -0.26562,1.78125 V 149.92 Z m 10.37573,-11.46875 v -1.89063 h 1.64063 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64063 V 149.92 Z m 4.14484,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07812,-1.59375 0.875,0 1.60938,0.3125 0.73437,0.3125 1.09375,0.82813 0.375,0.5 0.51562,1.20312 0.0937,0.45313 0.0937,1.59375 V 149.92 h -1.64062 v -5.89063 q 0,-1 -0.20313,-1.48437 -0.1875,-0.5 -0.67187,-0.79688 -0.48438,-0.29687 -1.14063,-0.29687 -1.04687,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 149.92 Z m 17.00073,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29688 -1.23437,-1.3125 -1.23437,-3.67187 0,-2.45313 1.25,-3.79688 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32813 1.23438,1.3125 1.23438,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48438 1.01562,-1.51563 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76563,0.75 -0.84375,2.01562 z"
+ id="path97"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 458.99213,183 h 142.99213 v 60 H 458.99213 Z"
+ id="path99"
+ inkscape:connector-curvature="0"
+ style="fill:#fff2cc;fill-rule:evenodd" />
+ <path
+ d="m 458.99213,183 h 142.99213 v 60 H 458.99213 Z"
+ id="path101"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 493.48032,191.50394 h 74.01572 v 42.99213 h -74.01572 z"
+ id="path103"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 503.94907,218.42393 v -13.35937 h 5.92187 q 1.78125,0 2.7031,0.35937 0.9375,0.35938 1.48437,1.28125 0.5625,0.90625 0.5625,2.01563 0,1.40625 -0.92187,2.39062 -0.92188,0.96875 -2.84372,1.23438 0.70312,0.34375 1.07812,0.67187 0.7656,0.70313 1.4531,1.76563 l 2.32812,3.64062 h -2.21875 l -1.76559,-2.78125 q -0.78125,-1.20312 -1.28125,-1.82812 -0.5,-0.64063 -0.90625,-0.89063 -0.39063,-0.26562 -0.79688,-0.35937 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45312 h 3.79688 q 1.21875,0 1.89062,-0.25 0.68747,-0.26563 1.04685,-0.8125 0.35937,-0.54688 0.35937,-1.1875 0,-0.95313 -0.6875,-1.5625 -0.68747,-0.60938 -2.18747,-0.60938 h -4.21875 z m 11.14481,0.95312 q 0,-3.32812 1.78125,-5.20312 1.78125,-1.89063 4.60938,-1.89063 1.84375,0 3.32812,0.89063 1.48438,0.875 2.26563,2.46875 0.78125,1.57812 0.78125,3.57812 0,2.03125 -0.82813,3.64063 -0.8125,1.59375 -2.3125,2.42187 -1.5,0.82813 -3.25,0.82813 -1.875,0 -3.35937,-0.90625 -1.48438,-0.92188 -2.25,-2.5 -0.76563,-1.57813 -0.76563,-3.32813 z m 1.8125,0.0156 q 0,2.42187 1.29688,3.8125 1.29687,1.39062 3.26562,1.39062 2,0 3.28125,-1.40625 1.28125,-1.40625 1.28125,-3.98437 0,-1.625 -0.54687,-2.84375 -0.54688,-1.21875 -1.60938,-1.875 -1.0625,-0.67188 -2.375,-0.67188 -1.89062,0 -3.25,1.29688 -1.34375,1.28125 -1.34375,4.28125 z m 13.18329,6.48437 v -13.35937 h 2.65625 l 3.15625,9.45312 q 0.4375,1.32813 0.64062,1.98438 0.23438,-0.73438 0.70313,-2.14063 l 3.20312,-9.29687 h 2.375 v 13.35937 h -1.70312 v -11.17187 l -3.875,11.17187 h -1.59375 l -3.85938,-11.375 v 11.375 z"
+ id="path105"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="M 278.50394,188.49606 300,167 v 10.74803 H 405.48032 V 167 l 21.49606,21.49606 -21.49606,21.49606 V 199.24409 H 300 v 10.74803 z"
+ id="path107"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 278.50394,188.49606 300,167 v 10.74803 H 405.48032 V 167 l 21.49606,21.49606 -21.49606,21.49606 V 199.24409 H 300 v 10.74803 z"
+ id="path109"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="M 322.00787,140.98425 H 381 v 37.00787 h -58.99213 z"
+ id="path111"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 331.85162,163.60738 1.65625,-0.14063 q 0.125,1 0.54688,1.64063 0.4375,0.64062 1.34375,1.04687 0.92187,0.39063 2.0625,0.39063 1,0 1.78125,-0.29688 0.78125,-0.29687 1.15625,-0.8125 0.375,-0.53125 0.375,-1.15625 0,-0.625 -0.375,-1.09375 -0.35938,-0.46875 -1.1875,-0.79687 -0.54688,-0.20313 -2.39063,-0.64063 -1.82812,-0.45312 -2.5625,-0.84375 -0.96875,-0.5 -1.4375,-1.23437 -0.46875,-0.75 -0.46875,-1.67188 0,-1 0.57813,-1.875 0.57812,-0.89062 1.67187,-1.34375 1.10938,-0.45312 2.45313,-0.45312 1.48437,0 2.60937,0.48437 1.14063,0.46875 1.75,1.40625 0.60938,0.92188 0.65625,2.09375 l -1.6875,0.125 q -0.14062,-1.26562 -0.9375,-1.90625 -0.78125,-0.65625 -2.3125,-0.65625 -1.60937,0 -2.34375,0.59375 -0.73437,0.59375 -0.73437,1.42188 0,0.71875 0.53125,1.17187 0.5,0.46875 2.65625,0.96875 2.15625,0.48438 2.95312,0.84375 1.17188,0.53125 1.71875,1.35938 0.5625,0.82812 0.5625,1.90625 0,1.0625 -0.60937,2.01562 -0.60938,0.9375 -1.75,1.46875 -1.14063,0.51563 -2.57813,0.51563 -1.8125,0 -3.04687,-0.53125 -1.21875,-0.53125 -1.92188,-1.59375 -0.6875,-1.0625 -0.71875,-2.40625 z m 12.44357,0 1.65625,-0.14063 q 0.125,1 0.54688,1.64063 0.4375,0.64062 1.34375,1.04687 0.92187,0.39063 2.0625,0.39063 1,0 1.78125,-0.29688 0.78125,-0.29687 1.15625,-0.8125 0.375,-0.53125 0.375,-1.15625 0,-0.625 -0.375,-1.09375 -0.35938,-0.46875 -1.1875,-0.79687 -0.54688,-0.20313 -2.39063,-0.64063 -1.82812,-0.45312 -2.5625,-0.84375 -0.96875,-0.5 -1.4375,-1.23437 -0.46875,-0.75 -0.46875,-1.67188 0,-1 0.57813,-1.875 0.57812,-0.89062 1.67187,-1.34375 1.10938,-0.45312 2.45313,-0.45312 1.48437,0 2.60937,0.48437 1.14063,0.46875 1.75,1.40625 0.60938,0.92188 0.65625,2.09375 l -1.6875,0.125 q -0.14062,-1.26562 -0.9375,-1.90625 -0.78125,-0.65625 -2.3125,-0.65625 -1.60937,0 -2.34375,0.59375 -0.73437,0.59375 -0.73437,1.42188 0,0.71875 0.53125,1.17187 0.5,0.46875 2.65625,0.96875 2.15625,0.48438 2.95312,0.84375 1.17188,0.53125 1.71875,1.35938 0.5625,0.82812 0.5625,1.90625 0,1.0625 -0.60937,2.01562 -0.60938,0.9375 -1.75,1.46875 -1.14063,0.51563 -2.57813,0.51563 -1.8125,0 -3.04687,-0.53125 -1.21875,-0.53125 -1.92188,-1.59375 -0.6875,-1.0625 -0.71875,-2.40625 z m 13.09983,4.29687 v -13.35937 h 1.76562 v 5.48437 h 6.9375 v -5.48437 h 1.76563 v 13.35937 h -1.76563 v -6.29687 h -6.9375 v 6.29687 z"
+ id="path113"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 279.24408,347.84805 v 0 c 0,-7.63937 6.19293,-13.83231 13.83231,-13.83231 h 119.32754 c 3.66855,0 7.18686,1.45734 9.78091,4.0514 2.59406,2.59405 4.05137,6.11236 4.05137,9.78091 v 55.32751 c 0,7.63938 -6.19291,13.83231 -13.83228,13.83231 H 293.07639 c -7.63938,0 -13.83231,-6.19293 -13.83231,-13.83231 z"
+ id="path115"
+ inkscape:connector-curvature="0"
+ style="fill:#ead1dc;fill-rule:evenodd" />
+ <path
+ d="m 279.24408,347.84805 v 0 c 0,-7.63937 6.19293,-13.83231 13.83231,-13.83231 h 119.32754 c 3.66855,0 7.18686,1.45734 9.78091,4.0514 2.59406,2.59405 4.05137,6.11236 4.05137,9.78091 v 55.32751 c 0,7.63938 -6.19291,13.83231 -13.83228,13.83231 H 293.07639 c -7.63938,0 -13.83231,-6.19293 -13.83231,-13.83231 z"
+ id="path117"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 319.98907,371.43182 v -13.35937 h 9.65625 v 1.57812 h -7.875 v 4.09375 h 7.375 v 1.5625 h -7.375 v 4.54688 h 8.1875 v 1.57812 z m 11.11545,0 3.53125,-5.03125 -3.26563,-4.64062 h 2.04688 l 1.48437,2.26562 q 0.42188,0.64063 0.67188,1.07813 0.40625,-0.59375 0.73437,-1.0625 l 1.64063,-2.28125 h 1.95312 l -3.34375,4.54687 3.59375,5.125 h -2.01562 l -1.98438,-3 -0.51562,-0.8125 -2.54688,3.8125 z m 14,-1.46875 0.23437,1.45313 q -0.6875,0.14062 -1.23437,0.14062 -0.89063,0 -1.39063,-0.28125 -0.48437,-0.28125 -0.6875,-0.73437 -0.20312,-0.46875 -0.20312,-1.9375 v -5.57813 h -1.20313 v -1.26562 h 1.20313 v -2.39063 l 1.625,-0.98437 v 3.375 h 1.65625 v 1.26562 h -1.65625 v 5.67188 q 0,0.6875 0.0781,0.89062 0.0937,0.20313 0.28125,0.32813 0.20313,0.10937 0.57813,0.10937 0.26562,0 0.71875,-0.0625 z m 8.23019,-1.64062 1.6875,0.20312 q -0.40625,1.48438 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29687 -1.23437,-1.3125 -1.23437,-3.67188 0,-2.45312 1.25,-3.79687 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32812 1.23438,1.3125 1.23438,3.70313 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45312 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48437 1.01562,-1.51562 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82813 -0.78125,-0.95312 -2.03125,-0.95312 -1.125,0 -1.90625,0.76562 -0.76563,0.75 -0.84375,2.01563 z m 9.1257,5.76562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48437,-0.32812 1.0625,-0.32812 0.82812,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60938,-0.35937 -1.20313,-0.35937 -0.54687,0 -0.96875,0.32812 -0.42187,0.32813 -0.60937,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 6.2283,0 v -9.67187 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82812 0.375,0.5 0.51563,1.20313 0.0937,0.45312 0.0937,1.59375 v 5.95312 h -1.64063 v -5.89062 q 0,-1 -0.20312,-1.48438 -0.1875,-0.5 -0.67188,-0.79687 -0.48437,-0.29688 -1.14062,-0.29688 -1.04688,0 -1.8125,0.67188 -0.75,0.65625 -0.75,2.51562 v 5.28125 z m 16.68823,-1.1875 q -0.92187,0.76563 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98437 0,-0.71875 0.32813,-1.29688 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35937 1.1875,-0.54687 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23438 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42188 0,-1 -0.46875,-1.42187 -0.625,-0.54688 -1.875,-0.54688 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42188 l -1.60938,-0.21875 q 0.21875,-1.01563 0.71875,-1.64063 0.5,-0.64062 1.45313,-0.98437 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29687 0.78125,0.28125 1.14063,0.73438 0.375,0.4375 0.51562,1.10937 0.0781,0.42188 0.0781,1.51563 v 2.1875 q 0,2.28125 0.10937,2.89062 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51562 -0.32813,-1.1875 z m -0.14062,-3.67187 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14062 -1.4375,0.32812 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42187 1.09375,-1.14062 0.26563,-0.5625 0.26563,-1.64063 z m 4.15695,4.85937 v -13.35937 h 1.64062 v 13.35937 z"
+ id="path119"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 302.3446,393.43182 v -13.35937 h 5.04688 q 1.32812,0 2.03125,0.125 0.96875,0.17187 1.64062,0.64062 0.67188,0.45313 1.07813,1.28125 0.40625,0.82813 0.40625,1.82813 0,1.70312 -1.09375,2.89062 -1.07813,1.17188 -3.92188,1.17188 h -3.42187 v 5.42187 z m 1.76563,-7 h 3.45312 q 1.71875,0 2.4375,-0.64062 0.71875,-0.64063 0.71875,-1.79688 0,-0.84375 -0.42187,-1.4375 -0.42188,-0.59375 -1.125,-0.78125 -0.4375,-0.125 -1.64063,-0.125 h -3.42187 z m 10.45919,7 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48438,-0.32812 1.0625,-0.32812 0.82813,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60937,-0.35937 -1.20312,-0.35937 -0.54688,0 -0.96875,0.32812 -0.42188,0.32813 -0.60938,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 5.61893,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07812 3.04687,-1.07812 2,0 3.26563,1.3125 1.26562,1.29687 1.26562,3.60937 0,1.85938 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29687 -1.25,-1.3125 -1.25,-3.76563 z m 1.6875,0 q 0,1.85938 0.79688,2.79688 0.8125,0.92187 2.04687,0.92187 1.21875,0 2.03125,-0.92187 0.8125,-0.9375 0.8125,-2.84375 0,-1.79688 -0.8125,-2.71875 -0.8125,-0.92188 -2.03125,-0.92188 -1.23437,0 -2.04687,0.92188 -0.79688,0.90625 -0.79688,2.76562 z m 8.98511,5.64063 1.59375,0.23437 q 0.10937,0.75 0.5625,1.07813 0.60937,0.45312 1.67187,0.45312 1.14063,0 1.75,-0.45312 0.625,-0.45313 0.84375,-1.26563 0.125,-0.5 0.10938,-2.10937 -1.0625,1.26562 -2.67188,1.26562 -2,0 -3.09375,-1.4375 -1.09375,-1.4375 -1.09375,-3.45312 0,-1.39063 0.5,-2.5625 0.51563,-1.17188 1.45313,-1.79688 0.95312,-0.64062 2.25,-0.64062 1.70312,0 2.8125,1.375 v -1.15625 h 1.51562 v 8.35937 q 0,2.26563 -0.46875,3.20313 -0.45312,0.9375 -1.45312,1.48437 -0.98438,0.54688 -2.45313,0.54688 -1.71875,0 -2.79687,-0.78125 -1.0625,-0.76563 -1.03125,-2.34375 z m 1.35937,-5.8125 q 0,1.90625 0.75,2.78125 0.76563,0.875 1.90625,0.875 1.125,0 1.89063,-0.85938 0.76562,-0.875 0.76562,-2.73437 0,-1.78125 -0.79687,-2.67188 -0.78125,-0.90625 -1.89063,-0.90625 -1.09375,0 -1.85937,0.89063 -0.76563,0.875 -0.76563,2.625 z m 9.3132,5.01562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48438,-0.32812 1.0625,-0.32812 0.82813,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60937,-0.35937 -1.20312,-0.35937 -0.54688,0 -0.96875,0.32812 -0.42188,0.32813 -0.60938,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z m 12.54081,-1.1875 q -0.92188,0.76563 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98437 0,-0.71875 0.32812,-1.29688 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35937 1.1875,-0.54687 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23438 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42188 0,-1 -0.46875,-1.42187 -0.625,-0.54688 -1.875,-0.54688 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42188 l -1.60937,-0.21875 q 0.21875,-1.01563 0.71875,-1.64063 0.5,-0.64062 1.45312,-0.98437 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29687 0.78125,0.28125 1.14062,0.73438 0.375,0.4375 0.51563,1.10937 0.0781,0.42188 0.0781,1.51563 v 2.1875 q 0,2.28125 0.10938,2.89062 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51562 -0.32812,-1.1875 z m -0.14063,-3.67187 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14062 -1.4375,0.32812 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42187 1.09375,-1.14062 0.26562,-0.5625 0.26562,-1.64063 z m 4.20386,4.85937 v -9.67187 h 1.46875 v 1.35937 q 0.45312,-0.71875 1.20312,-1.14062 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45312 0.6875,0.4375 0.96875,1.23438 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79687 0.78125,0.79688 0.78125,2.45313 v 6.64062 h -1.64062 v -6.09375 q 0,-0.98437 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70312 -0.42187,-0.26563 -0.98437,-0.26563 -1.01563,0 -1.6875,0.6875 -0.67188,0.67188 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64062 -0.40625,-0.54688 -1.3125,-0.54688 -0.6875,0 -1.28125,0.35938 -0.59375,0.35937 -0.85938,1.0625 -0.25,0.70312 -0.25,2.03125 v 5.01562 z m 15.5408,0 v -9.67187 h 1.46875 v 1.35937 q 0.45313,-0.71875 1.20313,-1.14062 0.76562,-0.4375 1.71875,-0.4375 1.07812,0 1.76562,0.45312 0.6875,0.4375 0.96875,1.23438 1.15625,-1.6875 2.98438,-1.6875 1.45312,0 2.21875,0.79687 0.78125,0.79688 0.78125,2.45313 v 6.64062 h -1.64063 v -6.09375 q 0,-0.98437 -0.15625,-1.40625 -0.15625,-0.4375 -0.57812,-0.70312 -0.42188,-0.26563 -0.98438,-0.26563 -1.01562,0 -1.6875,0.6875 -0.67187,0.67188 -0.67187,2.15625 v 5.625 h -1.64063 v -6.28125 q 0,-1.09375 -0.40625,-1.64062 -0.40625,-0.54688 -1.3125,-0.54688 -0.6875,0 -1.28125,0.35938 -0.59375,0.35937 -0.85937,1.0625 -0.25,0.70312 -0.25,2.03125 v 5.01562 z m 22.1658,-3.10937 1.6875,0.20312 q -0.40625,1.48438 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29687 -1.23437,-1.3125 -1.23437,-3.67188 0,-2.45312 1.25,-3.79687 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32812 1.23438,1.3125 1.23438,3.70313 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45312 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48437 1.01562,-1.51562 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82813 -0.78125,-0.95312 -2.03125,-0.95312 -1.125,0 -1.90625,0.76562 -0.76563,0.75 -0.84375,2.01563 z m 9.1257,5.76562 v -9.67187 h 1.46875 v 1.46875 q 0.5625,-1.03125 1.03125,-1.35938 0.48437,-0.32812 1.0625,-0.32812 0.82812,0 1.6875,0.53125 l -0.5625,1.51562 q -0.60938,-0.35937 -1.20313,-0.35937 -0.54687,0 -0.96875,0.32812 -0.42187,0.32813 -0.60937,0.89063 -0.28125,0.875 -0.28125,1.92187 v 5.0625 z"
+ id="path121"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 205.22835,283.00787 v 53.35828 c 0,17.884 14.49786,32.38186 32.38188,32.38186 h 23.12991 V 378 l 18.50394,-18.50394 -18.50394,-18.50394 v 9.25195 h -23.12991 v 0 c -7.66458,0 -13.87795,-6.21334 -13.87795,-13.87792 v -53.35828 z"
+ id="path123"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="m 205.22835,283.00787 v 53.35828 c 0,17.884 14.49786,32.38186 32.38188,32.38186 h 23.12991 V 378 l 18.50394,-18.50394 -18.50394,-18.50394 v 9.25195 h -23.12991 v 0 c -7.66458,0 -13.87795,-6.21334 -13.87795,-13.87792 v -53.35828 z"
+ id="path125"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 426.97638,378 h 32.82284 c 18.12754,0 32.82282,-14.69528 32.82282,-32.82285 V 262.74014 H 502 l -18.75592,-18.75589 -18.75589,18.75589 h 9.37796 v 82.43701 0 c 0,7.76896 -6.29797,14.06693 -14.06693,14.06693 h -32.82284 z"
+ id="path127"
+ inkscape:connector-curvature="0"
+ style="fill:#ead1dc;fill-rule:evenodd" />
+ <path
+ d="m 426.97638,378 h 32.82284 c 18.12754,0 32.82282,-14.69528 32.82282,-32.82285 V 262.74014 H 502 l -18.75592,-18.75589 -18.75589,18.75589 h 9.37796 v 82.43701 0 c 0,7.76896 -6.29797,14.06693 -14.06693,14.06693 h -32.82284 z"
+ id="path129"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ </g>
+ <rect
+ id="rect214"
+ width="561.27228"
+ height="321.81293"
+ x="0"
+ y="0"
+ ry="0" />
+</svg>
diff --git a/util/ubertest/drawing_local_programmer_is_dut.svg b/util/ubertest/drawing_local_programmer_is_dut.svg
new file mode 100644
index 000000000..3aa1c2a44
--- /dev/null
+++ b/util/ubertest/drawing_local_programmer_is_dut.svg
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ viewBox="0 0 169.98425 183.02362"
+ stroke-miterlimit="10"
+ id="svg27"
+ sodipodi:docname="drawing_local_programmer.svg"
+ width="169.98425"
+ height="183.02362"
+ style="fill:none;stroke:none;stroke-linecap:square;stroke-miterlimit:10"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata33">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs31" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1922"
+ inkscape:window-height="1005"
+ id="namedview29"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="1.5733333"
+ inkscape:cx="116.46357"
+ inkscape:cy="52.165074"
+ inkscape:window-x="1343"
+ inkscape:window-y="852"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg27" />
+ <clipPath
+ id="p.0">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path2"
+ inkscape:connector-curvature="0"
+ style="clip-rule:nonzero" />
+ </clipPath>
+ <g
+ clip-path="url(#p.0)"
+ id="g25"
+ transform="translate(-187,-188)">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path5"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="M 187,188 H 356.98425 V 371.02362 H 187 Z"
+ id="path7"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 187,188 H 356.98425 V 371.02362 H 187 Z"
+ id="path9"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 205,188 h 142.99213 v 54.99213 H 205 Z"
+ id="path11"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 215.35938,214.92 v -13.35938 h 1.78125 v 11.78125 h 6.5625 V 214.92 Z m 9.64134,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 15.61009,1.29687 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 9.32813,2.35938 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 4.15697,4.85938 v -13.35938 h 1.64061 V 214.92 Z m 9.37499,0 v -9.67188 h 1.46875 v 1.35938 q 0.45312,-0.71875 1.20312,-1.14063 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 214.92 h -1.64062 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70313 -0.42187,-0.26562 -0.98437,-0.26562 -1.01563,0 -1.6875,0.6875 -0.67188,0.67187 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85938,1.0625 -0.25,0.70313 -0.25,2.03125 V 214.92 Z m 21.85333,-1.1875 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 10.51633,1.3125 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 3.01563,3.54688 v -13.35938 h 1.64062 v 4.79688 q 1.14063,-1.32813 2.89063,-1.32813 1.07812,0 1.85937,0.42188 0.79688,0.42187 1.14063,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64063 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51562,-0.5625 -0.71875,0 -1.35938,0.39063 -0.64062,0.375 -0.92187,1.01562 -0.26563,0.64063 -0.26563,1.78125 V 214.92 Z m 10.3757,-11.46875 v -1.89063 h 1.64062 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64062 V 214.92 Z m 4.14483,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82813 0.375,0.5 0.51563,1.20312 0.0937,0.45313 0.0937,1.59375 V 214.92 h -1.64063 v -5.89063 q 0,-1 -0.20312,-1.48437 -0.1875,-0.5 -0.67188,-0.79688 -0.48437,-0.29687 -1.14062,-0.29687 -1.04688,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 214.92 Z m 17.00074,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48438,2.3125 -1.07812,0.8125 -2.76562,0.8125 -2.125,0 -3.375,-1.29688 -1.23438,-1.3125 -1.23438,-3.67187 0,-2.45313 1.25,-3.79688 1.26563,-1.34375 3.26563,-1.34375 1.9375,0 3.15625,1.32813 1.23437,1.3125 1.23437,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01563,0.84375 0.90625,0 1.54687,-0.46875 0.64063,-0.48438 1.01563,-1.51563 z m -5.39063,-2.65625 h 5.40625 q -0.10937,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76562,0.75 -0.84375,2.01562 z"
+ id="path13"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 221,252 h 110.99213 v 68 H 221 Z"
+ id="path15"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 200.49606,270 h 142.99213 v 60 H 200.49606 Z"
+ id="path17"
+ inkscape:connector-curvature="0"
+ style="fill:#fff2cc;fill-rule:evenodd" />
+ <path
+ d="m 200.49606,270 h 142.99213 v 60 H 200.49606 Z"
+ id="path19"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="M 234.98425,278.50394 H 309 v 42.99213 h -74.01575 z"
+ id="path21"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 245.453,305.42395 v -13.35938 h 5.92187 q 1.78125,0 2.70313,0.35938 0.9375,0.35937 1.48437,1.28125 0.5625,0.90625 0.5625,2.01562 0,1.40625 -0.92187,2.39063 -0.92188,0.96875 -2.84375,1.23437 0.70312,0.34375 1.07812,0.67188 0.76563,0.70312 1.45313,1.76562 l 2.32812,3.64063 h -2.21875 l -1.76562,-2.78125 q -0.78125,-1.20313 -1.28125,-1.82813 -0.5,-0.64062 -0.90625,-0.89062 -0.39063,-0.26563 -0.79688,-0.35938 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45313 h 3.79688 q 1.21875,0 1.89062,-0.25 0.6875,-0.26562 1.04688,-0.8125 0.35937,-0.54687 0.35937,-1.1875 0,-0.95312 -0.6875,-1.5625 -0.6875,-0.60937 -2.1875,-0.60937 h -4.21875 z m 11.14481,0.95313 q 0,-3.32813 1.78125,-5.20313 1.78125,-1.89062 4.60938,-1.89062 1.84375,0 3.32812,0.89062 1.48438,0.875 2.26563,2.46875 0.78125,1.57813 0.78125,3.57813 0,2.03125 -0.82813,3.64062 -0.8125,1.59375 -2.3125,2.42188 -1.5,0.82812 -3.25,0.82812 -1.875,0 -3.35937,-0.90625 -1.48438,-0.92187 -2.25,-2.5 -0.76563,-1.57812 -0.76563,-3.32812 z m 1.8125,0.0156 q 0,2.42188 1.29688,3.8125 1.29687,1.39063 3.26562,1.39063 2,0 3.28125,-1.40625 1.28125,-1.40625 1.28125,-3.98438 0,-1.625 -0.54687,-2.84375 -0.54688,-1.21875 -1.60938,-1.875 -1.0625,-0.67187 -2.375,-0.67187 -1.89062,0 -3.25,1.29687 -1.34375,1.28125 -1.34375,4.28125 z m 13.18332,6.48438 v -13.35938 h 2.65625 l 3.15625,9.45313 q 0.4375,1.32812 0.64062,1.98437 0.23438,-0.73437 0.70313,-2.14062 l 3.20312,-9.29688 h 2.375 v 13.35938 h -1.70312 v -11.17188 l -3.875,11.17188 h -1.59375 l -3.85938,-11.375 v 11.375 z"
+ id="path23"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ </g>
+ <rect
+ id="rect3738"
+ width="169.98425"
+ height="183.02362"
+ x="0"
+ y="0" />
+</svg>
diff --git a/util/ubertest/drawing_remote_host_is_remote_dut.svg b/util/ubertest/drawing_remote_host_is_remote_dut.svg
new file mode 100644
index 000000000..6c39dfc0d
--- /dev/null
+++ b/util/ubertest/drawing_remote_host_is_remote_dut.svg
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ viewBox="0 0 540.95764 185.23334"
+ stroke-miterlimit="10"
+ id="svg198"
+ sodipodi:docname="drawing_remote_host_remote_dut.svg"
+ width="540.95764"
+ height="185.23334"
+ style="fill:none;stroke:none;stroke-linecap:square;stroke-miterlimit:10"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata204">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs202" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1736"
+ inkscape:window-height="1198"
+ id="namedview200"
+ showgrid="false"
+ inkscape:zoom="1.5733333"
+ inkscape:cx="136.34177"
+ inkscape:cy="95.880647"
+ inkscape:window-x="1284"
+ inkscape:window-y="714"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg198"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <clipPath
+ id="p.0">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path159"
+ inkscape:connector-curvature="0"
+ style="clip-rule:nonzero" />
+ </clipPath>
+ <g
+ clip-path="url(#p.0)"
+ id="g196"
+ transform="translate(-93.36441,-100.148)">
+ <path
+ d="M 0,0 H 800 V 600 H 0 Z"
+ id="path162"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="M 94,106.50394 H 276.01575 V 283.00788 H 94 Z"
+ id="path164"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 94,106.50394 H 276.01575 V 283.00788 H 94 Z"
+ id="path166"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 120,123 h 142.99213 v 54.99213 H 120 Z"
+ id="path168"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 130.35938,149.92 v -13.35938 h 1.78125 v 11.78125 h 6.5625 V 149.92 Z m 9.64134,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 15.61009,1.29687 1.60938,0.21875 q -0.26563,1.65625 -1.35938,2.60938 -1.07812,0.9375 -2.67187,0.9375 -1.98438,0 -3.1875,-1.29688 -1.20313,-1.29687 -1.20313,-3.71875 0,-1.57812 0.51563,-2.75 0.51562,-1.17187 1.57812,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57813,0 2.57813,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23438,-0.96875 -0.8125,-1.45313 -0.57813,-0.5 -1.39063,-0.5 -1.23437,0 -2.01562,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95312,0.875 0.96875,0 1.60938,-0.59375 0.65625,-0.59375 0.82812,-1.82813 z m 9.32813,2.35938 q -0.92188,0.76562 -1.76563,1.09375 -0.82812,0.3125 -1.79687,0.3125 -1.59375,0 -2.45313,-0.78125 -0.85937,-0.78125 -0.85937,-1.98438 0,-0.71875 0.32812,-1.29687 0.32813,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45313,-0.25 1.98437,-0.23437 2.92187,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70313,0.40625 -0.54687,0.40625 -0.8125,1.42187 l -1.60937,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45312,-0.98438 0.95313,-0.34375 2.1875,-0.34375 1.25,0 2.01563,0.29688 0.78125,0.28125 1.14062,0.73437 0.375,0.4375 0.51563,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10938,2.89063 0.10937,0.59375 0.40625,1.15625 h -1.70313 q -0.26562,-0.51563 -0.32812,-1.1875 z m -0.14063,-3.67188 q -0.89062,0.375 -2.67187,0.625 -1.01563,0.14063 -1.4375,0.32813 -0.42188,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45312,0.4375 0.9375,0 1.67188,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26562,-0.5625 0.26562,-1.64062 z m 4.15697,4.85938 v -13.35938 h 1.64063 V 149.92 Z m 9.375,0 v -9.67188 h 1.46875 v 1.35938 q 0.45313,-0.71875 1.20313,-1.14063 0.76562,-0.4375 1.71875,-0.4375 1.07812,0 1.76562,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98438,-1.6875 1.45312,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 149.92 h -1.64063 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57812,-0.70313 -0.42188,-0.26562 -0.98438,-0.26562 -1.01562,0 -1.6875,0.6875 -0.67187,0.67187 -0.67187,2.15625 v 5.625 h -1.64063 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85937,1.0625 -0.25,0.70313 -0.25,2.03125 V 149.92 Z m 21.8533,-1.1875 q -0.92187,0.76562 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98438 0,-0.71875 0.32813,-1.29687 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23437 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42187 l -1.60938,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45313,-0.98438 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29688 0.78125,0.28125 1.14063,0.73437 0.375,0.4375 0.51562,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10937,2.89063 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51563 -0.32813,-1.1875 z m -0.14062,-3.67188 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14063 -1.4375,0.32813 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26563,-0.5625 0.26563,-1.64062 z m 10.51634,1.3125 1.60937,0.21875 q -0.26562,1.65625 -1.35937,2.60938 -1.07813,0.9375 -2.67188,0.9375 -1.98437,0 -3.1875,-1.29688 -1.20312,-1.29687 -1.20312,-3.71875 0,-1.57812 0.51562,-2.75 0.51563,-1.17187 1.57813,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57812,0 2.57812,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23437,-0.96875 -0.8125,-1.45313 -0.57812,-0.5 -1.39062,-0.5 -1.23438,0 -2.01563,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95313,0.875 0.96875,0 1.60937,-0.59375 0.65625,-0.59375 0.82813,-1.82813 z m 3.01562,3.54688 v -13.35938 h 1.64063 v 4.79688 q 1.14062,-1.32813 2.89062,-1.32813 1.07813,0 1.85938,0.42188 0.79687,0.42187 1.14062,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64062 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51563,-0.5625 -0.71875,0 -1.35937,0.39063 -0.64063,0.375 -0.92188,1.01562 -0.26562,0.64063 -0.26562,1.78125 V 149.92 Z m 10.37572,-11.46875 v -1.89063 h 1.64063 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64063 V 149.92 Z m 4.14482,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07813,-1.59375 0.875,0 1.60937,0.3125 0.73438,0.3125 1.09375,0.82813 0.375,0.5 0.51563,1.20312 0.0937,0.45313 0.0937,1.59375 V 149.92 h -1.64063 v -5.89063 q 0,-1 -0.20312,-1.48437 -0.1875,-0.5 -0.67188,-0.79688 -0.48437,-0.29687 -1.14062,-0.29687 -1.04688,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 149.92 Z m 17.00072,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29688 -1.23437,-1.3125 -1.23437,-3.67187 0,-2.45313 1.25,-3.79688 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32813 1.23438,1.3125 1.23438,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48438 1.01562,-1.51563 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76563,0.75 -0.84375,2.01562 z"
+ id="path170"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 426.99213,106.50394 h 206.99213 v 172 H 426.99213 Z"
+ id="path172"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="m 426.99213,106.50394 h 206.99213 v 172 H 426.99213 Z"
+ id="path174"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 440,123 h 173.98425 v 54.99213 H 440 Z"
+ id="path176"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 450.46875,149.92 v -13.35938 h 5.92187 q 1.78125,0 2.70313,0.35938 0.9375,0.35937 1.48437,1.28125 0.5625,0.90625 0.5625,2.01562 0,1.40625 -0.92187,2.39063 -0.92188,0.96875 -2.84375,1.23437 0.70312,0.34375 1.07812,0.67188 0.76563,0.70312 1.45313,1.76562 l 2.32812,3.64063 h -2.21875 L 458.25,147.13875 q -0.78125,-1.20313 -1.28125,-1.82813 -0.5,-0.64062 -0.90625,-0.89062 -0.39063,-0.26563 -0.79688,-0.35938 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45313 h 3.79688 q 1.21875,0 1.89062,-0.25 0.6875,-0.26562 1.04688,-0.8125 0.35937,-0.54687 0.35937,-1.1875 0,-0.95312 -0.6875,-1.5625 -0.6875,-0.60937 -2.1875,-0.60937 h -4.21875 z m 18.09797,4.34375 1.6875,0.20313 q -0.40625,1.48437 -1.48438,2.3125 -1.07812,0.8125 -2.76562,0.8125 -2.125,0 -3.375,-1.29688 -1.23438,-1.3125 -1.23438,-3.67187 0,-2.45313 1.25,-3.79688 1.26563,-1.34375 3.26563,-1.34375 1.9375,0 3.15625,1.32813 1.23437,1.3125 1.23437,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01563,0.84375 0.90625,0 1.54687,-0.46875 0.64063,-0.48438 1.01563,-1.51563 z m -5.39063,-2.65625 h 5.40625 q -0.10937,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76562,0.75 -0.84375,2.01562 z m 9.14133,5.76563 v -9.67188 h 1.46875 v 1.35938 q 0.45312,-0.71875 1.20312,-1.14063 0.76563,-0.4375 1.71875,-0.4375 1.07813,0 1.76563,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98437,-1.6875 1.45313,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 149.92 h -1.64062 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57813,-0.70313 -0.42187,-0.26562 -0.98437,-0.26562 -1.01563,0 -1.6875,0.6875 -0.67188,0.67187 -0.67188,2.15625 v 5.625 h -1.64062 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85938,1.0625 -0.25,0.70313 -0.25,2.03125 V 149.92 Z m 14.93142,-4.84375 q 0,-2.6875 1.48438,-3.96875 1.25,-1.07813 3.04687,-1.07813 2,0 3.26563,1.3125 1.26562,1.29688 1.26562,3.60938 0,1.85937 -0.5625,2.9375 -0.5625,1.0625 -1.64062,1.65625 -1.0625,0.59375 -2.32813,0.59375 -2.03125,0 -3.28125,-1.29688 -1.25,-1.3125 -1.25,-3.76562 z m 1.6875,0 q 0,1.85937 0.79688,2.79687 0.8125,0.92188 2.04687,0.92188 1.21875,0 2.03125,-0.92188 0.8125,-0.9375 0.8125,-2.84375 0,-1.79687 -0.8125,-2.71875 -0.8125,-0.92187 -2.03125,-0.92187 -1.23437,0 -2.04687,0.92187 -0.79688,0.90625 -0.79688,2.76563 z m 12.87574,3.375 0.23437,1.45312 q -0.6875,0.14063 -1.23437,0.14063 -0.89063,0 -1.39063,-0.28125 -0.48437,-0.28125 -0.6875,-0.73438 -0.20312,-0.46875 -0.20312,-1.9375 v -5.57812 h -1.20313 v -1.26563 h 1.20313 v -2.39062 l 1.625,-0.98438 v 3.375 h 1.65625 v 1.26563 h -1.65625 v 5.67187 q 0,0.6875 0.0781,0.89063 0.0937,0.20312 0.28125,0.32812 0.20313,0.10938 0.57813,0.10938 0.26562,0 0.71875,-0.0625 z m 8.23016,-1.64063 1.6875,0.20313 q -0.40625,1.48437 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29688 -1.23437,-1.3125 -1.23437,-3.67187 0,-2.45313 1.25,-3.79688 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32813 1.23438,1.3125 1.23438,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48438 1.01562,-1.51563 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76563,0.75 -0.84375,2.01562 z m 14.32464,5.76563 v -9.67188 h 1.46875 v 1.35938 q 0.45313,-0.71875 1.20313,-1.14063 0.76562,-0.4375 1.71875,-0.4375 1.07812,0 1.76562,0.45313 0.6875,0.4375 0.96875,1.23437 1.15625,-1.6875 2.98438,-1.6875 1.45312,0 2.21875,0.79688 0.78125,0.79687 0.78125,2.45312 V 149.92 h -1.64063 v -6.09375 q 0,-0.98438 -0.15625,-1.40625 -0.15625,-0.4375 -0.57812,-0.70313 -0.42188,-0.26562 -0.98438,-0.26562 -1.01562,0 -1.6875,0.6875 -0.67187,0.67187 -0.67187,2.15625 v 5.625 h -1.64063 v -6.28125 q 0,-1.09375 -0.40625,-1.64063 -0.40625,-0.54687 -1.3125,-0.54687 -0.6875,0 -1.28125,0.35937 -0.59375,0.35938 -0.85937,1.0625 -0.25,0.70313 -0.25,2.03125 V 149.92 Z m 21.85333,-1.1875 q -0.92187,0.76562 -1.76562,1.09375 -0.82813,0.3125 -1.79688,0.3125 -1.59375,0 -2.45312,-0.78125 -0.85938,-0.78125 -0.85938,-1.98438 0,-0.71875 0.32813,-1.29687 0.32812,-0.59375 0.84375,-0.9375 0.53125,-0.35938 1.1875,-0.54688 0.46875,-0.125 1.45312,-0.25 1.98438,-0.23437 2.92188,-0.5625 0.0156,-0.34375 0.0156,-0.42187 0,-1 -0.46875,-1.42188 -0.625,-0.54687 -1.875,-0.54687 -1.15625,0 -1.70312,0.40625 -0.54688,0.40625 -0.8125,1.42187 l -1.60938,-0.21875 q 0.21875,-1.01562 0.71875,-1.64062 0.5,-0.64063 1.45313,-0.98438 0.95312,-0.34375 2.1875,-0.34375 1.25,0 2.01562,0.29688 0.78125,0.28125 1.14063,0.73437 0.375,0.4375 0.51562,1.10938 0.0781,0.42187 0.0781,1.51562 v 2.1875 q 0,2.28125 0.10937,2.89063 0.10938,0.59375 0.40625,1.15625 h -1.70312 q -0.26563,-0.51563 -0.32813,-1.1875 z m -0.14062,-3.67188 q -0.89063,0.375 -2.67188,0.625 -1.01562,0.14063 -1.4375,0.32813 -0.42187,0.1875 -0.65625,0.53125 -0.21875,0.34375 -0.21875,0.78125 0,0.65625 0.5,1.09375 0.5,0.4375 1.45313,0.4375 0.9375,0 1.67187,-0.40625 0.75,-0.42188 1.09375,-1.14063 0.26563,-0.5625 0.26563,-1.64062 z m 10.5163,1.3125 1.60937,0.21875 q -0.26562,1.65625 -1.35937,2.60938 -1.07813,0.9375 -2.67188,0.9375 -1.98437,0 -3.1875,-1.29688 -1.20312,-1.29687 -1.20312,-3.71875 0,-1.57812 0.51562,-2.75 0.51563,-1.17187 1.57813,-1.75 1.0625,-0.59375 2.3125,-0.59375 1.57812,0 2.57812,0.79688 1,0.79687 1.28125,2.26562 l -1.59375,0.23438 q -0.23437,-0.96875 -0.8125,-1.45313 -0.57812,-0.5 -1.39062,-0.5 -1.23438,0 -2.01563,0.89063 -0.78125,0.89062 -0.78125,2.8125 0,1.95312 0.75,2.84375 0.75,0.875 1.95313,0.875 0.96875,0 1.60937,-0.59375 0.65625,-0.59375 0.82813,-1.82813 z m 3.01562,3.54688 v -13.35938 h 1.64063 v 4.79688 q 1.14062,-1.32813 2.89062,-1.32813 1.07813,0 1.85938,0.42188 0.79687,0.42187 1.14062,1.17187 0.34375,0.75 0.34375,2.17188 v 6.125 h -1.64062 v -6.125 q 0,-1.23438 -0.53125,-1.79688 -0.53125,-0.5625 -1.51563,-0.5625 -0.71875,0 -1.35937,0.39063 -0.64063,0.375 -0.92188,1.01562 -0.26562,0.64063 -0.26562,1.78125 V 149.92 Z m 10.37573,-11.46875 v -1.89063 h 1.64063 v 1.89063 z m 0,11.46875 v -9.67188 h 1.64063 V 149.92 Z m 4.14484,0 v -9.67188 h 1.46875 v 1.375 q 1.0625,-1.59375 3.07812,-1.59375 0.875,0 1.60938,0.3125 0.73437,0.3125 1.09375,0.82813 0.375,0.5 0.51562,1.20312 0.0937,0.45313 0.0937,1.59375 V 149.92 h -1.64062 v -5.89063 q 0,-1 -0.20313,-1.48437 -0.1875,-0.5 -0.67187,-0.79688 -0.48438,-0.29687 -1.14063,-0.29687 -1.04687,0 -1.8125,0.67187 -0.75,0.65625 -0.75,2.51563 V 149.92 Z m 17.00073,-3.10938 1.6875,0.20313 q -0.40625,1.48437 -1.48437,2.3125 -1.07813,0.8125 -2.76563,0.8125 -2.125,0 -3.375,-1.29688 -1.23437,-1.3125 -1.23437,-3.67187 0,-2.45313 1.25,-3.79688 1.26562,-1.34375 3.26562,-1.34375 1.9375,0 3.15625,1.32813 1.23438,1.3125 1.23438,3.70312 0,0.15625 0,0.4375 h -7.21875 q 0.0937,1.59375 0.90625,2.45313 0.8125,0.84375 2.01562,0.84375 0.90625,0 1.54688,-0.46875 0.64062,-0.48438 1.01562,-1.51563 z m -5.39062,-2.65625 h 5.40625 q -0.10938,-1.21875 -0.625,-1.82812 -0.78125,-0.95313 -2.03125,-0.95313 -1.125,0 -1.90625,0.76563 -0.76563,0.75 -0.84375,2.01562 z"
+ id="path178"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="m 458.99213,183 h 142.99213 v 60 H 458.99213 Z"
+ id="path180"
+ inkscape:connector-curvature="0"
+ style="fill:#fff2cc;fill-rule:evenodd" />
+ <path
+ d="m 458.99213,183 h 142.99213 v 60 H 458.99213 Z"
+ id="path182"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="m 493.48032,191.50394 h 74.01572 v 42.99213 h -74.01572 z"
+ id="path184"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 503.94907,218.42393 v -13.35937 h 5.92187 q 1.78125,0 2.7031,0.35937 0.9375,0.35938 1.48437,1.28125 0.5625,0.90625 0.5625,2.01563 0,1.40625 -0.92187,2.39062 -0.92188,0.96875 -2.84372,1.23438 0.70312,0.34375 1.07812,0.67187 0.7656,0.70313 1.4531,1.76563 l 2.32812,3.64062 h -2.21875 l -1.76559,-2.78125 q -0.78125,-1.20312 -1.28125,-1.82812 -0.5,-0.64063 -0.90625,-0.89063 -0.39063,-0.26562 -0.79688,-0.35937 -0.29687,-0.0781 -0.98437,-0.0781 h -2.04688 v 5.9375 z m 1.76562,-7.45312 h 3.79688 q 1.21875,0 1.89062,-0.25 0.68747,-0.26563 1.04685,-0.8125 0.35937,-0.54688 0.35937,-1.1875 0,-0.95313 -0.6875,-1.5625 -0.68747,-0.60938 -2.18747,-0.60938 h -4.21875 z m 11.14481,0.95312 q 0,-3.32812 1.78125,-5.20312 1.78125,-1.89063 4.60938,-1.89063 1.84375,0 3.32812,0.89063 1.48438,0.875 2.26563,2.46875 0.78125,1.57812 0.78125,3.57812 0,2.03125 -0.82813,3.64063 -0.8125,1.59375 -2.3125,2.42187 -1.5,0.82813 -3.25,0.82813 -1.875,0 -3.35937,-0.90625 -1.48438,-0.92188 -2.25,-2.5 -0.76563,-1.57813 -0.76563,-3.32813 z m 1.8125,0.0156 q 0,2.42187 1.29688,3.8125 1.29687,1.39062 3.26562,1.39062 2,0 3.28125,-1.40625 1.28125,-1.40625 1.28125,-3.98437 0,-1.625 -0.54687,-2.84375 -0.54688,-1.21875 -1.60938,-1.875 -1.0625,-0.67188 -2.375,-0.67188 -1.89062,0 -3.25,1.29688 -1.34375,1.28125 -1.34375,4.28125 z m 13.18329,6.48437 v -13.35937 h 2.65625 l 3.15625,9.45312 q 0.4375,1.32813 0.64062,1.98438 0.23438,-0.73438 0.70313,-2.14063 l 3.20312,-9.29687 h 2.375 v 13.35937 h -1.70312 v -11.17187 l -3.875,11.17187 h -1.59375 l -3.85938,-11.375 v 11.375 z"
+ id="path186"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ <path
+ d="M 278.50394,188.49606 300,167 v 10.74803 H 405.48032 V 167 l 21.49606,21.49606 -21.49606,21.49606 V 199.24409 H 300 v 10.74803 z"
+ id="path188"
+ inkscape:connector-curvature="0"
+ style="fill:#cfe2f3;fill-rule:evenodd" />
+ <path
+ d="M 278.50394,188.49606 300,167 v 10.74803 H 405.48032 V 167 l 21.49606,21.49606 -21.49606,21.49606 V 199.24409 H 300 v 10.74803 z"
+ id="path190"
+ inkscape:connector-curvature="0"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round" />
+ <path
+ d="M 322.00787,140.98425 H 381 v 37.00787 h -58.99213 z"
+ id="path192"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd" />
+ <path
+ d="m 331.85162,163.60738 1.65625,-0.14063 q 0.125,1 0.54688,1.64063 0.4375,0.64062 1.34375,1.04687 0.92187,0.39063 2.0625,0.39063 1,0 1.78125,-0.29688 0.78125,-0.29687 1.15625,-0.8125 0.375,-0.53125 0.375,-1.15625 0,-0.625 -0.375,-1.09375 -0.35938,-0.46875 -1.1875,-0.79687 -0.54688,-0.20313 -2.39063,-0.64063 -1.82812,-0.45312 -2.5625,-0.84375 -0.96875,-0.5 -1.4375,-1.23437 -0.46875,-0.75 -0.46875,-1.67188 0,-1 0.57813,-1.875 0.57812,-0.89062 1.67187,-1.34375 1.10938,-0.45312 2.45313,-0.45312 1.48437,0 2.60937,0.48437 1.14063,0.46875 1.75,1.40625 0.60938,0.92188 0.65625,2.09375 l -1.6875,0.125 q -0.14062,-1.26562 -0.9375,-1.90625 -0.78125,-0.65625 -2.3125,-0.65625 -1.60937,0 -2.34375,0.59375 -0.73437,0.59375 -0.73437,1.42188 0,0.71875 0.53125,1.17187 0.5,0.46875 2.65625,0.96875 2.15625,0.48438 2.95312,0.84375 1.17188,0.53125 1.71875,1.35938 0.5625,0.82812 0.5625,1.90625 0,1.0625 -0.60937,2.01562 -0.60938,0.9375 -1.75,1.46875 -1.14063,0.51563 -2.57813,0.51563 -1.8125,0 -3.04687,-0.53125 -1.21875,-0.53125 -1.92188,-1.59375 -0.6875,-1.0625 -0.71875,-2.40625 z m 12.44357,0 1.65625,-0.14063 q 0.125,1 0.54688,1.64063 0.4375,0.64062 1.34375,1.04687 0.92187,0.39063 2.0625,0.39063 1,0 1.78125,-0.29688 0.78125,-0.29687 1.15625,-0.8125 0.375,-0.53125 0.375,-1.15625 0,-0.625 -0.375,-1.09375 -0.35938,-0.46875 -1.1875,-0.79687 -0.54688,-0.20313 -2.39063,-0.64063 -1.82812,-0.45312 -2.5625,-0.84375 -0.96875,-0.5 -1.4375,-1.23437 -0.46875,-0.75 -0.46875,-1.67188 0,-1 0.57813,-1.875 0.57812,-0.89062 1.67187,-1.34375 1.10938,-0.45312 2.45313,-0.45312 1.48437,0 2.60937,0.48437 1.14063,0.46875 1.75,1.40625 0.60938,0.92188 0.65625,2.09375 l -1.6875,0.125 q -0.14062,-1.26562 -0.9375,-1.90625 -0.78125,-0.65625 -2.3125,-0.65625 -1.60937,0 -2.34375,0.59375 -0.73437,0.59375 -0.73437,1.42188 0,0.71875 0.53125,1.17187 0.5,0.46875 2.65625,0.96875 2.15625,0.48438 2.95312,0.84375 1.17188,0.53125 1.71875,1.35938 0.5625,0.82812 0.5625,1.90625 0,1.0625 -0.60937,2.01562 -0.60938,0.9375 -1.75,1.46875 -1.14063,0.51563 -2.57813,0.51563 -1.8125,0 -3.04687,-0.53125 -1.21875,-0.53125 -1.92188,-1.59375 -0.6875,-1.0625 -0.71875,-2.40625 z m 13.09983,4.29687 v -13.35937 h 1.76562 v 5.48437 h 6.9375 v -5.48437 h 1.76563 v 13.35937 h -1.76563 v -6.29687 h -6.9375 v 6.29687 z"
+ id="path194"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:nonzero" />
+ </g>
+ <rect
+ id="rect206"
+ width="539.98425"
+ height="172"
+ x="0.6355896"
+ y="6.3559341" />
+ <rect
+ id="rect208"
+ width="539.98425"
+ height="172"
+ x="0.6355896"
+ y="6.3559341" />
+ <rect
+ id="rect212"
+ width="540.95764"
+ height="185.23334"
+ x="0"
+ y="0" />
+</svg>
diff --git a/util/ubertest/ubertest.sh b/util/ubertest/ubertest.sh
new file mode 100755
index 000000000..009194d45
--- /dev/null
+++ b/util/ubertest/ubertest.sh
@@ -0,0 +1,935 @@
+#!/bin/sh
+#
+# Copyright (C) 2016 Google Inc.
+# Copyright (C) 2020 Facebook Inc.
+#
+# 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+EXIT_SUCCESS=0
+EXIT_FAILURE=1
+RC=$EXIT_SUCCESS
+FATAL=0
+NONFATAL=1
+
+#
+# Stuff obtained from command-line
+#
+
+# Generic options
+BACKUP_IMAGE=""
+OLD_FLASHROM=""
+NEW_FLASHROM=""
+NO_CLEAN=0
+SKIP_CONSISTENCY_CHECK=0
+UPLOAD_RESULTS=0
+
+# LOCAL_FLASHROM is required if both a secondary programmer *and* a remote host
+# are used since OLD_FLASHROM and NEW_FLASHROM will be called on the remote
+# host and we need a local copy of flashrom to control the secondary programmer.
+# By default this will be set to the result of `which flashrom`.
+LOCAL_FLASHROM=""
+
+# Primary/Secondary programmer options
+PRIMARY_OPTS=""
+SECONDARY_OPTS=""
+
+# Calls preflash_hook() and postflash_hook() before and after doing a command.
+CUSTOM_HOOKS_FILENAME=""
+
+# logfile to store the script's output
+SCRIPT_LOGFILE="flashrom-test_script_output.txt"
+
+# Test type
+TEST_TYPE_UNKNOWN=0
+TEST_TYPE_SINGLE=1
+TEST_TYPE_ENDURANCE=2
+TEST_TYPE=$TEST_TYPE_UNKNOWN
+
+# Region modes
+REGION_MODE_UNKNOWN=0
+REGION_MODE_CLOBBER=1
+REGION_MODE_DESCRIPTOR=2
+REGION_MODE_FLASHMAP=3
+REGION_MODE_LAYOUT=4
+REGION_MODE=$REGION_MODE_UNKNOWN
+DESCRIPTOR_REGION="BIOS"
+FLASHMAP_REGION="RW_SECTION_A"
+LAYOUT_FILE=""
+LAYOUT_REGION="RW"
+SMALL_REGION=0
+
+# Remote testing options
+SSH_PORT=""
+REMOTE_HOST=""
+REMOTE_PORT_OPTION=""
+REMOTE_ONLY=0
+REMOTE_PROGRAMMER_PARAMS=""
+SSH_CMD="ssh $REMOTE_PORT_OPTION root@${REMOTE_HOST} command -v"
+
+LOCAL=0
+REMOTE=1
+DO_REMOTE=0 # boolean to use for cmd() and tests
+
+# relative path from flashrom root directory
+TEST_SCRIPT_PATH="util/testing/test_v2.1.sh"
+
+# In case we need to run flashrom locally and we're not already root.
+SUDO_CMD=""
+if [ "$(id -u)" -ne "0" ]; then
+ SUDO_CMD="sudo"
+fi
+
+# 1KB
+K=1024
+
+show_help() {
+ printf "Usage:
+ ${0} <options>
+
+General options:
+ -b, --backup-image <path>
+ Backup image to write unconditionally at end of testing.
+ -h, --help
+ Show this message.
+ -l, --layout-file <path>
+ Layout file (required if mode is \"layout\", resides locally).
+ -m, --mode <arg>
+ Region access mode (clobber, descriptor, flashmap, layout).
+ -n, --new <path>
+ Path to new version of flashrom.
+ -o, --old <path>
+ Path to old (stable) version of flashrom.
+ -p, --primary-programmer <parameters>
+ Primary programmer options.
+ -r, --remote-host <host>
+ Remote host to test primary programmer on.
+ -s, --secondary-programmer <parameters>
+ Secondary programmer options.
+ -t, --type <arg>
+ Test type (single, endurance).
+ -u, --upload-results
+ Upload results to flashrom.org.
+ -v, --voltage
+ Chip voltage in millivolts (usually 1800 or 3300).
+
+Long options:
+ --custom-hooks <filename>
+ Supply a script with custom hooks to run before and after commands.
+ --descriptor-region <name>
+ Specify region to use in descriptor mode (default: $DESCRIPTOR_REGION)
+ --flashmap-region <name>
+ Specify region to use in flashmap mode (default: $FLASHMAP_REGION)
+ --layout-region <name>
+ Specify region to use in layout mode (default: $LAYOUT_REGION)
+ --local-flashrom <path>
+ Path to local version of flashrom when using both a secondary programmer
+ and remote host (default: $($SUDO_CMD which flashrom))
+ --no-clean
+ Do not remove temporary files.
+ --skip-consistency-check
+ Skip the consistency check (two consecutive reads) at beginning.
+ --small-region
+ Omit tests that require large amounts of space (>16KB).
+Remote connectivity options:
+ --ssh-port <port>
+ Use a specific SSH port.
+
+See documentation for usage examples (TODO: Migrate https://goo.gl/3jNoL7
+to flashrom wiki).\n
+"
+}
+
+getopt -T
+if [ $? -ne 4 ]; then
+ printf "GNU-compatible getopt(1) required.\n"
+ exit $EXIT_FAILURE
+fi
+
+LONGOPTS="backup-image:,help,,new:,old:,remote-host:,upload-results:"
+LONGOPTS="${LONGOPTS},primary-programmer:,secondary-programmer:,local-flashrom:"
+LONGOPTS="${LONGOPTS},custom-hooks:,mode:,skip-consistency-check,small-region"
+LONGOPTS="${LONGOPTS},type:,voltage:"
+LONGOPTS="${LONGOPTS},layout-file:,descriptor-region:,flashmap-region:,layout-region:"
+LONGOPTS="${LONGOPTS},no-clean"
+LONGOPTS="${LONGOPTS},ssh-port:"
+
+ARGS=$(getopt -o b:hl:m:n:o:p:r:s:t:u -l "$LONGOPTS" -n "$0" -- "$@");
+if [ $? != 0 ] ; then printf "Terminating...\n" >&2 ; exit 1 ; fi
+eval set -- "$ARGS"
+while true ; do
+ case "$1" in
+ # Generic options
+ -b|--backup-image)
+ shift
+ BACKUP_IMAGE="$1"
+ ;;
+ -h|--help)
+ show_help
+ exit $EXIT_SUCCESS
+ ;;
+ -l|--layout-file)
+ shift
+ LAYOUT_FILE="$1"
+ ;;
+ -m|--mode)
+ shift
+ if [ "$1" = "clobber" ]; then
+ REGION_MODE=$REGION_MODE_CLOBBER
+ elif [ "$1" = "descriptor" ]; then
+ REGION_MODE=$REGION_MODE_DESCRIPTOR
+ elif [ "$1" = "flashmap" ]; then
+ REGION_MODE=$REGION_MODE_FLASHMAP
+ elif [ "$1" = "layout" ]; then
+ REGION_MODE=$REGION_MODE_LAYOUT
+ else
+ printf "Unknown mode: $1\n"
+ exit $EXIT_FAILURE
+ fi
+ ;;
+ -n|--new)
+ shift
+ NEW_FLASHROM="$1"
+ ;;
+ -o|--old)
+ shift
+ OLD_FLASHROM="$1"
+ ;;
+ -p|--primary_programmer)
+ shift
+ PRIMARY_OPTS="-p $1"
+ ;;
+ -s|--secondary_programmer)
+ shift
+ SECONDARY_OPTS="-p $1"
+ ;;
+ -t|--type)
+ shift
+ if [ "$1" = "single" ]; then
+ TEST_TYPE=$TEST_TYPE_SINGLE
+ elif [ "$1" = "endurance" ]; then
+ TEST_TYPE=$TEST_TYPE_ENDURANCE
+ else
+ printf "Unknown type: $1\n"
+ exit $EXIT_FAILURE
+ fi
+ ;;
+ -r|--remote-host)
+ DO_REMOTE=1
+ shift
+ REMOTE_HOST="$1"
+ ;;
+ -u|--upload-results)
+ UPLOAD_RESULTS=1
+ ;;
+ -v|--voltage)
+ shift
+ VOLTAGE="$1"
+ ;;
+
+ # Longopts only
+ --custom-hooks)
+ shift
+ CUSTOM_HOOKS_FILENAME="$1"
+ ;;
+ --descriptor-region)
+ shift
+ DESCRIPTOR_REGION="$1"
+ ;;
+ --flashmap-region)
+ shift
+ FLASHMAP_REGION="$1"
+ ;;
+ --layout-region)
+ shift
+ LAYOUT_REGION="$1"
+ ;;
+ --local-flashrom)
+ shift
+ LOCAL_FLASHROM="$1"
+ ;;
+ --no-clean)
+ NO_CLEAN=1
+ ;;
+ --skip-consistency-check)
+ SKIP_CONSISTENCY_CHECK=1
+ ;;
+ --small-region)
+ SMALL_REGION=1
+ ;;
+
+ # Remote testing options
+ --ssh-port)
+ shift
+ REMOTE_PORT_OPTION="-p $1"
+ ;;
+
+ # error handling
+ --)
+ shift
+ if [ -n "$*" ]; then
+ printf "Non-option parameters detected: '$*'\n"
+ exit $EXIT_FAILURE
+ fi
+ break
+ ;;
+ *)
+ printf "error processing options at '$1'\n"
+ exit $EXIT_FAILURE
+ esac
+ shift
+done
+
+# TODO: Implement this.
+if [ $UPLOAD_RESULTS -eq 1 ]; then
+ printf "TODO: Implement ability to upload results.\n"
+ exit $EXIT_FAILURE
+fi
+
+#
+# Source helper scripts
+#
+export REMOTE_HOST REMOTE_PORT_OPTION
+export LOCAL REMOTE FATAL NONFATAL EXIT_SUCCESS EXIT_FAILURE
+export CUSTOM_HOOKS_FILENAME SUDO_CMD VOLTAGE
+. "$(pwd)/util/testing/cmd.sh"
+
+# We will set up a logs directory within the tmpdirs to store
+# all output logs.
+LOGS="logs"
+
+# Setup temporary working directories:
+# LOCAL_TMPDIR: Working directory on local host.
+# REMOTE_TMPDIR: Working directory on remote host.
+# 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
+ printf "Could not create temporary directory\n"
+ exit $EXIT_FAILURE
+fi
+mkdir "${LOCAL_TMPDIR}/${LOGS}"
+
+if [ $DO_REMOTE -eq 1 ]; then
+ REMOTE_TMPDIR=$(ssh root@${REMOTE_HOST} mktemp -d --tmpdir flashrom_test.XXXXXXXX)
+ if [ $? -ne 0 ] ; then
+ printf "Could not create temporary directory\n"
+ exit $EXIT_FAILURE
+ fi
+ scmd $REMOTE "mkdir ${REMOTE_TMPDIR}/${LOGS}"
+fi
+
+if [ $DO_REMOTE -eq 0 ]; then
+ TMPDIR="$LOCAL_TMPDIR"
+else
+ TMPDIR="$REMOTE_TMPDIR"
+fi
+
+#
+# Test command-line validity.
+#
+if [ $TEST_TYPE -eq $TEST_TYPE_UNKNOWN ]; then
+ printf "Must specify a test type (-t/--type).\n"
+ exit $EXIT_FAILURE
+elif [ $TEST_TYPE -eq $TEST_TYPE_SINGLE ]; then
+ if [ $REGION_MODE -eq $REGION_MODE_UNKNOWN ]; then
+ printf "Must specify a region access mode (-m/--mode).\n"
+ exit $EXIT_FAILURE
+ elif [ $REGION_MODE -eq $REGION_MODE_LAYOUT ]; then
+ if [ -z "$LAYOUT_FILE" ]; then
+ printf "Must specify a layout file when using layout mode.\n"
+ exit $EXIT_FAILURE
+ fi
+
+ scmd $DO_REMOTE "stat $LAYOUT_FILE"
+ if [ $? -ne 0 ]; then
+ if [ $DO_REMOTE -eq 1 ]; then
+ tmp=" on remote host $REMOTE_HOST."
+ else
+ tmp=" on local host."
+ fi
+ printf "Layout file $LAYOUT_FILE not found${tmp}\n"
+ exit $EXIT_FAILURE
+ fi
+
+ if [ $DO_REMOTE -eq 1 ]; then
+ scp root@"${REMOTE_HOST}:$LAYOUT_FILE" "${LOCAL_TMPDIR}/" 2>&1 >/dev/null
+ else
+ cp "$LAYOUT_FILE" "${LOCAL_TMPDIR}/"
+ fi
+ fi
+fi
+
+if [ -n "$VOLTAGE" ]; then
+ echo "$VOLTAGE" | grep -q '[^0-9]'
+ if [ $? -ne 1 ]; then
+ printf "Voltage must be an integer with units of millivolts."
+ exit $EXIT_FAILURE
+ fi
+fi
+
+if [ $DO_REMOTE -eq 1 ]; then
+ # Test connection to remote host
+ test_cmd $DO_REMOTE "ls /" $NONFATAL
+ if [ $? -ne 0 ]; then
+ printf "Could not connect to remote host ${REMOTE_HOST}\n"
+ exit $EXIT_FAILURE
+ fi
+fi
+
+# Remote host and secondary programmer are in use, so either the user must
+# specify a local version of flashrom to control the secondary programmer
+# or it must be found in the default path.
+if [ $DO_REMOTE -eq 1 ] && [ -n "$SECONDARY_OPTS" ]; then
+ if [ -z "$LOCAL_FLASHROM" ]; then
+ LOCAL_FLASHROM="$($SUDO_CMD which flashrom)"
+ fi
+
+ if [ ! -e "$LOCAL_FLASHROM" ]; then
+ printf "$LOCAL_FLASHROM does not exist\n"
+ exit $EXIT_FAILURE
+ fi
+fi
+
+#
+# Dependencies
+#
+
+# cmp is used to compare files
+test_cmd $DO_REMOTE "cmp" $FATAL
+
+if [ ! -e "/dev/urandom" ]; then
+ printf "This script uses /dev/urandom\n"
+ exit $EXIT_FAILURE
+fi
+
+if [ ! -e "/dev/zero" ]; then
+ printf "This script uses /dev/zero\n"
+ exit $EXIT_FAILURE
+fi
+
+#
+# Setup.
+#
+
+# Naive path check
+if [ ! -e "$TEST_SCRIPT_PATH" ] ; then
+ printf "Script must be run from root of flashrom directory\n"
+ exit $EXIT_FAILURE
+fi
+
+if [ -z "$OLD_FLASHROM" ]; then
+ if [ $DO_REMOTE -eq 1 ]; then
+ OLD_FLASHROM="$(ssh root@${REMOTE_HOST} which flashrom)"
+ else
+ OLD_FLASHROM="$($SUDO_CMD which flashrom)"
+ fi
+fi
+test_cmd $DO_REMOTE "$OLD_FLASHROM --help" $NONFATAL
+if [ $? -ne 0 ]; then
+ printf "Old flashrom binary is not usable.\n"
+ exit $EXIT_FAILURE
+fi
+
+# print $1 and store it in the script log file
+print_and_log()
+{
+ printf "$1" | tee -a "${LOCAL_TMPDIR}/${LOGS}/${SCRIPT_LOGFILE}"
+}
+
+# Copy files from local tmpdir to remote host tmpdir
+copy_to_remote()
+{
+ for F in $@; do
+ scp "${LOCAL_TMPDIR}/${F}" root@"${REMOTE_HOST}:${REMOTE_TMPDIR}" 2>&1 >/dev/null
+ done
+}
+
+# Copy files from remote host tmpdir to local tmpdir
+copy_from_remote()
+{
+ for F in $@; do
+ scp root@"${REMOTE_HOST}:${REMOTE_TMPDIR}/${F}" "${LOCAL_TMPDIR}/" 2>&1 >/dev/null
+ done
+}
+
+# A wrapper for scmd calls to flashrom when we want to log the output
+# $1: 0 ($LOCAL) to run command locally,
+# 1 ($REMOTE) to run remotely if remote host defined
+# $2: arguments to be passed into scmd
+# $3: context of the flashrom call (to be used in the logfile)
+flashrom_log_scmd()
+{
+ local logfile="flashrom-${3}.txt"
+ local rc
+
+ if [ $1 -eq $REMOTE ]; then
+ tmpdir=$REMOTE_TMPDIR
+ else
+ tmpdir=$LOCAL_TMPDIR
+ fi
+
+ scmd $1 "$2 -o ${tmpdir}/${LOGS}/${logfile}"; rc=$?
+ # if the call was successful, we don't want to save the log (only save failure logs)
+ if [ $rc -eq $EXIT_SUCCESS ]; then
+ scmd $1 "rm -f ${tmpdir}/${LOGS}/${logfile}"
+ else
+ # if the log was stored remotely, we want to copy it over to local tmpdir
+ if [ $1 -eq $REMOTE ]; then
+ scp root@"${REMOTE_HOST}:${REMOTE_TMPDIR}/${LOGS}/${logfile}" "${LOCAL_TMPDIR}/${LOGS}" 2>&1 >/dev/null
+ fi
+ fi
+
+ return $rc
+}
+
+# Read current image as backup in case one hasn't already been specified.
+if [ -z "$BACKUP_IMAGE" ]; then
+ backup_file="backup.bin"
+ if [ $DO_REMOTE -eq 1 ]; then
+ BACKUP_IMAGE="${REMOTE_TMPDIR}/${backup_file}"
+ else
+ BACKUP_IMAGE="${LOCAL_TMPDIR}/${backup_file}"
+ fi
+
+ print_and_log "Reading backup image..."
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -r $BACKUP_IMAGE" "read_backup"
+
+ if [ $? -ne 0 ]; then
+ print_and_log "Failed to read backup image, aborting.\n"
+ exit $EXIT_FAILURE
+ fi
+
+ if [ $DO_REMOTE -eq 1 ]; then
+ copy_from_remote "$backup_file"
+ fi
+else
+ if [ $DO_REMOTE -eq 1 ]; then
+ scmd $DO_REMOTE "cp $BACKUP_IMAGE $REMOTE_TMPDIR"
+ copy_from_remote "$(basename $BACKUP_IMAGE)"
+ fi
+fi
+
+# The copy of flashrom to test. If unset, we'll assume the user wants to test
+# a newly built flashrom binary in the current directory.
+if [ -z "$NEW_FLASHROM" ] ; then
+ if [ -x "flashrom" ]; then
+ NEW_FLASHROM="flashrom"
+ else
+ print_and_log "Must supply new flashrom version to test\n"
+ exit $EXIT_FAILURE
+ fi
+fi
+
+print_and_log "Stable flashrom binary: ${OLD_FLASHROM}\n"
+print_and_log "New flashrom binary to test: ${NEW_FLASHROM}\n"
+print_and_log "Local temporary files will be stored in ${LOCAL_TMPDIR}\n"
+if [ $DO_REMOTE -eq 1 ]; then
+ print_and_log "Remote temporary files will be stored in ${REMOTE_HOST}:${REMOTE_TMPDIR}\n"
+ print_and_log "Backup image: ${REMOTE_HOST}:${BACKUP_IMAGE}\n"
+ print_and_log "Backup image also stored at: ${LOCAL_TMPDIR}/$(basename ${BACKUP_IMAGE})\n"
+else
+ print_and_log "Backup image: ${BACKUP_IMAGE}\n"
+fi
+
+#
+# Now the fun begins.
+#
+cmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS --flash-size" "${LOCAL_TMPDIR}/old_chip_size.txt.orig"
+cmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS --flash-size" "${LOCAL_TMPDIR}/new_chip_size.txt.orig"
+# get rid of banner and other superfluous output from flashrom
+tail -n 1 "${LOCAL_TMPDIR}/old_chip_size.txt.orig" > "${LOCAL_TMPDIR}/old_chip_size.txt"
+tail -n 1 "${LOCAL_TMPDIR}/new_chip_size.txt.orig" > "${LOCAL_TMPDIR}/new_chip_size.txt"
+tmp=$(cat ${LOCAL_TMPDIR}/old_chip_size.txt)
+CHIP_SIZE=$(cat ${LOCAL_TMPDIR}/new_chip_size.txt)
+CHIP_SIZE_KB=$(($CHIP_SIZE / $K))
+CHIP_SIZE_HALF=$(($CHIP_SIZE / 2))
+if [ $CHIP_SIZE -ne $tmp ]; then
+ print_and_log "New flashrom and old flashrom disagree on chip size. Aborting.\n"
+ exit $EXIT_FAILURE
+else
+ print_and_log "Chip size: $CHIP_SIZE_KB KiB\n"
+fi
+
+# Upload results
+#do_upload()
+#{
+# # TODO: implement this
+#}
+
+# Remove temporary files
+do_cleanup()
+{
+ if [ $NO_CLEAN -eq 1 ]; then
+ print_and_log "Skipping cleanup.\n"
+ return $EXIT_SUCCESS
+ fi
+
+ rm -rf "$LOCAL_TMPDIR"
+ if [ -n "$REMOTE_HOST" ]; then
+ ssh root@${REMOTE_HOST} rm -rf "$REMOTE_TMPDIR"
+ fi
+
+ return $EXIT_SUCCESS
+}
+
+# $1: Message to display to user.
+test_fail()
+{
+ print_and_log "$1\n"
+ printf "Skipping cleanup (logs saved).\n"
+ exit $EXIT_FAILURE
+}
+
+write_backup_image()
+{
+ print_and_log "Writing backup image.\n"
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -w $BACKUP_IMAGE" "write_backup"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to write backup image.\n"
+ fi
+}
+
+# Read a region twice and compare results
+# $1: address of region (in bytes)
+# $2: length of region (in bytes)
+double_read_test()
+{
+ local cmp1="${TMPDIR}/read_test1.bin"
+ local cmp2="${TMPDIR}/read_test2.bin"
+ local layout="double_read_test_layout.txt"
+ local len=$(($2 / $K))
+
+ print_and_log "Doing double read test, size: $len KiB\n"
+ # FIXME: Figure out how to do printf remotely...
+ printf "%06x:%06x region\n" $1 $(($1 + $2 - 1)) > "${LOCAL_TMPDIR}/${layout}"
+ if [ $DO_REMOTE -eq 1 ]; then copy_to_remote "$layout" ; fi
+
+ flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -l ${TMPDIR}/${layout} -i region -r ${cmp1}" "double_read_1"
+ # FIXME: second (or maybe third?) read should be done using secondary programmer, if applicable.
+ flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -l ${TMPDIR}/${layout} -i region -r ${cmp2}" "double_read_2"
+ scmd $DO_REMOTE "cmp $cmp1 $cmp2"
+ if [ $? -ne 0 ]; then
+ test_fail "Double-read test failed, aborting."
+ fi
+}
+
+PARTIAL_WRITE_TEST_REGION_SIZE=0
+PARTIAL_WRITE_TEST_ALIGN_SIZE_KB=0
+# FIXME: Hack due to lack of region-sized file handling.
+PARTIAL_WRITE_TEST_REGION_BASE_OFFSET=-1
+
+# helper function to reduce repetitiveness of partial_write_test
+partial_write_test_helper()
+{
+ local test_name="$1"
+ local pattern_offset_kb=$2
+ local pattern_size_kb=$3
+ local hex="$4"
+ local oct=""
+ local base_offset_kb=$(($PARTIAL_WRITE_TEST_REGION_BASE_OFFSET / $K))
+
+ oct="\\$(printf "%03o" $hex)"
+ cp "${LOCAL_TMPDIR}/random_4k_test.bin" "${LOCAL_TMPDIR}/${test_name}.bin"
+ dd if=/dev/zero bs=1k count=${pattern_size_kb} 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${pattern_size_kb}k.bin"
+
+ while [ $(($(($pattern_offset_kb + $pattern_size_kb)) * $K)) -lt $PARTIAL_WRITE_TEST_REGION_SIZE ]; do
+ dd if="${LOCAL_TMPDIR}/${hex}_${pattern_size_kb}k.bin" of="${LOCAL_TMPDIR}/${test_name}.bin" bs=1k count=$pattern_size_kb seek=$(($base_offset_kb + $pattern_offset_kb)) conv=notrunc 2>/dev/null
+ pattern_offset_kb=$(($pattern_offset_kb + $PARTIAL_WRITE_TEST_ALIGN_SIZE_KB))
+ done
+}
+
+# Regional partial write test. Given a region name, this will write patterns
+# of bytes designed to test corner cases.
+#
+# We assume that eraseable block size can be either 4KB or 64KB and
+# must test for both. For simplicity, we'll assume the region size is
+# at least 256KB.
+#
+# $1: Region name
+partial_write_test()
+{
+ local opts="--noverify-all"
+ local secondary_opts="" # for secondary programmer
+ local region_name="$1"
+ local filename=""
+ local test_num=0
+ local prev_test_num=0
+
+ # FIXME: Hack due to lack of region-sized file handling.
+ if [ $((PARTIAL_WRITE_TEST_REGION_SIZE)) -eq 0 ]; then
+ print_and_log "Size of $region_name unknown\n"
+ return $EXIT_FAILURE
+ fi
+ if [ $((PARTIAL_WRITE_TEST_REGION_BASE_OFFSET)) -lt 0 ]; then
+ print_and_log "Offset of $region_name is unknown\n"
+ return $EXIT_FAILURE
+ fi
+
+ if [ $TEST_TYPE -eq $TEST_TYPE_SINGLE ]; then
+ if [ $REGION_MODE -eq $REGION_MODE_LAYOUT ]; then
+ opts="$opts -l $LAYOUT_FILE"
+ secondary_opts="$opts"
+ elif [ $REGION_MODE -eq $REGION_MODE_CLOBBER ]; then
+ printf "000000:%06x RW\n" $(($CHIP_SIZE - 1)) > "${LOCAL_TMPDIR}/clobber_mode_layout.txt"
+ if [ $DO_REMOTE -eq 1 ]; then
+ copy_to_remote "clobber_mode_layout.txt"
+ fi
+ secondary_opts="$opts -l ${LOCAL_TMPDIR}/clobber_mode_layout.txt"
+ opts="$opts -l ${TMPDIR}/clobber_mode_layout.txt"
+ fi
+ fi
+
+ if [ $SMALL_REGION -eq 1 ]; then
+ PARTIAL_WRITE_TEST_ALIGN_SIZE_KB=16
+ else
+ PARTIAL_WRITE_TEST_ALIGN_SIZE_KB=256
+ fi
+
+ # FIXME: Add sanity checks.
+
+ print_and_log "Doing region-based partial write test on region \"$region_name\"\n"
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS $opts -i ${region_name} -r ${TMPDIR}/${region_name}.bin" "read_region_${region_name}"
+ if [ $DO_REMOTE -eq 1 ]; then
+ copy_from_remote "${region_name}.bin"
+ fi
+
+ if [ $(($PARTIAL_WRITE_TEST_REGION_SIZE % $(($PARTIAL_WRITE_TEST_ALIGN_SIZE_KB)))) -ne 0 ]; then
+ print_and_log "Region $region_name is not aligned to $PARTIAL_WRITE_TEST_ALIGN_SIZE_KB\n"
+ return $EXIT_FAILURE
+ fi
+
+ # Test procedure:
+ # Clobber region with random content first. Then do writes using the
+ # following sequences for each 128KB:
+ # 0-2K : 0x00 (\000) Partial 4KB sector, lower half
+ # 2K-6K : 0x11 (\021) Crossover 4KB sector boundary
+ # 6K-8K : 0x22 (\042) Partial 4KB sector, upper half
+ # 8K-16K : 0x33 (\063) Full 4KB sectors
+ #
+ # Repeat the above sequence for 64KB-aligned sizes
+ # 0-32K : 0x44 (\104) Partial 64KB block, lower half
+ # 32K-96K : 0x55 (\125) Crossover 64KB block boundary
+ # 96K-128K : 0x66 (\146) Partial 64KB block, upper half
+ # 128K-256K : 0x77 (\167) Full 64KB blocks
+
+ test_num=0
+ dd if=/dev/zero of="${LOCAL_TMPDIR}/random_4k_test.bin" bs=1k count=$CHIP_SIZE_KB 2>/dev/null
+ dd if=/dev/urandom of="${LOCAL_TMPDIR}/random_4k_test.bin" bs=4k count=$(($PARTIAL_WRITE_TEST_REGION_SIZE / $((4 * $K)))) seek=$(($((PARTIAL_WRITE_TEST_REGION_BASE_OFFSET)) / $((4 * $K)))) conv=notrunc 2>/dev/null
+
+ # 0-2K : 0x00 (\000) Partial 4KB sector, lower half
+ partial_write_test_helper "4k_test_${test_num}" 0 2 "0x00"
+ prev_test_num=$test_num
+ test_num=$(($test_num + 1))
+
+ # 2K-6K : 0x11 (\021) Crossover 4KB sector boundary
+ partial_write_test_helper "4k_test_${test_num}" 2 4 "0x11"
+ test_num=$(($test_num + 1))
+
+ # 6K-8K : 0x22 (\042) Partial 4KB sector, upper half
+ partial_write_test_helper "4k_test_${test_num}" 6 2 "0x22"
+ test_num=$(($test_num + 1))
+
+ # 8K-16K : 0x33 (\063) Full 4KB sectors
+ partial_write_test_helper "4k_test_${test_num}" 8 8 "0x33"
+
+ for F in ${LOCAL_TMPDIR}/random_4k_test.bin ${LOCAL_TMPDIR}/4k_test_*.bin ; do
+ filename=$(basename $F)
+ if [ $DO_REMOTE -eq 1 ]; then
+ copy_to_remote $filename
+ fi
+
+ flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS $opts -i ${region_name} -w ${TMPDIR}/${filename}" "write_${filename}"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to write $filename to $region_name"
+ fi
+
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS $opts -i ${region_name} -v ${TMPDIR}/${filename}" "verify_${filename}"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to verify write of $filename to $region_name"
+ fi
+
+ if [ -n "$SECONDARY_OPTS" ]; then
+ flashrom_log_scmd $LOCAL "$LOCAL_FLASHROM $SECONDARY_OPTS $secondary_opts -i ${region_name} -v ${LOCAL_TMPDIR}/${filename}" "verify_secondary_${filename}"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to verify write of $filename to $region_name using secondary programmer"
+ fi
+ fi
+
+ print_and_log "\tWrote $filename to $region_name region successfully.\n"
+ done
+
+ if [ $SMALL_REGION -eq 1 ]; then
+ return $EXIT_SUCCESS
+ fi
+
+ #
+ # Second half: Tests for 64KB chunks
+ #
+ test_num=0
+# dd if=/dev/urandom of="${LOCAL_TMPDIR}/random_64k_test.bin" bs=128k count=$(($PARTIAL_WRITE_TEST_REGION_SIZE / $((128*$K)))) 2>/dev/null
+ dd if=/dev/zero of="${LOCAL_TMPDIR}/random_64k_test.bin" bs=1k count=$CHIP_SIZE_KB 2>/dev/null
+ dd if=/dev/urandom of="${LOCAL_TMPDIR}/random_64k_test.bin" bs=128k count=$(($PARTIAL_WRITE_TEST_REGION_SIZE / $((128 * $K)))) seek=$(($((PARTIAL_WRITE_TEST_REGION_BASE_OFFSET)) / $((128 * $K)))) conv=notrunc 2>/dev/null
+
+ # 0-32K : 0x44 (\104) Partial 64KB block, lower half
+ partial_write_test_helper "64k_test_${test_num}" 0 32 "0x44"
+ prev_test_num=$test_num
+ test_num=$(($test_num + 1))
+
+ # 32K-96K : 0x55 (\125) Crossover 64KB block boundary
+ partial_write_test_helper "64k_test_${test_num}" 32 64 "0x55"
+ test_num=$(($test_num + 1))
+
+ # 96K-128K : 0x66 (\146) Partial 64KB block, upper half
+ partial_write_test_helper "64k_test_${test_num}" 96 32 "0x66"
+ test_num=$(($test_num + 1))
+
+ # 128K-256K : 0x77 (\167) Full 64KB blocks
+ partial_write_test_helper "64k_test_${test_num}" 128 128 "0x77"
+
+ for F in ${LOCAL_TMPDIR}/random_64k_test.bin ${LOCAL_TMPDIR}/64k_test_*.bin ; do
+ filename=$(basename $F)
+ if [ $DO_REMOTE -eq 1 ]; then
+ copy_to_remote $filename
+ fi
+
+ flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS $opts -i ${region_name} -w ${TMPDIR}/${filename}" "write_${filename}"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to write $filename to $region_name"
+ fi
+
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS $opts -i ${region_name} -v ${TMPDIR}/${filename}" "verify_${filename}"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to verify write of $filename to $region_name"
+ fi
+
+ if [ -n "$SECONDARY_OPTS" ]; then
+ flashrom_log_scmd $LOCAL "$LOCAL_FLASHROM $SECONDARY_OPTS $secondary_opts -i ${region_name} -v ${LOCAL_TMPDIR}/${filename}" "verify_secondary_${filename}"
+ if [ $? -ne 0 ]; then
+ test_fail "Failed to verify write of $filename to $region_name using secondary programmer"
+ fi
+ fi
+
+ print_and_log "\tWrote $filename to $region_name region successfully.\n"
+ done
+
+ return $EXIT_SUCCESS
+}
+
+# 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
+ test_fail "Failed to find flash chips while probing, aborting."
+fi
+
+# Read ROM twice to test for consistency.
+if [ $SKIP_CONSISTENCY_CHECK -eq 0 ]; then
+ double_read_test 0 $CHIP_SIZE
+fi
+
+if [ $TEST_TYPE -eq $TEST_TYPE_SINGLE ]; then
+ if [ $REGION_MODE -eq $REGION_MODE_CLOBBER ]; then
+ random_file="${TMPDIR}/random_${CHIP_SIZE_KB}K.bin"
+ cmp_file="${TMPDIR}/cmp.bin"
+
+ scmd $DO_REMOTE "dd if=/dev/urandom of=${random_file} bs=1k count=${CHIP_SIZE_KB}"
+ flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -w $random_file" "clobber_write"
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -r $cmp_file" "clobber_verify"
+ scmd $DO_REMOTE "cmp $random_file $cmp_file"
+ if [ $? -ne 0 ]; then
+ write_backup_image
+ test_fail "Failed to clobber entire ROM."
+ fi
+ elif [ $REGION_MODE -eq $REGION_MODE_DESCRIPTOR ]; then
+ # FIXME: Need region-sized file handling or some way to get region info
+ print_and_log "Currently broken due to lack of region-sized file handling."
+ exit $EXIT_FAILURE
+ elif [ $REGION_MODE -eq $REGION_MODE_FLASHMAP ]; then
+ # FIXME: Need region-sized file handling or some way to get region info
+ print_and_log "Currently broken due to lack of region-sized file handling."
+ exit $EXIT_FAILURE
+ elif [ $REGION_MODE -eq $REGION_MODE_LAYOUT ]; then
+ rw_layout=""
+ addr=""
+ end=""
+ size=""
+ size_kb=""
+
+ # Look for a region name with any amount of leading whitespace
+ # and no trailing whitespace or characters.
+ rw_layout=$(grep "\s${LAYOUT_REGION}$" "${LOCAL_TMPDIR}/$(basename $LAYOUT_FILE)" | head -n 1)
+ if [ -z "$rw_layout" ]; then
+ print_and_log "No region matching \"${LAYOUT_REGION}\" found layout file \"${LAYOUT_FILE}\"\n"
+ test_fail ""
+ fi
+
+ addr="0x$(echo "$rw_layout" | cut -d ' ' -f -1 | awk -F ':' '{ print $1 }')"
+ end="0x$(echo "$rw_layout" | cut -d ' ' -f -1 | awk -F ':' '{ print $2 }')"
+ size="$(($end - $addr + 1))"
+ size_kb="$(($size / $K))"
+
+ # FIXME: Hack to make this work without region-sized file handling.
+ PARTIAL_WRITE_TEST_REGION_BASE_OFFSET=$addr
+ PARTIAL_WRITE_TEST_REGION_SIZE=$size
+
+ print_and_log "\"$LAYOUT_REGION\" region address: ${addr}, size: $size_kb KiB\n"
+ partial_write_test "$LAYOUT_REGION"
+ if [ $? -ne 0 ]; then
+ print_and_log "Layout mode test failed\n"
+ RC=$EXIT_FAILURE
+ fi
+ fi
+elif [ $TEST_TYPE -eq $TEST_TYPE_ENDURANCE ]; then
+ iteration=1
+ terminate=0
+ random_file="${TMPDIR}/random_${CHIP_SIZE_KB}K.bin"
+ cmp_file="${TMPDIR}/cmp.bin"
+ # TODO: We can measure how long the tests take on average, throughput, etc.
+ # i.e. { time $NEW_FLASHROM $PRIMARY_OPTS -w $random_file ; } 2>&1 | grep user | cut -f2
+
+ # For this test we want to run clobber mode until failure
+ while [ $terminate -eq 0 ]
+ do
+ print_and_log "Running iteration #${iteration}\n"
+
+ scmd $DO_REMOTE "dd if=/dev/urandom of=${random_file} bs=1k count=${CHIP_SIZE_KB}"
+ flashrom_log_scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -w $random_file" "endurance_write_${iteration}"
+ flashrom_log_scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -r $cmp_file" "endurance_verify_${iteration}"
+ scmd $DO_REMOTE "cmp $random_file $cmp_file"
+ if [ $? -ne 0 ]; then
+ terminate=1
+ fi
+ scmd $DO_REMOTE "rm -f $cmp_file $random_file"
+
+ iteration=$(($iteration + 1))
+ done
+
+ # TODO: Determine what to return for the endurance test exit status
+ # i.e. what constitutes a test pass and what constitutes a test fail?
+ print_and_log "Failed on iteration $iteration\n"
+ # TODO - Print performance metrics?
+fi
+
+# restore and cleanup
+write_backup_image
+
+if [ $RC -eq 0 ]; then
+ print_and_log "Test status: PASS\n"
+else
+ print_and_log "Test status: FAIL\n"
+fi
+do_cleanup
+
+exit $RC