I had a bit more time to work on this today and managed to fix several bugs and simplify the code - now all modes use a single algorithm for testing. I probably won't do another major revision for a few days, but fortunately it seems much more usable now. I also updated the documentation at https://goo.gl/3jNoL7.
I also tried to get the test working with upstream, but there are a few features that need to be added first: - Add -i <region>[:<file>] support ( http://patchwork.coreboot.org/patch/4076/) - Partial reads need to be supported. - -r/-w/-v shouldn't require a positional argument ( https://gerrit.chromium.org/gerrit/60515) since we want to do purely partial reads/writes in this case. - Command-line options from chromium: --get-size to obtain chip size (I want to rename this --chip-size at some point), --ignore-fmap (which can be a noop), and --fast-verify (needed for Intel systems with read-locked regions, but good to have in any case).
So for now, those who want to try this script can do so using the chromium.org fork: https://chromium.googlesource.com/chromiumos/third_party/flashrom/
Here is patch revision 20 from https://chromium-review.googlesource.com/#/c/353912/:
From 8af85a07966b9486ea78c381c948aeb44bdcca7c Mon Sep 17 00:00:00 2001
From: David Hendricks dhendrix@chromium.org Date: Sun, 19 Jun 2016 12:53:22 -0700 Subject: [PATCH] WIP: New test script for flashrom
** work in progress **
Shiny new testing capabilities for Flashrom: - Region awareness - Remote testing - Primary and secondary programmer support - Autotest-friendly
Long-term goals: - Performance measurement - Endurance testing
BUG=chromium:621715 BRANCH=none TEST=this *is* the test
Change-Id: Ic643cf2edaf7d8772c63ce8119363d882d1b116d Signed-off-by: David Hendricks dhendrix@chromium.org --- tests/tests_v2/cmd.sh | 109 +++++++ tests/tests_v2/test_v2.sh | 798 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 907 insertions(+) create mode 100644 tests/tests_v2/cmd.sh create mode 100644 tests/tests_v2/test_v2.sh
diff --git a/tests/tests_v2/cmd.sh b/tests/tests_v2/cmd.sh new file mode 100644 index 0000000..1cfd219 --- /dev/null +++ b/tests/tests_v2/cmd.sh @@ -0,0 +1,109 @@ +#!/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. + +# 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 + + 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 + else + ssh $REMOTE_PORT_OPTION "root@${REMOTE_HOST}" "$2" > /dev/null 2>&1 + fi + else + if [ $silent -eq 0 ]; then + $2 > "$pipe_location" 2>/dev/null + else + $2 > /dev/null 2>&1 + fi + fi + + return $? +} + +# 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/tests/tests_v2/test_v2.sh b/tests/tests_v2/test_v2.sh new file mode 100644 index 0000000..cb9bace --- /dev/null +++ b/tests/tests_v2/test_v2.sh @@ -0,0 +1,798 @@ +#!/bin/sh +# +# Copyright (C) 2016 Google 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 + +# For old flashrom version, if -o is not specified +DEFAULT_FLASHROM_PATH="/usr/local/sbin/flashrom" + +# +# Stuff obtained from command-line +# + +# Generic options +BACKUP_IMAGE="" +OLD_FLASHROM="" +NEW_FLASHROM="" +NO_CLEAN=0 +SKIP_CONSISTENCY_CHECK=0 +UPLOAD_RESULTS=0 + +# Primary/Secondary programmer options +PRIMARY_OPTS="" +SECONDARY_OPTS="" + +# 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=$LOCAL # boolean to use for cmd() and tests + +# 1KB +K="1024" + +show_help() { + echo "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. + -u, --upload-results + Upload results to flashrom.org. + +Long options: + --descriptor-region <name> + Specify region to use in descriptor mode (default: $DESCRIPTOR_REGION) + --fmap-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) + --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). +" +} + +getopt -T +if [ $? -ne 4 ]; then + echo "GNU-compatible getopt(1) required." + exit $EXIT_FAILURE +fi + +LONGOPTS="backup-image:,help,,new:,old:,remote-host:,upload-results:" +LONGOPTS="${LONGOPTS},primary-programmer:,secondary-programmer:" +LONGOPTS="${LONGOPTS},skip-consistency-check" +LONGOPTS="${LONGOPTS},mode:,clobber,descriptor,flashmap,layout,small-region" +LONGOPTS="${LONGOPTS},layout-file:,desctriptor-region:,flashmap-region:,layout-region:" +LONGOPTS="${LONGOPTS},no-clean" +LONGOPTS="${LONGOPTS},ssh-port:" + +ARGS=$(getopt -o b:hl:m:n:o:p:r:s:u -l "$LONGOPTS" -n "$0" -- "$@"); +if [ $? != 0 ] ; then echo "Terminating..." >&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 + echo "Unknown mode: $1" + 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" + ;; + -r|--remote-host) + DO_REMOTE=1 + shift + REMOTE_HOST="$1" + ;; + -u|--upload-results) + UPLOAD_RESULTS=1 + ;; + + # Longopts only + --descriptor-region) + shift + DESCRIPTOR_REGION="$1" + ;; + --flashmap-region) + shift + FLASHMAP_REGION="$1" + ;; + --layout-region) + shift + LAYOUT_REGION="$1" + ;; + --no-clean) + NO_CLEAN=1 + ;; + --skip-consistency-check) + SKIP_CONSISTENCY_CHECK=1 + ;; + + # Remote testing options + --ssh-port) + shift + REMOTE_PORT_OPTION="-p $1" + ;; + + # error handling + --) + shift + if [ -n "$*" ]; then + echo "Non-option parameters detected: '$*'" + exit $EXIT_FAILURE + fi + break + ;; + *) + echo "error processing options at '$1'" + exit $EXIT_FAILURE + esac + shift +done + +# TODO: Implement this. +if [ $UPLOAD_RESULTS -eq 1 ]; then + echo "TODO: Implement ability to upload results." + exit $EXIT_FAILURE +fi + +# +# Source helper scripts +# +export REMOTE_HOST REMOTE_PORT_OPTION +export LOCAL REMOTE FATAL NONFATAL EXIT_SUCCESS EXIT_FAILURE +. "$(pwd)/tests/tests_v2/cmd.sh" + +# +# Test command-line validity. +# +if [ $REGION_MODE -eq $REGION_MODE_UNKNOWN ]; then + echo "Must specify a region access mode (-m/--mode)." + exit $EXIT_FAILURE +elif [ $REGION_MODE -eq $REGION_MODE_LAYOUT ]; then + if [ -z "$LAYOUT_FILE" ]; then + echo "Must specify a layout file when using layout mode." + 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 + echo "Layout file $LAYOUT_FILE not found${TMP}" + exit $EXIT_FAILURE + fi + + if [ $DO_REMOTE -eq 1 ]; then + scp root@"${REMOTE_HOST}:$LAYOUT_FILE" "${LOCAL_TMPDIR}/" 2>&1 >/dev/null + fi +fi + +if [ $DO_REMOTE -eq 1 ]; then + # Test connection to remote host + test_cmd $DO_REMOTE "ls /" $NONFATAL + if [ $? -ne 0 ]; then + echo "Could not connect to remote host $REMOTE_HOST" + exit $EXIT_FAILURE + fi +fi + +# +# Dependencies +# + +# cmp is used to compare files +test_cmd $DO_REMOTE "cmp" $FATAL + +if [ ! -e "/dev/urandom" ]; then + echo "This script uses /dev/urandom" + exit $EXIT_FAILURE +fi + +if [ ! -e "/dev/zero" ]; then + echo "This script uses /dev/zero" + exit $EXIT_FAILURE +fi + +# +# Setup. +# +grep -rH 'projectname = .*flashrom' .git/config >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "Script must be run from root of flashrom directory" + exit $EXIT_FAILURE +fi + +if [ -z "$OLD_FLASHROM" ]; then + OLD_FLASHROM="$DEFAULT_FLASHROM_PATH" +fi +test_cmd $DO_REMOTE "$OLD_FLASHROM --help" $NONFATAL +if [ $? -ne 0 ]; then + echo "Old flashrom binary is not usable." + exit $EXIT_FAILURE +fi + +# Setup temporary working directories: +# LOCAL_TMPDIR: Working directory on local host. +# REMOTE_TMPDIR: Working directory on remote host. +# TMPDIR: The temporary directy in which we do most of the work. This is +# convenient for commands that depend on $DO_REMOTE. +LOCAL_TMPDIR=$(mktemp -d --tmpdir flashrom_test.XXXXXXXX) +if [ $? -ne 0 ] ; then + echo "Could not create temporary directory" + exit $EXIT_FAILURE +fi + +if [ $DO_REMOTE -eq 1 ]; then + REMOTE_TMPDIR=$(ssh root@${REMOTE_HOST} mktemp -d --tmpdir flashrom_test.XXXXXXXX) + if [ $? -ne 0 ] ; then + echo "Could not create temporary directory" + exit $EXIT_FAILURE + fi +fi + +if [ $DO_REMOTE -eq 0 ]; then + TMPDIR="$LOCAL_TMPDIR" +else + TMPDIR="$REMOTE_TMPDIR" +fi + +# 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 +} + +# 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 + + scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -r $BACKUP_IMAGE" + if [ $? -ne 0 ]; then + echo "Failed to read backup image, aborting." + 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 + echo "Must supply new flashrom version to test" + exit $EXIT_FAILURE + fi +fi + +echo "Stable flashrom binary: $OLD_FLASHROM" +echo "New flashrom binary to test: $NEW_FLASHROM" +echo "Local temporary files will be stored in $LOCAL_TMPDIR" +if [ $DO_REMOTE -eq 1 ]; then + echo "Remote temporary files will be stored in ${REMOTE_HOST}:${REMOTE_TMPDIR}" + echo "Backup image: ${REMOTE_HOST}:${BACKUP_IMAGE}" + echo "Backup image also stored at: ${LOCAL_TMPDIR}/$(basename ${BACKUP_IMAGE})" +else + echo "Backup image: ${BACKUP_IMAGE}" +fi + +# +# Now the fun begins. +# +cmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS --get-size" "${LOCAL_TMPDIR}/chip_size.txt" +tmp=$(cat ${LOCAL_TMPDIR}/chip_size.txt) +cmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS --get-size" "${LOCAL_TMPDIR}/chip_size.txt" +CHIP_SIZE=$(cat ${LOCAL_TMPDIR}/chip_size.txt) +CHIP_SIZE_KB=$(($CHIP_SIZE / $K)) +if [ $CHIP_SIZE -ne $tmp ]; then + echo "New flashrom and old flashrom disagree on chip size. Aborting." + exit $EXIT_FAILURE +else + echo "Chip size: ${CHIP_SIZE_KB} KiB" +fi + +# Upload results +#do_upload() +#{ +# # TODO: implement this +#} + +# Remove temporary files +do_cleanup() +{ + if [ $NO_CLEAN -eq 1 ]; then + echo "Skipping cleanup." + 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() +{ + echo "$1" + do_cleanup + exit $EXIT_FAILURE +} + +write_backup_image() +{ + scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -w $BACKUP_IMAGE" +} + +# 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}/cmp1.bin" + local cmp2="${TMPDIR}/cmp2.bin" + local layout="double_read_test_layout.txt" + + printf "Doing double read test, size: %u KiB\n" $(($2 / $K)) + # 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 + + scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -r -l ${TMPDIR}/${layout} --ignore-fmap -i region:$cmp1" + scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -r -l ${TMPDIR}/${layout} --ignore-fmap -i region:$cmp2" + scmd $DO_REMOTE "cmp $cmp1 $cmp2" + if [ $? -ne 0 ]; then + test_fail "Double-read test failed, aborting." + fi +} + +# 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="--fast-verify" + local layout="partial_write_test_layout.txt" + local region_size=0 + local align_size_kb=0 + local region_name="$1" + local filename="" + local offset_kb=0 + local size_kb=0 + local test_num=0 + local prev_test_num=0 + + if [ $REGION_MODE -ne $REGION_MODE_FLASHMAP ]; then + opts="$opts --ignore-fmap" + fi + + if [ $REGION_MODE -eq $REGION_MODE_LAYOUT ]; then + opts="$opts -l $LAYOUT_FILE" + 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 + opts="$opts -l ${TMPDIR}/clobber_mode_layout.txt" + fi + + if [ $SMALL_REGION -eq 1 ]; then + align_size_kb=16 + else + align_size_kb=256 + fi + + # FIXME: Add sanity checks. + + echo "Doing region-based partial write test on region "$region_name"" + scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS $opts -r -i ${region_name}:${TMPDIR}/${region_name}.bin" + if [ $DO_REMOTE -eq 1 ]; then + copy_from_remote "${region_name}.bin" + fi + + region_size=$(stat --format=%s ${LOCAL_TMPDIR}/${region_name}.bin) + if [ $region_size -lt $(($align_size_kb * $K)) ]; then + echo "Region $region_name is too small" + return $EXIT_FAILURE + fi + + if [ $(($region_size % $(($align_size_kb * $K)))) -ne 0 ]; then + echo "Region $region_name is not aligned to ${align_size}KB" + 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/urandom of="${LOCAL_TMPDIR}/random_4k_test.bin" bs=4k count=$(($region_size / $((4 * $K)))) 2>/dev/null + + # 0-2K : 0x00 (\000) Partial 4KB sector, lower half + offset_kb=0 + size_kb=2 + hex="0x00" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/random_4k_test.bin" "${LOCAL_TMPDIR}/4k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/4k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + prev_test_num=$test_num + test_num=$(($test_num + 1)) + + # 2K-6K : 0x11 (\021) Crossover 4KB sector boundary + offset_kb=2 + size_kb=4 + hex="0x11" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/4k_test_${prev_test_num}.bin" "${LOCAL_TMPDIR}/4k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/4k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + test_num=$(($test_num + 1)) + + # 6K-8K : 0x22 (\042) Partial 4KB sector, upper half + offset_kb=2 + size_kb=4 + hex="0x11" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/4k_test_${prev_test_num}.bin" "${LOCAL_TMPDIR}/4k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/4k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + test_num=$(($test_num + 1)) + + # 8K-16K : 0x33 (\063) Full 4KB sectors + offset_kb=8 + size_kb=8 + hex="0x22" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/4k_test_${prev_test_num}.bin" "${LOCAL_TMPDIR}/4k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/4k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + test_num=$(($test_num + 1)) + + 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 + + scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS $opts -w -i ${region_name}:${TMPDIR}/${filename}" + if [ $? -ne 0 ]; then + test_fail "Failed to write $filename to $region_name" + fi + + scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS $opts -v -i ${region_name}:${TMPDIR}/${filename}" + if [ $? -ne 0 ]; then + test_fail "Failed to verify write of $filename to $region_name" + fi + + if [ -n "$SECONDARY_OPTS" ]; then + scmd $LOCAL "$OLD_FLASHROM $SECONDARY_OPTS $opts -v -i ${region_name}:${TMPDIR}/${filename}" + if [ $? -ne 0 ]; then + test_fail "Failed to verify write of $filename to $region_name using secondary programmer" + fi + fi + + printf "\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=$(($region_size / $((128*$K)))) 2>/dev/null + + # 0-32K : 0x44 (\104) Partial 64KB block, lower half + offset_kb=0 + size_kb=32 + hex="0x44" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/random_64k_test.bin" "${LOCAL_TMPDIR}/64k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/64k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + prev_test_num=$test_num + test_num=$(($test_num + 1)) + + # 32K-96K : 0x55 (\125) Crossover 64KB block boundary + offset_kb=32 + size_kb=64 + hex="0x55" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/64k_test_${prev_test_num}.bin" "${LOCAL_TMPDIR}/64k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/64k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + test_num=$(($test_num + 1)) + + # 96K-128K : 0x66 (\146) Partial 64KB block, upper half + offset_kb=96 + size_kb=32 + hex="0x66" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/64k_test_${prev_test_num}.bin" "${LOCAL_TMPDIR}/64k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/64k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + test_num=$(($test_num + 1)) + + # 128K-256K : 0x77 (\167) Full 64KB blocks + offset_kb=128 + size_kb=128 + hex="0x77" + oct="\$(printf "%03o" $hex)" + cp "${LOCAL_TMPDIR}/64k_test_${prev_test_num}.bin" "${LOCAL_TMPDIR}/64k_test_${test_num}.bin" + while [ $(($offset_kb * $K)) -lt $region_size ]; do + dd if=/dev/zero bs=1k count=$size_kb 2>/dev/null | tr "\000" "$oct" > "${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" + dd if="${LOCAL_TMPDIR}/${hex}_${size_kb}k.bin" of="${LOCAL_TMPDIR}/64k_test_${test_num}.bin" bs=1k count=${size_kb} seek=${offset_kb} conv=notrunc 2>/dev/null + offset_kb=$(($offset_kb + $align_size_kb)) + done + + 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 + + scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS $opts -w -i ${region_name}:${TMPDIR}/${filename}" + if [ $? -ne 0 ]; then + test_fail "Failed to write $filename to $region_name" + fi + + scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS $opts -v -i ${region_name}:${TMPDIR}/${filename}" + if [ $? -ne 0 ]; then + test_fail "Failed to verify write of $filename to $region_name" + fi + + if [ -n "$SECONDARY_OPTS" ]; then + scmd $LOCAL "$OLD_FLASHROM $SECONDARY_OPTS $opts -v -i ${region_name}:${TMPDIR}/${filename}" + if [ $? -ne 0 ]; then + test_fail "Failed to verify write of $filename to $region_name using secondary programmer" + fi + fi + + printf "\tWrote $filename to $region_name region successfully.\n" + done + + return $EXIT_SUCCESS +} + +# Do a consistency check for sanity before any other test. +if [ $SKIP_CONSISTENCY_CHECK -eq 0 ]; then + double_read_test 0 $CHIP_SIZE +fi + +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}" + scmd $DO_REMOTE "$NEW_FLASHROM $PRIMARY_OPTS -w $random_file" + scmd $DO_REMOTE "$OLD_FLASHROM $PRIMARY_OPTS -r $cmp_file" + scmd $DO_REMOTE "cmp $random_file $cmp_file" + if [ $? -ne 0 ]; then + write_backup_image + test_fail "Failed to clobber entire ROM." + fi + scmd $DO_REMOTE "rm -f $cmp_file $random_file" + + partial_write_test "RW" + if [ $? -ne 0 ]; then + echo "Layout mode test failed" + RC=$EXIT_FAILURE + fi +elif [ $REGION_MODE -eq $REGION_MODE_DESCRIPTOR ]; then + # FIXME: This depends on descriptor regions being translated into internal + # layout representation automatically so we can target them using -i. + echo "TODO: Descriptor mode" + exit $EXIT_FAILURE +elif [ $REGION_MODE -eq $REGION_MODE_FLASHMAP ]; then + partial_write_test "$FLASHMAP_REGION" 0 + if [ $? -ne 0 ]; then + echo "Flashmap mode test failed" + RC=$EXIT_FAILURE + fi +elif [ $REGION_MODE -eq $REGION_MODE_LAYOUT ]; then + rw_layout="" + addr="" + end="" + size="" + + # Look for a region named "RW" with any amount of leading whitespace + # and no trailing whitespace or characters. + rw_layout=$(grep "\s${LAYOUT_REGION}$" $LAYOUT_FILE | head -n 1) + if [ -z "$rw_layout" ]; then + printf "No region matching "${LAYOUT_REGION}" found layout file "%s"\n" "$LAYOUT_FILE" + 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))" + + printf ""$LAYOUT_REGION" region address: ${addr}, size: %u KiB\n" $(($size / $K)) + partial_write_test "$LAYOUT_REGION" + if [ $? -ne 0 ]; then + echo "Layout mode test failed" + RC=$EXIT_FAILURE + fi +fi + +# restore and cleanup +write_backup_image +do_cleanup + +if [ $RC -eq 0 ]; then + echo "Test status: PASS" +else + echo "Test status: FAIL" +fi + +exit $RC