David Hendricks has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/37407 )
Change subject: print.c: Fix alignment in print_supported_boards_helper()
......................................................................
print.c: Fix alignment in print_supported_boards_helper()
Commit 61e16e5 eliminated some duplicate logic for printing test status,
but in doing so introduced a regression where some entries in `flashrom
-L` output became unaligned.
Up until then, it was assumed that the status field will always be
8-wide, including space for padding. This patch fixes alignment when
using test_state_to_text() by making the padding consistent with other
fields where a for-loop is used to pad the field up to a maximum width.
Change-Id: Ie0a0b45c6466d14447aca443c2697e2048c6ef14
Signed-off-by: David Hendricks <david.hendricks(a)gmail.com>
---
M flash.h
M print.c
2 files changed, 29 insertions(+), 14 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/07/37407/1
diff --git a/flash.h b/flash.h
index 1a9bd9f..e7b1c89 100644
--- a/flash.h
+++ b/flash.h
@@ -145,10 +145,11 @@
enum test_state {
OK = 0,
- NT = 1, /* Not tested */
- BAD, /* Known to not work */
- DEP, /* Support depends on configuration (e.g. Intel flash descriptor) */
- NA, /* Not applicable (e.g. write support on ROM chips) */
+ NT = 1, /* Not tested */
+ BAD, /* Known to not work */
+ DEP, /* Support depends on configuration (e.g. Intel flash descriptor) */
+ NA, /* Not applicable (e.g. write support on ROM chips) */
+ TEST_STATE_END, /* Not a test state, just an indicator of how many are defined. */
};
#define TEST_UNTESTED (struct tested){ .probe = NT, .read = NT, .erase = NT, .write = NT }
diff --git a/print.c b/print.c
index 6a7ff5d..e5be6b6 100644
--- a/print.c
+++ b/print.c
@@ -35,6 +35,20 @@
}
}
+static size_t max_test_state_len(void)
+{
+ int i;
+ size_t max_len;
+
+ for (i = max_len = 0; i < TEST_STATE_END; i++) {
+ size_t len = strlen(test_state_to_text(i));
+ if (len > max_len)
+ max_len = len;
+ }
+
+ return max_len;
+}
+
static int print_supported_chips(void)
{
const char *delim = "/";
@@ -388,8 +402,12 @@
for (i = strlen("Board"); i < maxboardlen; i++)
msg_ginfo(" ");
- msg_ginfo("Status Required value for\n");
- for (i = 0; i < maxvendorlen + maxboardlen + strlen("Status "); i++)
+ msg_ginfo("Status");
+ for (i = strlen("Status"); i < max_test_state_len(); i++)
+ msg_ginfo(" ");
+
+ msg_ginfo("Required value for\n");
+ for (i = 0; i < maxvendorlen + maxboardlen + max_test_state_len(); i++)
msg_ginfo(" ");
msg_ginfo("-p internal:mainboard=\n");
@@ -401,14 +419,10 @@
for (i = 0; i < maxboardlen - strlen(b->name); i++)
msg_ginfo(" ");
- switch (b->working) {
- case OK: msg_ginfo("OK "); break;
- case NT: msg_ginfo("NT "); break;
- case DEP: msg_ginfo("DEP "); break;
- case NA: msg_ginfo("N/A "); break;
- case BAD:
- default: msg_ginfo("BAD "); break;
- }
+ const char *test_state = test_state_to_text(b->working);
+ msg_ginfo("%s", test_state);
+ for (i = 0; i < max_test_state_len() - strlen(test_state); i++)
+ msg_ginfo(" ");
for (e = board_matches; e->vendor_name != NULL; e++) {
if (strcmp(e->vendor_name, b->vendor)
--
To view, visit https://review.coreboot.org/c/flashrom/+/37407
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: Ie0a0b45c6466d14447aca443c2697e2048c6ef14
Gerrit-Change-Number: 37407
Gerrit-PatchSet: 1
Gerrit-Owner: David Hendricks <david.hendricks(a)gmail.com>
Gerrit-MessageType: newchange
David Hendricks has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/37406 )
Change subject: dediprog: add serial argument
......................................................................
dediprog: add serial argument
A quick hack to be able to select dediprogs by USB serial argument by
just adding a @serial_number parameter to dediprog_open() and using it
in preference to @id if available (since it is more specific).
Change-Id: I9cdfbce6cf941c16bf7b7364aa4166b91369e661
Signed-off-by: Inaky Perez-Gonzalez <inaky.perez-gonzalez(a)intel.com>
---
M dediprog.c
M flashrom.8.tmpl
2 files changed, 23 insertions(+), 7 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/06/37406/1
diff --git a/dediprog.c b/dediprog.c
index 175e099..480cf67 100644
--- a/dediprog.c
+++ b/dediprog.c
@@ -1006,15 +1006,19 @@
/*
* Open a dediprog_handle with the USB device at the given index.
* @index index of the USB device
+ * @serial_number serial number of the USB device (id is ignored then)
* @return 0 for success, -1 for error, -2 for busy device
*/
-static int dediprog_open(int index)
+static int dediprog_open(int index, char *serial_number)
{
const uint16_t vid = devs_dediprog[0].vendor_id;
const uint16_t pid = devs_dediprog[0].device_id;
int ret;
- dediprog_handle = usb_dev_get_by_vid_pid_number(usb_ctx, vid, pid, (unsigned int) index);
+ if (serial_number)
+ dediprog_handle = usb_dev_get_by_vid_pid_serial(usb_ctx, vid, pid, serial_number);
+ else
+ dediprog_handle = usb_dev_get_by_vid_pid_number(usb_ctx, vid, pid, (unsigned int) index);
if (!dediprog_handle) {
msg_perr("Could not find a Dediprog programmer on USB.\n");
libusb_exit(usb_ctx);
@@ -1057,7 +1061,8 @@
int dediprog_init(void)
{
- char *voltage, *id_str, *device, *spispeed, *target_str;
+ char *voltage, *id_str, *device, *spispeed, *target_str,
+ *serial_number;
int spispeed_idx = 1;
int millivolt = 3500;
int id = -1; /* -1 defaults to enumeration order */
@@ -1091,6 +1096,7 @@
msg_pinfo("Setting voltage to %i mV\n", millivolt);
}
+ serial_number = extract_programmer_param("serial");
id_str = extract_programmer_param("id");
if (id_str) {
char prefix0, prefix1;
@@ -1183,9 +1189,14 @@
return 1;
}
- if (id != -1) {
+ if (serial_number) {
+ if (dediprog_open(0, serial_number)) {
+ return 1;
+ }
+ found_id = dediprog_read_id();
+ } else if (id != -1) {
for (i = 0; ; i++) {
- ret = dediprog_open(i);
+ ret = dediprog_open(i, NULL);
if (ret == -1) {
/* no dev */
libusb_exit(usb_ctx);
@@ -1218,7 +1229,7 @@
break;
}
} else {
- if (dediprog_open(usedevice)) {
+ if (dediprog_open(usedevice, NULL)) {
return 1;
}
found_id = dediprog_read_id();
@@ -1276,6 +1287,5 @@
if (register_spi_master(&spi_master_dediprog) || dediprog_set_leds(LED_NONE))
return 1;
-
return 0;
}
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 27f3846..5af1a38 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -928,6 +928,12 @@
.BR 0V ", " 1.8V ", " 2.5V ", " 3.5V
or the equivalent in mV.
.sp
+You can use the
+.B serial
+parameter to explicitly specify which dediprog device should be used
+based on their USB serial number::
+.sp
+.B " flashrom \-p dediprog:serial=1230A12"
An optional
.B device
parameter specifies which of multiple connected Dediprog devices should be used.
--
To view, visit https://review.coreboot.org/c/flashrom/+/37406
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: I9cdfbce6cf941c16bf7b7364aa4166b91369e661
Gerrit-Change-Number: 37406
Gerrit-PatchSet: 1
Gerrit-Owner: David Hendricks <david.hendricks(a)gmail.com>
Gerrit-MessageType: newchange
Angel Pons has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/39173 )
Change subject: chipset_enable.c: Mark Skylake U Premium as DEP
......................................................................
chipset_enable.c: Mark Skylake U Premium as DEP
Tested reading, writing and erasing the internal flash chip using an
Acer Aspire ES1-572 laptop with an Intel i3-6006U. However, since all
ME-enabled chipsets are marked as DEP instead of OK, this one shall
follow suit as well.
Change-Id: Ib8ee9b5e811df74d2f48bd409806c72fe862bc24
Signed-off-by: Angel Pons <th3fanbus(a)gmail.com>
---
M chipset_enable.c
1 file changed, 1 insertion(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/73/39173/1
diff --git a/chipset_enable.c b/chipset_enable.c
index 84e4b6b..4afb0e4 100644
--- a/chipset_enable.c
+++ b/chipset_enable.c
@@ -1987,7 +1987,7 @@
{0x8086, 0x9d41, B_S, NT, "Intel", "Skylake / Kaby Lake Sample", enable_flash_pch100},
{0x8086, 0x9d43, B_S, NT, "Intel", "Skylake U Base", enable_flash_pch100},
{0x8086, 0x9d46, B_S, NT, "Intel", "Skylake Y Premium", enable_flash_pch100},
- {0x8086, 0x9d48, B_S, NT, "Intel", "Skylake U Premium", enable_flash_pch100},
+ {0x8086, 0x9d48, B_S, DEP, "Intel", "Skylake U Premium", enable_flash_pch100},
{0x8086, 0x9d4b, B_S, NT, "Intel", "Kaby Lake Y w/ iHDCP2.2 Prem.", enable_flash_pch100},
{0x8086, 0x9d4e, B_S, DEP, "Intel", "Kaby Lake U w/ iHDCP2.2 Prem.", enable_flash_pch100},
{0x8086, 0x9d50, B_S, NT, "Intel", "Kaby Lake U w/ iHDCP2.2 Base", enable_flash_pch100},
--
To view, visit https://review.coreboot.org/c/flashrom/+/39173
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: Ib8ee9b5e811df74d2f48bd409806c72fe862bc24
Gerrit-Change-Number: 39173
Gerrit-PatchSet: 1
Gerrit-Owner: Angel Pons <th3fanbus(a)gmail.com>
Gerrit-MessageType: newchange
Samir Ibradžić has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/38705 )
Change subject: ft2232_spi: Enhance csgpiol parameter for FT2232
......................................................................
ft2232_spi: Enhance csgpiol parameter for FT2232
This allows multiple 'csgpiol' bits to be set to active state at the
same time. Previously, only one GPIOL could be activated. I have an
use-case such that FT4232H is wired to two different SPI chips, and in
order to select one of them two GPIOLs have to be set.
Now, one can enable any particular GPIOL, for example:
csgpiol=01
would activate GPIOL0 and GPIOL1 at the same time.
The change is backward-compatible with previous csgpiol formatting.
Signed-off-by: Samir Ibradzic <sibradzic(a)gmail.com>
Change-Id: I645ddaa9852e9995bd2a6764862fda2b2ef0c26b
---
M ft2232_spi.c
1 file changed, 26 insertions(+), 14 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/05/38705/1
diff --git a/ft2232_spi.c b/ft2232_spi.c
index 5bdc2a7..947050f 100644
--- a/ft2232_spi.c
+++ b/ft2232_spi.c
@@ -86,10 +86,17 @@
/* The variables cs_bits and pindir store the values for the "set data bits low byte" MPSSE command that
* sets the initial state and the direction of the I/O pins. The pin offsets are as follows:
- * SCK is bit 0.
- * DO is bit 1.
- * DI is bit 2.
- * CS is bit 3.
+ * TCK/SK is bit 0.
+ * TDI/DO is bit 1.
+ * TDO/DI is bit 2.
+ * TMS/CS is bit 3.
+ * GPIOL0 is bit 4.
+ * GPIOL1 is bit 5.
+ * GPIOL2 is bit 6.
+ * GPIOL3 is bit 7.
+ *
+ * The pin signal direction bit offsets follow the same order; 0 means that
+ * pin at the matching bit index is an input, 1 means pin is an output.
*
* The default values (set below) are used for most devices:
* value: 0x08 CS=high, DI=low, DO=low, SK=low
@@ -331,19 +338,24 @@
}
free(arg);
+ /* Allows setting multiple GPIOL states, for example: csgpiol=012 */
arg = extract_programmer_param("csgpiol");
if (arg) {
- char *endptr;
- unsigned int temp = strtoul(arg, &endptr, 10);
- if (*endptr || endptr == arg || temp > 3) {
- msg_perr("Error: Invalid GPIOL specified: \"%s\".\n"
- "Valid values are between 0 and 3.\n", arg);
- free(arg);
- return -2;
+ unsigned int ngpios = strlen(arg);
+ for (unsigned int i=0; i<=ngpios; i++) {
+ int temp = arg[i] - '0';
+ if (ngpios == 0 || (ngpios != i && (temp < 0 || temp > 3))) {
+ msg_perr("Error: Invalid GPIOLs specified: \"%s\".\n"
+ "Valid values are numbers between 0 and 3. "
+ "Multiple GPIOLs can be specified.\n", arg);
+ free(arg);
+ return -2;
+ } else {
+ unsigned int pin = temp + 4;
+ cs_bits |= 1 << pin;
+ pindir |= 1 << pin;
+ }
}
- unsigned int pin = temp + 4;
- cs_bits |= 1 << pin;
- pindir |= 1 << pin;
}
free(arg);
--
To view, visit https://review.coreboot.org/c/flashrom/+/38705
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: I645ddaa9852e9995bd2a6764862fda2b2ef0c26b
Gerrit-Change-Number: 38705
Gerrit-PatchSet: 1
Gerrit-Owner: Samir Ibradžić <sibradzic(a)gmail.com>
Gerrit-MessageType: newchange
Edward O'Callaghan has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/38936 )
Change subject: raiden_debug: Upstream ChromiumOS usb_device helpers
......................................................................
raiden_debug: Upstream ChromiumOS usb_device helpers
These are helpful usb device accessors and helpers that
are later used for the so-called Raiden debugger programmer.
BUG=b:143389556
BRANCH=none
TEST=builds
Change-Id: Ic928220fc919fe4958c8150e61e11470dac88f13
Signed-off-by: Edward O'Callaghan <quasisec(a)google.com>
---
M Makefile
M meson.build
A usb_device.c
A usb_device.h
4 files changed, 559 insertions(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/36/38936/1
diff --git a/Makefile b/Makefile
index 7242b09..bfd4d1e 100644
--- a/Makefile
+++ b/Makefile
@@ -1088,7 +1088,7 @@
ifneq ($(NEED_LIBUSB1), )
CHECK_LIBUSB1 = yes
FEATURE_CFLAGS += -D'NEED_LIBUSB1=1'
-PROGRAMMER_OBJS += usbdev.o
+PROGRAMMER_OBJS += usbdev.o usb_device.o
# FreeBSD and DragonflyBSD use a reimplementation of libusb-1.0 that is simply called libusb
ifeq ($(TARGET_OS),$(filter $(TARGET_OS),FreeBSD DragonFlyBSD))
USB1LIBS += -lusb
diff --git a/meson.build b/meson.build
index 375089c..45a3681 100644
--- a/meson.build
+++ b/meson.build
@@ -84,6 +84,7 @@
# some programmers require libusb
if get_option('usb')
srcs += 'usbdev.c'
+ srcs += 'usb_device.c'
deps += dependency('libusb-1.0')
else
config_ch341a_spi = false
diff --git a/usb_device.c b/usb_device.c
new file mode 100644
index 0000000..48833fb
--- /dev/null
+++ b/usb_device.c
@@ -0,0 +1,393 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2020, Google Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#include "programmer.h"
+#include "spi.h"
+#include "usb_device.h"
+
+#include <assert.h>
+#include <libusb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Possibly extract a programmer parameter and use it to initialize the given
+ * match value structure.
+ */
+static void usb_match_value_init(struct usb_match_value *match,
+ char const *parameter)
+{
+ char *string = extract_programmer_param(parameter);
+
+ match->name = parameter;
+
+ if (string) {
+ match->set = 1;
+ match->value = strtol(string, NULL, 0);
+ } else {
+ match->set = 0;
+ }
+
+ free(string);
+}
+
+#define USB_MATCH_VALUE_INIT(NAME) \
+ usb_match_value_init(&match->NAME, #NAME)
+
+void usb_match_init(struct usb_match *match)
+{
+ USB_MATCH_VALUE_INIT(vid);
+ USB_MATCH_VALUE_INIT(pid);
+ USB_MATCH_VALUE_INIT(bus);
+ USB_MATCH_VALUE_INIT(address);
+ USB_MATCH_VALUE_INIT(config);
+ USB_MATCH_VALUE_INIT(interface);
+ USB_MATCH_VALUE_INIT(altsetting);
+ USB_MATCH_VALUE_INIT(class);
+ USB_MATCH_VALUE_INIT(subclass);
+ USB_MATCH_VALUE_INIT(protocol);
+}
+
+void usb_match_value_default(struct usb_match_value *value,
+ long int default_value)
+{
+ if (value->set)
+ return;
+
+ value->set = 1;
+ value->value = default_value;
+}
+
+/*
+ * Match the value against a possible user supplied parameter.
+ *
+ * Return:
+ * 0: The user supplied the given parameter and it did not match the value.
+ * 1: Either the user didn't supply the parameter, or they did and it
+ * matches the given value.
+ */
+static int check_match(struct usb_match_value const *match_value, int value)
+{
+ int reject = match_value->set && (match_value->value != value);
+
+ if (reject)
+ msg_pdbg("USB: Rejecting device because %s = %d != %d\n",
+ match_value->name,
+ value,
+ match_value->value);
+
+ return !reject;
+}
+
+/*
+ * Allocate a copy of device and add it to the head of the devices list.
+ */
+static void add_device(struct usb_device *device,
+ struct usb_device **devices)
+{
+ struct usb_device *copy = malloc(sizeof(struct usb_device));
+
+ assert(copy != NULL);
+
+ *copy = *device;
+
+ copy->next = *devices;
+ *devices = copy;
+
+ libusb_ref_device(copy->device);
+}
+
+/*
+ * Look through the interfaces of the current device config for a match. Stop
+ * looking after the first valid match is found.
+ *
+ * Return:
+ * 0: No matching interface was found.
+ * 1: A complete match was found and added to the devices list.
+ */
+static int find_interface(struct usb_match const *match,
+ struct usb_device *current,
+ struct usb_device **devices)
+{
+ int i, j;
+
+ for (i = 0; i < current->config_descriptor->bNumInterfaces; ++i) {
+ struct libusb_interface const *interface;
+
+ interface = ¤t->config_descriptor->interface[i];
+
+ for (j = 0; j < interface->num_altsetting; ++j) {
+ struct libusb_interface_descriptor const *descriptor;
+
+ descriptor = &interface->altsetting[j];
+
+ if (check_match(&match->interface,
+ descriptor->bInterfaceNumber) &&
+ check_match(&match->altsetting,
+ descriptor->bAlternateSetting) &&
+ check_match(&match->class,
+ descriptor->bInterfaceClass) &&
+ check_match(&match->subclass,
+ descriptor->bInterfaceSubClass) &&
+ check_match(&match->protocol,
+ descriptor->bInterfaceProtocol)) {
+ current->interface_descriptor = descriptor;
+ add_device(current, devices);
+ msg_pdbg("USB: Found matching device\n");
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Look through the configs of the current device for a match. Stop looking
+ * after the first valid match is found.
+ *
+ * Return:
+ * 0: All configurations successfully checked, one may have been added to
+ * the list.
+ * non-zero: There was a failure while checking for a match.
+ */
+static int find_config(struct usb_match const *match,
+ struct usb_device *current,
+ struct libusb_device_descriptor const *device_descriptor,
+ struct usb_device **devices)
+{
+ int i;
+
+ for (i = 0; i < device_descriptor->bNumConfigurations; ++i) {
+ int ret = LIBUSB(libusb_get_config_descriptor(
+ current->device, i,
+ ¤t->config_descriptor));
+ if (ret != 0) {
+ msg_perr("USB: Failed to get config descriptor");
+ return ret;
+ }
+
+ if (check_match(&match->config,
+ current->config_descriptor->
+ bConfigurationValue) &&
+ find_interface(match, current, devices))
+ break;
+
+ libusb_free_config_descriptor(current->config_descriptor);
+ }
+
+ return 0;
+}
+
+int usb_device_find(struct usb_match const *match, struct usb_device **devices)
+{
+ libusb_device **list;
+ ssize_t count;
+ ssize_t i;
+
+ *devices = NULL;
+
+ int ret = LIBUSB(count = libusb_get_device_list(NULL, &list));
+ if (ret != 0) {
+ msg_perr("USB: Failed to get device list");
+ return ret;
+ }
+
+ for (i = 0; i < count; ++i) {
+ struct libusb_device_descriptor descriptor;
+ struct usb_device current = {
+ .device = list[i],
+ .handle = NULL,
+ .next = NULL,
+ };
+
+ uint8_t bus = libusb_get_bus_number(list[i]);
+ uint8_t address = libusb_get_device_address(list[i]);
+
+ msg_pdbg("USB: Inspecting device (Bus %d, Address %d)\n",
+ bus,
+ address);
+
+ ret = LIBUSB(libusb_get_device_descriptor(list[i],
+ &descriptor));
+ if (ret != 0) {
+ msg_perr("USB: Failed to get device descriptor");
+ return ret;
+ }
+
+ if (check_match(&match->vid, descriptor.idVendor) &&
+ check_match(&match->pid, descriptor.idProduct) &&
+ check_match(&match->bus, bus) &&
+ check_match(&match->address, address)) {
+ ret = find_config(match,
+ ¤t,
+ &descriptor,
+ devices);
+ if (ret != 0) {
+ msg_perr("USB: Failed to find config");
+ return ret;
+ }
+ }
+ }
+
+ libusb_free_device_list(list, 1);
+
+ return (*devices == NULL);
+}
+
+/*
+ * If the underlying libusb device is not open, open it.
+ *
+ * Return:
+ * 0: The device was already open or was successfully opened.
+ * non-zero: There was a failure while opening the device.
+ */
+static int usb_device_open(struct usb_device *device)
+{
+ if (device->handle == NULL) {
+ int ret = LIBUSB(libusb_open(device->device, &device->handle));
+ if (ret != 0) {
+ msg_perr("USB: Failed to open device\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int usb_device_show(char const *prefix, struct usb_device *device)
+{
+ struct libusb_device_descriptor descriptor;
+ unsigned char product[256];
+ int ret;
+
+ ret = usb_device_open(device);
+ if (ret != 0) {
+ msg_perr("USB: Failed to open device\n");
+ return ret;
+ }
+
+ ret = LIBUSB(libusb_get_device_descriptor(device->device, &descriptor));
+ if (ret != 0) {
+ msg_perr("USB: Failed to get device descriptor\n");
+ return ret;
+ }
+
+ ret = LIBUSB(libusb_get_string_descriptor_ascii(
+ device->handle,
+ descriptor.iProduct,
+ product,
+ sizeof(product)));
+ if (ret != 0) {
+ msg_perr("USB: Failed to get device product string\n");
+ return ret;
+ }
+
+ product[255] = '\0';
+
+ msg_perr("%sbus=0x%02x,address=0x%02x | %s\n",
+ prefix,
+ libusb_get_bus_number(device->device),
+ libusb_get_device_address(device->device),
+ product);
+
+ return 0;
+}
+
+int usb_device_claim(struct usb_device *device)
+{
+ int current_config;
+
+ int ret = usb_device_open(device);
+ if (ret != 0) {
+ msg_perr("USB: Failed to open device\n");
+ return ret;
+ }
+
+ ret = LIBUSB(libusb_get_configuration(device->handle,
+ ¤t_config));
+ if (ret != 0) {
+ msg_perr("USB: Failed to get current device configuration\n");
+ return ret;
+ }
+
+ if (current_config != device->config_descriptor->bConfigurationValue) {
+ ret = LIBUSB(libusb_set_configuration(
+ device->handle,
+ device->
+ config_descriptor->
+ bConfigurationValue));
+ if (ret != 0) {
+ msg_perr("USB: Failed to set new configuration from %d to %d\n",
+ current_config,
+ device->config_descriptor->bConfigurationValue);
+ return ret;
+ }
+ }
+
+ ret = LIBUSB(libusb_set_auto_detach_kernel_driver(device->handle, 1));
+ if (ret != 0) {
+ msg_perr("USB: Failed to enable auto kernel driver detach\n");
+ return ret;
+ }
+
+ ret = LIBUSB(libusb_claim_interface(device->handle,
+ device->
+ interface_descriptor->
+ bInterfaceNumber));
+ if (ret != 0) {
+ msg_perr("USB: Could not claim device interface %d\n",
+ device->interface_descriptor->bInterfaceNumber);
+ return ret;
+ }
+
+ if (device->interface_descriptor->bAlternateSetting != 0) {
+ ret = LIBUSB(libusb_set_interface_alt_setting(
+ device->handle,
+ device->
+ interface_descriptor->
+ bInterfaceNumber,
+ device->
+ interface_descriptor->
+ bAlternateSetting));
+ if (ret != 0) {
+ msg_perr("USB: Failed to set alternate setting %d\n",
+ device->interface_descriptor->bAlternateSetting);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+struct usb_device *usb_device_free(struct usb_device *device)
+{
+ struct usb_device *next = device->next;
+
+ if (device->handle != NULL)
+ libusb_close(device->handle);
+
+ /*
+ * This unref balances the ref added in the add_device function.
+ */
+ libusb_unref_device(device->device);
+ libusb_free_config_descriptor(device->config_descriptor);
+
+ free(device);
+
+ return next;
+}
diff --git a/usb_device.h b/usb_device.h
new file mode 100644
index 0000000..d379675
--- /dev/null
+++ b/usb_device.h
@@ -0,0 +1,164 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2020, Google Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef USB_DEVICE_H
+#define USB_DEVICE_H
+
+/*
+ * USB device matching framework
+ *
+ * This can be used to match a USB device by a number of different parameters.
+ * The parameters can be passed on the command line and defaults can be set
+ * by the programmer.
+ */
+
+#include <libusb.h>
+#include <stdint.h>
+
+/*
+ * The LIBUSB macro converts a libusb failure code into an error code that
+ * flashrom recognizes. It also displays additional libusb specific
+ * information about the failure.
+ */
+#define LIBUSB(expression) \
+ ({ \
+ int libusb_error__ = (expression); \
+ \
+ if (libusb_error__ < 0) { \
+ msg_perr("libusb error: %s:%d %s\n", \
+ __FILE__, \
+ __LINE__, \
+ libusb_error_name(libusb_error__)); \
+ libusb_error__ = 0x20000 | -libusb_error__; \
+ } else { \
+ libusb_error__ = 0; \
+ } \
+ \
+ libusb_error__; \
+ })
+
+/*
+ * A USB match and associated value struct are used to encode the information
+ * about a device against which we wish to match. If the value of a
+ * usb_match_value has been set then a device must match that value. The name
+ * of the usb_match_value is used to fetch the programmer parameter from the
+ * flashrom command line and is the same as the name of the corresponding
+ * field in usb_match.
+ */
+struct usb_match_value {
+ char const *name;
+ int value;
+ int set;
+};
+
+struct usb_match {
+ struct usb_match_value bus;
+ struct usb_match_value address;
+ struct usb_match_value vid;
+ struct usb_match_value pid;
+ struct usb_match_value serial;
+ struct usb_match_value config;
+ struct usb_match_value interface;
+ struct usb_match_value altsetting;
+ struct usb_match_value class;
+ struct usb_match_value subclass;
+ struct usb_match_value protocol;
+};
+
+/*
+ * Initialize a usb_match structure so that each value's name matches the
+ * values name in the usb_match structure (so bus.name == "bus"...), and
+ * look for each value in the flashrom command line via
+ * extract_programmer_param. If the value is found convert it to an integer
+ * using strtol, accepting hex, decimal and octal encoding.
+ */
+void usb_match_init(struct usb_match *match);
+
+/*
+ * Add a default value to a usb_match_value. This must be done after calling
+ * usb_match_init. If usb_match_init already set the value of a usb_match_value
+ * we do nothing, otherwise set the value to default_value. This ensures that
+ * parameters passed on the command line override defaults.
+ */
+void usb_match_value_default(struct usb_match_value *match,
+ long int default_value);
+
+/*
+ * The usb_device structure is an entry in a linked list of devices that were
+ * matched by usb_device_find.
+ */
+struct usb_device {
+ struct libusb_device *device;
+ struct libusb_config_descriptor *config_descriptor;
+ struct libusb_interface_descriptor const *interface_descriptor;
+
+ /*
+ * Initially NULL, the libusb_device_handle is only valid once the
+ * usb_device has been successfully passed to usb_device_show or
+ * usb_device_claim.
+ */
+ struct libusb_device_handle *handle;
+
+ /*
+ * Link to next device, or NULL
+ */
+ struct usb_device *next;
+};
+
+/*
+ * Find and return a list of all compatible devices. Each device is added to
+ * the list with its first valid configuration and interface. If an alternate
+ * configuration (config, interface, altsetting...) is desired the specifics
+ * can be supplied as programmer parameters.
+ *
+ * Return:
+ * 0: At least one matching device was found.
+ * 1: No matching devices were found.
+ */
+int usb_device_find(struct usb_match const *match, struct usb_device **devices);
+
+/*
+ * Display the devices bus and address as well as its product string. The
+ * underlying libusb device is opened if it is not already open.
+ *
+ * Return:
+ * 0: The device information was displayed.
+ * non-zero: There was a failure while displaying the device information.
+ */
+int usb_device_show(char const *prefix, struct usb_device *device);
+
+/*
+ * Open the underlying libusb device, set its config, claim the interface and
+ * select the correct alternate interface.
+ *
+ * Return:
+ * 0: The device was successfully claimed.
+ * non-zero: There was a failure while trying to claim the device.
+ */
+int usb_device_claim(struct usb_device *device);
+
+/*
+ * Free a usb_device structure.
+ *
+ * This ensures that the libusb device is closed and that all allocated
+ * handles and descriptors are freed.
+ *
+ * Return:
+ * The next device in the device list.
+ */
+struct usb_device *usb_device_free(struct usb_device *device);
+
+#endif /* USB_DEVICE_H */
--
To view, visit https://review.coreboot.org/c/flashrom/+/38936
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: Ic928220fc919fe4958c8150e61e11470dac88f13
Gerrit-Change-Number: 38936
Gerrit-PatchSet: 1
Gerrit-Owner: Edward O'Callaghan <quasisec(a)chromium.org>
Gerrit-MessageType: newchange
Edward O'Callaghan has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/38951 )
Change subject: util/flashrom_tester: Upstream E2E testing framework
......................................................................
util/flashrom_tester: Upstream E2E testing framework
The following is a E2E tester for a specific chip/chipset
combo. The tester itself is completely self-contained and
allows the user to specify which tests they wish to preform.
Supported tests include:
- chip-name
- read
- write
- erase
- wp-locking
Change-Id: Ic2905a76cad90b1546b9328d668bf8abbf8aed44
Signed-off-by: Edward O'Callaghan <quasisec(a)google.com>
---
A util/flashrom_tester/.gitignore
A util/flashrom_tester/Cargo.toml
A util/flashrom_tester/build.rs
A util/flashrom_tester/flashrom/Cargo.toml
A util/flashrom_tester/flashrom/src/cmd.rs
A util/flashrom_tester/flashrom/src/lib.rs
A util/flashrom_tester/flashrom_remote.sh
A util/flashrom_tester/src/cros_sysinfo.rs
A util/flashrom_tester/src/lib.rs
A util/flashrom_tester/src/logger.rs
A util/flashrom_tester/src/main.rs
A util/flashrom_tester/src/rand_util.rs
A util/flashrom_tester/src/tester.rs
A util/flashrom_tester/src/tests.rs
A util/flashrom_tester/src/types.rs
A util/flashrom_tester/src/utils.rs
16 files changed, 2,680 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/51/38951/1
diff --git a/util/flashrom_tester/.gitignore b/util/flashrom_tester/.gitignore
new file mode 100644
index 0000000..1e7caa9
--- /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 0000000..50f2c4a
--- /dev/null
+++ b/util/flashrom_tester/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "flashrom_tester"
+version = "1.6.0"
+authors = ["Edward O'Callaghan <quasisec(a)chromium.org>",
+ "Peter Marheine <pmarheine(a)chromium.org>"]
+edition = "2018"
+build = "build.rs"
+
+[lib]
+name = "flashrom_tester"
+
+[[bin]]
+name = "flashrom_tester"
+required-features = ["cli"]
+
+[dependencies]
+built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] }
+chrono = { version = "0.4", optional = true }
+clap = { version = "2.33", default-features = false, optional = true }
+flashrom = { path = "flashrom/" }
+log = { version = "0.4", features = ["std"] }
+rand = "0.6.4"
+serde_json = "1"
+sys-info = "0.5.7"
+
+[build-dependencies]
+built = { version = "0.3", default-features = false, features = ["serialized_time", "serialized_version"] }
+
+[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 0000000..3800c17
--- /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 0000000..27216cb
--- /dev/null
+++ b/util/flashrom_tester/flashrom/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "flashrom"
+version = "1.0.0"
+authors = ["Edward O'Callaghan <quasisec(a)chromium.org>",
+ "Peter Marheine <pmarheine(a)chromium.org>"]
+edition = "2018"
+
+[dependencies]
+log = "0.4"
\ No newline at end of file
diff --git a/util/flashrom_tester/flashrom/src/cmd.rs b/util/flashrom_tester/flashrom/src/cmd.rs
new file mode 100644
index 0000000..3fd2ac0
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/cmd.rs
@@ -0,0 +1,355 @@
+//
+// 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, FlashromOpt};
+
+use std::process::Command;
+
+#[derive(PartialEq, 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 => return Err("Found no purely-numeric lines in flashrom output".into()),
+ Some(Err(e)) => {
+ return Err(format!(
+ "Failed to parse flashrom size output as integer: {}",
+ e
+ ))
+ }
+ Some(Ok(sz)) => Ok(sz),
+ }
+}
+
+impl crate::Flashrom for FlashromCmd {
+ fn get_size(&self) -> Result<i64, FlashromError> {
+ let (stdout, _) = flashrom_dispatch(self.path.as_str(), &["--flash-size"], self.fc)?;
+ let sz = String::from_utf8_lossy(&stdout);
+
+ flashrom_extract_size(&sz)
+ }
+
+ fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ let params = flashrom_decode_opts(fropt);
+ flashrom_dispatch(self.path.as_str(), ¶ms, self.fc)
+ }
+}
+
+fn flashrom_decode_opts(opts: FlashromOpt) -> Vec<String> {
+ let mut params = Vec::<String>::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".to_string());
+ params.push(hex_string(x0));
+ params.push(hex_string(x1));
+ }
+ if opts.wp_opt.status {
+ params.push("--wp-status".to_string());
+ } else if opts.wp_opt.list {
+ params.push("--wp-list".to_string());
+ } else if opts.wp_opt.enable {
+ params.push("--wp-enable".to_string());
+ } else if opts.wp_opt.disable {
+ params.push("--wp-disable".to_string());
+ }
+
+ // io_opt
+ if opts.io_opt.read.is_some() {
+ params.push("-r".to_string());
+ params.push(opts.io_opt.read.unwrap().to_string());
+ } else if opts.io_opt.write.is_some() {
+ params.push("-w".to_string());
+ params.push(opts.io_opt.write.unwrap().to_string());
+ } else if opts.io_opt.verify.is_some() {
+ params.push("-v".to_string());
+ params.push(opts.io_opt.verify.unwrap().to_string());
+ } else if opts.io_opt.erase {
+ params.push("-E".to_string());
+ }
+
+ // misc_opt
+ if opts.layout.is_some() {
+ params.push("-l".to_string());
+ params.push(opts.layout.unwrap().to_string());
+ }
+ if opts.image.is_some() {
+ params.push("-i".to_string());
+ params.push(opts.image.unwrap().to_string());
+ }
+
+ if opts.flash_name {
+ params.push("--flash-name".to_string());
+ }
+ if opts.ignore_fmap {
+ params.push("--ignore-fmap".to_string());
+ }
+ if opts.verbose {
+ params.push("-V".to_string());
+ }
+
+ params
+}
+
+fn flashrom_dispatch<S: AsRef<str>>(
+ path: &str,
+ params: &[S],
+ fc: FlashChip,
+) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ // from man page:
+ // ' -p, --programmer <name>[:parameter[,parameter[,parameter]]] '
+ let mut args: Vec<&str> = vec!["-p", 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)),
+ };
+ if !output.status.success() {
+ // There is two cases on failure;
+ // i. ) A bad exit code,
+ // ii.) A SIG killed us.
+ match output.status.code() {
+ Some(code) => {
+ return Err(format!(
+ "{}\nExited with error code: {}",
+ String::from_utf8_lossy(&output.stderr),
+ code
+ ));
+ }
+ None => return Err("Process terminated by a signal".into()),
+ }
+ }
+
+ Ok((output.stdout, output.stderr))
+}
+
+pub fn dut_ctrl_toggle_wp(en: bool) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ let args = if en {
+ ["fw_wp_en:off", "fw_wp:on"]
+ } else {
+ ["fw_wp_en:on", "fw_wp:off"]
+ };
+ dut_ctrl(&args)
+}
+
+pub fn dut_ctrl_servo_type() -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ let args = ["servo_type"];
+ dut_ctrl(&args)
+}
+
+fn dut_ctrl(args: &[&str]) -> Result<(Vec<u8>, Vec<u8>), FlashromError> {
+ let output = match Command::new("dut-control").args(args).output() {
+ Ok(x) => x,
+ Err(e) => return Err(format!("Failed to run dut-control: {}", e)),
+ };
+ if !output.status.success() {
+ // There is two cases on failure;
+ // i. ) A bad exit code,
+ // ii.) A SIG killed us.
+ match output.status.code() {
+ Some(code) => {
+ return Err(format!("Exited with error code: {}", code).into());
+ }
+ None => return Err("Process terminated by a signal".into()),
+ }
+ }
+
+ Ok((output.stdout, output.stderr))
+}
+
+fn hex_string(v: i64) -> String {
+ format!("{:#08X}", v).to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::flashrom_decode_opts;
+ use crate::{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: opts,
+ ..Default::default()
+ }),
+ expected
+ );
+ }
+
+ test_io_opt(
+ IOOpt {
+ read: Some("foo.bin"),
+ ..Default::default()
+ },
+ &["-r", "foo.bin"],
+ );
+ test_io_opt(
+ IOOpt {
+ write: Some("bar.bin"),
+ ..Default::default()
+ },
+ &["-w", "bar.bin"],
+ );
+ test_io_opt(
+ IOOpt {
+ verify: Some("/tmp/baz.bin"),
+ ..Default::default()
+ },
+ &["-v", "/tmp/baz.bin"],
+ );
+ test_io_opt(
+ IOOpt {
+ erase: true,
+ ..Default::default()
+ },
+ &["-E"],
+ );
+ }
+
+ #[test]
+ fn decode_misc() {
+ //use Default::default;
+ assert_eq!(
+ flashrom_decode_opts(FlashromOpt {
+ layout: Some("TestLayout"),
+ ..Default::default()
+ }),
+ &["-l", "TestLayout"]
+ );
+
+ assert_eq!(
+ flashrom_decode_opts(FlashromOpt {
+ image: Some("TestImage"),
+ ..Default::default()
+ }),
+ &["-i", "TestImage"]
+ );
+
+ assert_eq!(
+ flashrom_decode_opts(FlashromOpt {
+ flash_name: true,
+ ignore_fmap: true,
+ verbose: true,
+ ..Default::default()
+ }),
+ &["--flash-name", "--ignore-fmap", "-V"]
+ );
+ }
+
+ #[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())
+ );
+ }
+}
diff --git a/util/flashrom_tester/flashrom/src/lib.rs b/util/flashrom_tester/flashrom/src/lib.rs
new file mode 100644
index 0000000..734e3ff
--- /dev/null
+++ b/util/flashrom_tester/flashrom/src/lib.rs
@@ -0,0 +1,381 @@
+//
+// 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;
+
+pub use cmd::{dut_ctrl_toggle_wp, FlashromCmd};
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum FlashChip {
+ EC,
+ HOST,
+ SERVO,
+ DEDIPROG,
+}
+
+impl FlashChip {
+ pub fn from(s: &str) -> Result<FlashChip, &str> {
+ let r = match s {
+ "ec" => Ok(FlashChip::EC),
+ "host" => Ok(FlashChip::HOST),
+ "servo" => Ok(FlashChip::SERVO),
+ "dediprog" => Ok(FlashChip::DEDIPROG),
+ _ => Err("cannot convert str to enum"),
+ };
+ return r;
+ }
+ pub fn to(fc: FlashChip) -> &'static str {
+ let r = match fc {
+ FlashChip::EC => "ec",
+ FlashChip::HOST => "host",
+ FlashChip::SERVO => "ft2231_spi:type=servo-v2",
+ FlashChip::DEDIPROG => "dediprog",
+ };
+ return r;
+ }
+
+ /// 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 | FlashChip::EC => true,
+ FlashChip::SERVO | FlashChip::DEDIPROG => false,
+ }
+ }
+}
+
+pub type FlashromError = String;
+
+#[derive(Default)]
+pub struct FlashromOpt<'a> {
+ pub wp_opt: WPOpt,
+ pub io_opt: IOOpt<'a>,
+
+ pub layout: Option<&'a str>, // -l <file>
+ pub image: Option<&'a str>, // -i <name>
+
+ pub flash_name: bool, // --flash-name
+ pub ignore_fmap: bool, // --ignore-fmap
+ pub verbose: bool, // -V
+}
+
+#[derive(Default)]
+pub struct WPOpt {
+ pub range: Option<(i64, i64)>, // --wp-range x0 x1
+ pub status: bool, // --wp-status
+ pub list: bool, // --wp-list
+ pub enable: bool, // --wp-enable
+ pub disable: bool, // --wp-disable
+}
+
+#[derive(Default)]
+pub struct IOOpt<'a> {
+ pub read: Option<&'a str>, // -r <file>
+ pub write: Option<&'a str>, // -w <file>
+ pub verify: Option<&'a str>, // -v <file>
+ pub erase: bool, // -E
+}
+
+pub trait Flashrom {
+ fn get_size(&self) -> Result<i64, FlashromError>;
+ fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec<u8>, Vec<u8>), FlashromError>;
+}
+
+pub fn name(cmd: &cmd::FlashromCmd) -> Result<(String, String), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: IOOpt {
+ ..Default::default()
+ },
+
+ flash_name: true,
+
+ ..Default::default()
+ };
+
+ let (stdout, stderr) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ let eoutput = String::from_utf8_lossy(stderr.as_slice());
+ debug!("name()'stdout: {:#?}.", output);
+ debug!("name()'stderr: {:#?}.", eoutput);
+
+ match extract_flash_name(&output) {
+ None => Err("Didn't find chip vendor/name in flashrom output".into()),
+ Some((vendor, name)) => Ok((vendor.into(), name.into())),
+ }
+}
+
+/// Get a flash vendor and name from the first matching line of flashrom output.
+///
+/// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name.
+/// This is usually the last line of output.
+fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> {
+ for line in stdout.lines() {
+ if !line.starts_with("vendor=\"") {
+ continue;
+ }
+
+ let tail = line.trim_start_matches("vendor=\"");
+ let mut split = tail.splitn(2, "\" name=\"");
+ let vendor = split.next();
+ let name = split.next().map(|s| s.trim_end_matches('"'));
+
+ match (vendor, name) {
+ (Some(v), Some(n)) => return Some((v, n)),
+ _ => continue,
+ }
+ }
+ None
+}
+
+pub struct ROMWriteSpecifics<'a> {
+ pub layout_file: Option<&'a str>,
+ pub write_file: Option<&'a str>,
+ pub name_file: Option<&'a str>,
+}
+
+pub fn write_file_with_layout(
+ cmd: &cmd::FlashromCmd,
+ rws: &ROMWriteSpecifics,
+) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: IOOpt {
+ write: rws.write_file,
+ ..Default::default()
+ },
+
+ layout: rws.layout_file,
+ image: rws.name_file,
+
+ ignore_fmap: true,
+
+ ..Default::default()
+ };
+
+ let (stdout, stderr) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ let eoutput = String::from_utf8_lossy(stderr.as_slice());
+ debug!("write_file_with_layout()'stdout:\n{}.", output);
+ debug!("write_file_with_layout()'stderr:\n{}.", eoutput);
+ Ok(true)
+}
+
+pub fn wp_range(
+ cmd: &cmd::FlashromCmd,
+ range: (i64, i64),
+ wp_enable: bool,
+) -> Result<bool, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ range: Some(range),
+ enable: wp_enable,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, stderr) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ let eoutput = String::from_utf8_lossy(stderr.as_slice());
+ debug!("wp_range()'stdout:\n{}.", output);
+ debug!("wp_range()'stderr:\n{}.", eoutput);
+ Ok(true)
+}
+
+pub fn wp_list(cmd: &cmd::FlashromCmd) -> Result<String, FlashromError> {
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ list: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ if output.len() == 0 {
+ return Err(
+ "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list".into(),
+ );
+ }
+ Ok(output.to_string())
+}
+
+pub fn wp_status(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> {
+ let status = if en { "en" } else { "dis" };
+ info!("See if chip write protect is {}abled", status);
+
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ status: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+
+ debug!("wp_status():\n{}", output);
+
+ let s = std::format!("write protect is {}abled", status);
+ Ok(output.contains(&s))
+}
+
+pub fn wp_toggle(cmd: &cmd::FlashromCmd, en: bool) -> Result<bool, FlashromError> {
+ let status = if en { "en" } else { "dis" };
+
+ // For MTD, --wp-range and --wp-enable must be used simultaneously.
+ let range = if en {
+ let rom_sz: i64 = cmd.get_size()?;
+ Some((0, rom_sz)) // (start, len)
+ } else {
+ None
+ };
+
+ let opts = FlashromOpt {
+ wp_opt: WPOpt {
+ range: range,
+ enable: en,
+ disable: !en,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, stderr) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ let eoutput = String::from_utf8_lossy(stderr.as_slice());
+
+ debug!("wp_toggle()'stdout:\n{}.", output);
+ debug!("wp_toggle()'stderr:\n{}.", eoutput);
+
+ match wp_status(&cmd, true) {
+ Ok(_ret) => {
+ info!("Successfully {}abled write-protect", status);
+ Ok(true)
+ }
+ Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e)),
+ }
+}
+
+pub fn read(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: IOOpt {
+ read: Some(path),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ debug!("read():\n{}", output);
+ Ok(())
+}
+
+pub fn write(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: IOOpt {
+ write: Some(path),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ debug!("write():\n{}", output);
+ Ok(())
+}
+
+pub fn verify(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: IOOpt {
+ verify: Some(path),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ debug!("verify():\n{}", output);
+ Ok(())
+}
+
+pub fn erase(cmd: &cmd::FlashromCmd) -> Result<(), FlashromError> {
+ let opts = FlashromOpt {
+ io_opt: IOOpt {
+ erase: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ let (stdout, _) = cmd.dispatch(opts)?;
+ let output = String::from_utf8_lossy(stdout.as_slice());
+ debug!("erase():\n{}", output);
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ #[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_remote.sh b/util/flashrom_tester/flashrom_remote.sh
new file mode 100755
index 0000000..8dba943
--- /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 0000000..ddb0802
--- /dev/null
+++ b/util/flashrom_tester/src/cros_sysinfo.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::ffi::OsStr;
+use std::fmt::Debug;
+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 eventlog_list() -> Result<String, std::io::Error> {
+ mosys_dispatch(&["eventlog", "list"])
+}
+
+fn mosys_dispatch<S: AsRef<OsStr> + Debug>(args: &[S]) -> IoResult<String> {
+ info!("mosys_dispatch() running: /usr/sbin/mosys {:?}", args);
+
+ let output = Command::new("/usr/sbin/mosys")
+ .args(args)
+ .stdin(Stdio::null())
+ .output()?;
+ if !output.status.success() {
+ return Err(utils::translate_command_error(&output));
+ }
+
+ let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
+ Ok(stdout)
+}
diff --git a/util/flashrom_tester/src/lib.rs b/util/flashrom_tester/src/lib.rs
new file mode 100644
index 0000000..d8f1cb6
--- /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 0000000..e1c00f5
--- /dev/null
+++ b/util/flashrom_tester/src/logger.rs
@@ -0,0 +1,172 @@
+//
+// 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;
+use std::path::PathBuf;
+use std::sync::Mutex;
+
+struct Logger<W: Write + Send> {
+ level: log::LevelFilter,
+ target: LogTarget<W>,
+}
+
+enum LogTarget<W>
+where
+ W: Write,
+{
+ Terminal,
+ Write(Mutex<W>),
+}
+
+impl<W: Write + Send> log::Log for Logger<W> {
+ fn enabled(&self, metadata: &log::Metadata) -> bool {
+ metadata.level() <= self.level
+ }
+
+ fn log(&self, record: &log::Record) {
+ fn log_internal<W: Write>(mut w: W, record: &log::Record) -> std::io::Result<()> {
+ let now = chrono::Local::now();
+ write!(w, "{}{} ", types::MAGENTA, now.format("%Y-%m-%dT%H:%M:%S"))?;
+ write!(
+ w,
+ "{}[ {} ]{} ",
+ types::YELLOW,
+ record.level(),
+ types::RESET
+ )?;
+ writeln!(w, "{}", record.args())
+ }
+
+ // Write errors deliberately ignored
+ let _ = match self.target {
+ LogTarget::Terminal => {
+ let stdout = std::io::stdout();
+ let mut lock = stdout.lock();
+ log_internal(&mut lock, record)
+ }
+ LogTarget::Write(ref mutex) => {
+ let mut lock = mutex.lock().unwrap();
+ log_internal(&mut *lock, record)
+ }
+ };
+ }
+
+ fn flush(&self) {
+ // Flush errors deliberately ignored
+ let _ = match self.target {
+ LogTarget::Terminal => std::io::stdout().flush(),
+ LogTarget::Write(ref w) => w.lock().unwrap().flush(),
+ };
+ }
+}
+
+pub fn init(to_file: Option<PathBuf>, debug: bool) {
+ let mut logger = Logger {
+ level: log::LevelFilter::Info,
+ target: LogTarget::Terminal,
+ };
+
+ if debug {
+ logger.level = log::LevelFilter::Debug;
+ }
+ if let Some(path) = to_file {
+ logger.target = LogTarget::Write(Mutex::new(
+ std::fs::File::create(path).expect("Unable to open log file for writing"),
+ ));
+ }
+
+ log::set_max_level(logger.level);
+ log::set_boxed_logger(Box::new(logger)).unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{LogTarget, Logger};
+ use log::{Level, LevelFilter, Log, Record};
+ use std::sync::Mutex;
+
+ fn run_records(records: &[Record]) -> String {
+ let mut buf = Vec::<u8>::new();
+ {
+ let lock = Mutex::new(&mut buf);
+ let logger = Logger {
+ level: LevelFilter::Info,
+ target: LogTarget::Write(lock),
+ };
+
+ for record in records {
+ if logger.enabled(record.metadata()) {
+ logger.log(&record);
+ }
+ }
+ }
+ String::from_utf8(buf).unwrap()
+ }
+
+ /// 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
+ assert_eq!(
+ &buf[24..],
+ " \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 0000000..1cc525e
--- /dev/null
+++ b/util/flashrom_tester/src/main.rs
@@ -0,0 +1,143 @@
+//
+// 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;
+use flashrom_tester::{tester, tests};
+use std::path::PathBuf;
+
+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("flashrom_binary").required(true))
+ .arg(
+ Arg::with_name("ccd_target_type")
+ .required(true)
+ .possible_values(&["host", "ec", "servo"]),
+ )
+ .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-file")
+ .short("o")
+ .long("log-file")
+ .takes_value(true)
+ .help("Write logs to a file rather than stdout"),
+ )
+ .arg(
+ Arg::with_name("log_debug")
+ .short("d")
+ .long("debug")
+ .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.value_of_os("log-file").map(PathBuf::from),
+ matches.is_present("log_debug"),
+ );
+ debug!("Args parsed and logging initialized OK");
+
+ let flashrom_path = matches
+ .value_of("flashrom_binary")
+ .expect("flashrom_binary should be required");
+ 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 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(
+ flashrom_path,
+ ccd_type,
+ print_layout,
+ output_format,
+ test_names,
+ ) {
+ eprintln!("Failed to run tests: {:?}", e);
+ std::process::exit(1);
+ }
+}
diff --git a/util/flashrom_tester/src/rand_util.rs b/util/flashrom_tester/src/rand_util.rs
new file mode 100644
index 0000000..51411d0
--- /dev/null
+++ b/util/flashrom_tester/src/rand_util.rs
@@ -0,0 +1,81 @@
+//
+// 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 rand::prelude::*;
+
+pub fn gen_rand_testdata(path: &str, size: usize) -> std::io::Result<()> {
+ let mut buf = BufWriter::new(File::create(path)?);
+
+ let mut a: Vec<u8> = Vec::with_capacity(size);
+ // Pad out array to be filled in by Rng::fill().
+ a.resize(size, 0b0);
+ 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 = "/tmp/idk_test00";
+ let path1 = "/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 0000000..fbef201
--- /dev/null
+++ b/util/flashrom_tester/src/tester.rs
@@ -0,0 +1,636 @@
+//
+// 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::{FlashChip, Flashrom, FlashromCmd};
+use serde_json::json;
+use std::mem::MaybeUninit;
+use std::sync::Mutex;
+
+// 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 FlashromCmd,
+ layout: LayoutSizes,
+
+ pub wp: WriteProtectState<'a, 'static>,
+ /// The path to a file containing the flash contents at test start.
+ // TODO(pmarheine) migrate this to a PathBuf for clarity
+ original_flash_contents: String,
+ /// The path to a file containing flash-sized random data
+ // TODO(pmarheine) make this a PathBuf too
+ random_data: String,
+}
+
+impl<'a> TestEnv<'a> {
+ pub fn create(chip_type: FlashChip, cmd: &'a FlashromCmd) -> Result<Self, String> {
+ let rom_sz = cmd.get_size()?;
+ let out = TestEnv {
+ chip_type: chip_type,
+ cmd: cmd,
+ layout: utils::get_layout_sizes(rom_sz)?,
+ wp: WriteProtectState::from_hardware(cmd)?,
+ original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(),
+ random_data: "/tmp/random_content.bin".into(),
+ };
+
+ info!("Stashing golden image for verification/recovery on completion");
+ flashrom::read(&out.cmd, &out.original_flash_contents)?;
+ flashrom::verify(&out.cmd, &out.original_flash_contents)?;
+
+ 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 use_dut_control = self.chip_type == FlashChip::SERVO;
+ if use_dut_control && flashrom::dut_ctrl_toggle_wp(false).is_err() {
+ error!("failed to dispatch dut_ctrl_toggle_wp()!");
+ }
+
+ let name = test.get_name();
+ info!("Beginning test: {}", name);
+ let out = test.run(self);
+ info!("Completed test: {}; result {:?}", name, out);
+
+ if use_dut_control && flashrom::dut_ctrl_toggle_wp(true).is_err() {
+ error!("failed to dispatch dut_ctrl_toggle_wp()!");
+ }
+ out
+ }
+
+ 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) -> &str {
+ &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 {
+ flashrom::verify(&self.cmd, &self.original_flash_contents).is_ok()
+ }
+
+ /// Do whatever is necessary to make the current Flash contents the same as they
+ /// were at the start of testing.
+ pub fn ensure_golden(&mut self) -> Result<(), String> {
+ self.wp.set_hw(false)?.set_sw(false)?;
+ flashrom::write(&self.cmd, &self.original_flash_contents)
+ }
+
+ /// Attempt to erase the flash.
+ pub fn erase(&self) -> Result<(), String> {
+ flashrom::erase(self.cmd)
+ }
+
+ /// Verify that the current Flash contents are the same as the file at the given
+ /// path.
+ ///
+ /// Returns Err if they are not the same.
+ pub fn verify(&self, contents_path: &str) -> Result<(), String> {
+ flashrom::verify(self.cmd, contents_path)
+ }
+}
+
+impl Drop for TestEnv<'_> {
+ 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);
+ }
+ }
+ }
+}
+
+/// RAII handle for setting write protect in either hardware or software.
+///
+/// Given an instance, the state of either write protect can be modified by calling
+/// `set` or `push`. When it goes out of scope, the write protects will be returned
+/// to the state they had then it was created.
+///
+/// The lifetime `'p` on this struct is the parent state it derives from; `'static`
+/// implies it is derived from hardware, while anything else is part of a stack
+/// created by `push`ing states. An initial state is always static, and the stack
+/// forms a lifetime chain `'static -> 'p -> 'p1 -> ... -> 'pn`.
+pub struct WriteProtectState<'a, 'p> {
+ /// The parent state this derives from.
+ ///
+ /// If it's a root (gotten via `from_hardware`), then this is Hardware and the
+ /// liveness flag will be reset on drop.
+ initial: InitialState<'p>,
+ // Tuples are (hardware, software)
+ current: (bool, bool),
+ cmd: &'a FlashromCmd,
+}
+
+enum InitialState<'p> {
+ Hardware(bool, bool),
+ Previous(&'p WriteProtectState<'p, 'p>),
+}
+
+impl InitialState<'_> {
+ fn get_target(&self) -> (bool, bool) {
+ match self {
+ InitialState::Hardware(hw, sw) => (*hw, *sw),
+ InitialState::Previous(s) => s.current,
+ }
+ }
+}
+
+impl<'a> WriteProtectState<'a, 'static> {
+ /// Initialize a state from the current state of the hardware.
+ ///
+ /// Panics if there is already a live state derived from hardware. In such a situation the
+ /// new state must be derived from the live one, or the live one must be dropped first.
+ pub fn from_hardware(cmd: &'a FlashromCmd) -> Result<Self, String> {
+ let mut lock = Self::get_liveness_lock()
+ .lock()
+ .expect("Somebody panicked during WriteProtectState init from hardware");
+ if *lock {
+ drop(lock); // Don't poison the lock
+ panic!("Attempted to create a new WriteProtectState when one is already live");
+ }
+
+ let hw = Self::get_hw(cmd)?;
+ let sw = Self::get_sw(cmd)?;
+ info!("Initial hardware write protect: HW={} SW={}", hw, sw);
+
+ *lock = true;
+ Ok(WriteProtectState {
+ initial: InitialState::Hardware(hw, sw),
+ current: (hw, sw),
+ cmd,
+ })
+ }
+
+ /// Get the actual hardware write protect state.
+ fn get_hw(cmd: &FlashromCmd) -> Result<bool, String> {
+ if cmd.fc.can_control_hw_wp() {
+ super::utils::get_hardware_wp()
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// Get the actual software write protect state.
+ fn get_sw(cmd: &FlashromCmd) -> Result<bool, String> {
+ flashrom::wp_status(cmd, true)
+ }
+}
+
+impl<'a, 'p> WriteProtectState<'a, 'p> {
+ /// Return true if the current programmer supports setting the hardware
+ /// write protect.
+ ///
+ /// If false, calls to set_hw() will do nothing.
+ pub fn can_control_hw_wp(&self) -> bool {
+ self.cmd.fc.can_control_hw_wp()
+ }
+
+ /// Set the software write protect.
+ pub fn set_sw(&mut self, enable: bool) -> Result<&mut Self, String> {
+ info!("request={}, current={}", enable, self.current.1);
+ if self.current.1 != enable {
+ flashrom::wp_toggle(self.cmd, /* en= */ enable)?;
+ self.current.1 = enable;
+ }
+ Ok(self)
+ }
+
+ /// Set the hardware write protect.
+ pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> {
+ if self.current.0 != enable {
+ if self.can_control_hw_wp() {
+ super::utils::toggle_hw_wp(/* dis= */ !enable)?;
+ self.current.0 = enable;
+ } else if enable {
+ info!(
+ "Ignoring attempt to enable hardware WP with {:?} programmer",
+ self.cmd.fc
+ );
+ }
+ }
+ Ok(self)
+ }
+
+ /// Stack a new write protect state on top of the current one.
+ ///
+ /// This is useful if you need to temporarily make a change to write protection:
+ ///
+ /// ```no_run
+ /// # fn main() -> Result<(), String> {
+ /// # let cmd: flashrom::FlashromCmd = unimplemented!();
+ /// let wp = flashrom_tester::tester::WriteProtectState::from_hardware(&cmd)?;
+ /// {
+ /// let mut wp = wp.push();
+ /// wp.set_sw(false)?;
+ /// // Do something with software write protect disabled
+ /// }
+ /// // Now software write protect returns to its original state, even if
+ /// // set_sw() failed.
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// This returns a new state which restores the original when it is dropped- the new state
+ /// refers to the old, so the compiler enforces that states are disposed of in the reverse
+ /// order of their creation and correctly restore the original state.
+ pub fn push<'p1>(&'p1 self) -> WriteProtectState<'a, 'p1> {
+ WriteProtectState {
+ initial: InitialState::Previous(self),
+ current: self.current,
+ cmd: self.cmd,
+ }
+ }
+
+ fn get_liveness_lock() -> &'static Mutex<bool> {
+ static INIT: std::sync::Once = std::sync::Once::new();
+ /// Value becomes true when there is a live WriteProtectState derived `from_hardware`,
+ /// blocking duplicate initialization.
+ ///
+ /// This is required because hardware access is not synchronized; it's possible to leave the
+ /// hardware in an unintended state by creating a state handle from it, modifying the state,
+ /// creating another handle from the hardware then dropping the first handle- then on drop
+ /// of the second handle it will restore the state to the modified one rather than the initial.
+ ///
+ /// This flag ensures that a duplicate root state cannot be created.
+ ///
+ /// This is a Mutex<bool> rather than AtomicBool because acquiring the flag needs to perform
+ /// several operations that may themselves fail- acquisitions must be fully synchronized.
+ static mut LIVE_FROM_HARDWARE: MaybeUninit<Mutex<bool>> = MaybeUninit::uninit();
+
+ unsafe {
+ INIT.call_once(|| {
+ LIVE_FROM_HARDWARE.as_mut_ptr().write(Mutex::new(false));
+ });
+ &*LIVE_FROM_HARDWARE.as_ptr()
+ }
+ }
+
+ /// 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> {
+ unsafe {
+ let out = self.drop_internal();
+ // We just ran drop, don't do it again
+ std::mem::forget(self);
+ out
+ }
+ }
+
+ /// Internal Drop impl.
+ ///
+ /// This is unsafe because it effectively consumes self when clearing the
+ /// liveness lock. Callers must be able to guarantee that self will be forgotten
+ /// if the state was constructed from hardware in order to uphold the liveness
+ /// invariant (that only a single state constructed from hardware exists at any
+ /// time).
+ unsafe fn drop_internal(&mut self) -> Result<(), String> {
+ let lock = match self.initial {
+ InitialState::Hardware(_, _) => Some(
+ Self::get_liveness_lock()
+ .lock()
+ .expect("Somebody panicked during WriteProtectState drop from hardware"),
+ ),
+ _ => None,
+ };
+ let (hw, sw) = self.initial.get_target();
+
+ fn enable_str(enable: bool) -> &'static str {
+ if enable {
+ "en"
+ } else {
+ "dis"
+ }
+ }
+
+ // Toggle both protects back to their initial states.
+ // Software first because we can't change it once hardware is enabled.
+ if sw != self.current.1 {
+ // Is the hw wp currently enabled?
+ if self.current.0 {
+ super::utils::toggle_hw_wp(/* dis= */ true).map_err(|e| {
+ format!(
+ "Failed to {}able hardware write protect: {}",
+ enable_str(false),
+ e
+ )
+ })?;
+ }
+ flashrom::wp_toggle(self.cmd, /* en= */ sw).map_err(|e| {
+ format!(
+ "Failed to {}able software write protect: {}",
+ enable_str(sw),
+ e
+ )
+ })?;
+ }
+
+ assert!(
+ self.cmd.fc.can_control_hw_wp() || (!self.current.0 && !hw),
+ "HW WP must be disabled if it cannot be controlled"
+ );
+ if hw != self.current.0 {
+ super::utils::toggle_hw_wp(/* dis= */ !hw).map_err(|e| {
+ format!(
+ "Failed to {}able hardware write protect: {}",
+ enable_str(hw),
+ e
+ )
+ })?;
+ }
+
+ if let Some(mut lock) = lock {
+ // Initial state was constructed via from_hardware, now we can clear the liveness
+ // lock since reset is complete.
+ *lock = false;
+ }
+ Ok(())
+ }
+}
+
+impl<'a, 'p> Drop for WriteProtectState<'a, 'p> {
+ /// Sets both write protects to the state they had when this state was created.
+ ///
+ /// Panics on error because there is no mechanism to report errors in Drop.
+ fn drop(&mut self) {
+ unsafe { 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, Debug)]
+pub enum TestConclusion {
+ Pass,
+ Fail,
+ UnexpectedPass,
+ UnexpectedFail,
+}
+
+pub struct ReportMetaData {
+ pub chip_name: String,
+ pub os_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),
+ }
+}
+
+pub fn run_all_tests<T, TS>(
+ chip: FlashChip,
+ cmd: &FlashromCmd,
+ ts: TS,
+) -> Vec<(String, (TestConclusion, Option<TestError>))>
+where
+ T: TestCase + Copy,
+ TS: IntoIterator<Item = T>,
+{
+ let mut env = TestEnv::create(chip, cmd).expect("Failed to set up test environment");
+
+ let mut results = Vec::new();
+ for t in ts {
+ let result = decode_test_result(env.run_test(t), t.expected_result());
+ results.push((t.get_name().into(), result));
+ }
+ results
+}
+
+#[derive(Debug, PartialEq, 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 => {
+ println!();
+ println!(" =============================");
+ println!(" ===== AVL qual RESULTS ====");
+ println!(" =============================");
+ println!();
+ println!(" %---------------------------%");
+ println!(" os release: {}", meta_data.os_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), types::BOLD),
+ style_dbg!(result, types::RED)
+ );
+ match error {
+ None => {}
+ Some(e) => info!(" - {} failure details:\n{}", name, e.to_string()),
+ };
+ } else {
+ println!(
+ " {} {}",
+ style!(format!(" <+> {} test:", name), types::BOLD),
+ style_dbg!(result, types::GREEN)
+ );
+ }
+ }
+ 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 0000000..dd75689
--- /dev/null
+++ b/util/flashrom_tester/src/tests.rs
@@ -0,0 +1,385 @@
+//
+// 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, FlashromCmd};
+use std::collections::{HashMap, HashSet};
+use std::fs::File;
+use std::io::{BufRead, Write};
+
+const LAYOUT_FILE: &'static str = "/tmp/layout.file";
+
+/// 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.
+pub fn generic<'a, TN: Iterator<Item = &'a str>>(
+ path: &str,
+ fc: FlashChip,
+ print_layout: bool,
+ output_format: OutputFormat,
+ test_names: Option<TN>,
+) -> Result<(), Box<dyn std::error::Error>> {
+ let p = path.to_string();
+ let cmd = FlashromCmd { path: p, fc };
+
+ utils::ac_power_warning();
+
+ info!("Calculate ROM partition sizes & Create the layout file.");
+ let rom_sz: i64 = cmd.get_size()?;
+ let layout_sizes = utils::get_layout_sizes(rom_sz)?;
+ {
+ let mut f = File::create(LAYOUT_FILE)?;
+ let mut buf: Vec<u8> = vec![];
+ utils::construct_layout_file(&mut buf, &layout_sizes)?;
+
+ f.write_all(&buf)?;
+ if print_layout {
+ info!(
+ "Dumping layout file as requested:\n{}",
+ String::from_utf8_lossy(&buf)
+ );
+ }
+ }
+
+ info!(
+ "Record crossystem information.\n{}",
+ utils::collect_crosssystem()?
+ );
+
+ // Register tests to run:
+ let tests: &[&dyn TestCase] = &[
+ &("Get_device_name", get_device_name_test),
+ &("Coreboot_ELOG_sanity", elog_sanity_test),
+ &("Host_is_ChromeOS", host_is_chrome_test),
+ &("Toggle_WP", wp_toggle_test),
+ &("Erase_and_Write", erase_write_test),
+ &("Fail_to_verify", verify_fail_test),
+ &("Lock", lock_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>> = if let Some(names) = test_names {
+ Some(names.map(|s| s.to_lowercase()).collect())
+ } else {
+ None
+ };
+ let tests = filter_tests(tests, &mut filter_names);
+
+ // ------------------------.
+ // Run all the tests and collate the findings:
+ let results = tester::run_all_tests(fc, &cmd, tests);
+
+ // Any leftover filtered names were specified to be run but don't exist
+ for leftover in filter_names.iter().flatten() {
+ warn!("No test matches filter name \"{}\"", leftover);
+ }
+
+ let chip_name = flashrom::name(&cmd)
+ .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1))
+ .unwrap_or("<Unknown chip>".into());
+ let os_rel = sys_info::os_release().unwrap_or("<Unknown OS>".to_string());
+ let system_info = cros_sysinfo::system_info().unwrap_or("<Unknown System>".to_string());
+ let bios_info = cros_sysinfo::bios_info().unwrap_or("<Unknown BIOS>".to_string());
+
+ let meta_data = tester::ReportMetaData {
+ chip_name: chip_name,
+ os_release: os_rel,
+ system_info: system_info,
+ bios_info: bios_info,
+ };
+ tester::collate_all_test_runs(&results, meta_data, output_format);
+ Ok(())
+}
+
+fn get_device_name_test(env: &mut TestEnv) -> TestResult {
+ // Success means we got something back, which is good enough.
+ flashrom::name(env.cmd)?;
+ Ok(())
+}
+
+fn wp_toggle_test(env: &mut TestEnv) -> TestResult {
+ // NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms.
+ // However, we will warn when it does fail.
+ // List the write-protected regions of flash.
+ match flashrom::wp_list(env.cmd) {
+ Ok(list_str) => info!("\n{}", list_str),
+ Err(e) => warn!("{}", e),
+ };
+ // Fails if unable to set either one
+ env.wp.set_hw(false)?;
+ env.wp.set_sw(false)?;
+ Ok(())
+}
+
+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(())
+}
+
+fn lock_test(env: &mut TestEnv) -> TestResult {
+ if !env.wp.can_control_hw_wp() {
+ return Err("Lock test requires ability to control hardware write protect".into());
+ }
+
+ env.wp.set_hw(false)?.set_sw(true)?;
+ // Toggling software WP off should work when hardware is off.
+ // Then enable again for another go.
+ env.wp.push().set_sw(false)?;
+
+ env.wp.set_hw(true)?;
+ // Clearing should fail when hardware is enabled
+ if env.wp.set_sw(false).is_ok() {
+ return Err("Software WP was reset despite hardware WP being enabled".into());
+ }
+ Ok(())
+}
+
+fn elog_sanity_test(env: &mut TestEnv) -> TestResult {
+ // Check that the elog contains *something*, as an indication that Coreboot
+ // is actually able to write to the Flash. Because this invokes mosys on the
+ // host, it doesn't make sense to run for other chips.
+ if env.chip_type() != FlashChip::HOST {
+ info!("Skipping ELOG sanity check for non-host chip");
+ return Ok(());
+ }
+ // mosys reads the flash, it should be back in the golden state
+ env.ensure_golden()?;
+ // Output is one event per line, drop empty lines in the interest of being defensive.
+ let event_count = cros_sysinfo::eventlog_list()?
+ .lines()
+ .filter(|l| !l.is_empty())
+ .count();
+
+ if event_count == 0 {
+ Err("ELOG contained no events".into())
+ } else {
+ Ok(())
+ }
+}
+
+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())
+ }
+ }
+}
+
+fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResult {
+ move |env: &mut TestEnv| {
+ // Need a clean image for verification
+ env.ensure_golden()?;
+
+ let (name, start, len) = utils::layout_section(env.layout(), section);
+ // Disable software WP so we can do range protection, but hardware WP
+ // must remain enabled for (most) range protection to do anything.
+ env.wp.set_hw(false)?.set_sw(false)?;
+ flashrom::wp_range(env.cmd, (start, len), true)?;
+ env.wp.set_hw(true)?;
+
+ let rws = flashrom::ROMWriteSpecifics {
+ layout_file: Some(LAYOUT_FILE),
+ write_file: Some(env.random_data_file()),
+ name_file: Some(name),
+ };
+ if flashrom::write_file_with_layout(env.cmd, &rws).is_ok() {
+ 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());
+ }
+ Ok(())
+ }
+}
+
+fn verify_fail_test(env: &mut TestEnv) -> TestResult {
+ // Comparing the flash contents to random data says they're not the same.
+ 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 0000000..b22ded2
--- /dev/null
+++ b/util/flashrom_tester/src/types.rs
@@ -0,0 +1,53 @@
+//
+// 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 const BOLD: &str = "\x1b[1m";
+
+pub const RESET: &str = "\x1b[0m";
+pub const MAGENTA: &str = "\x1b[35m";
+pub const YELLOW: &str = "\x1b[33m";
+pub const GREEN: &str = "\x1b[92m";
+pub const RED: &str = "\x1b[31m";
+
+macro_rules! style_dbg {
+ ($s: expr, $c: expr) => {
+ format!("{}{:?}{}", $c, $s, types::RESET)
+ };
+}
+macro_rules! style {
+ ($s: expr, $c: expr) => {
+ format!("{}{}{}", $c, $s, types::RESET)
+ };
+}
diff --git a/util/flashrom_tester/src/utils.rs b/util/flashrom_tester/src/utils.rs
new file mode 100644
index 0000000..d17480b
--- /dev/null
+++ b/util/flashrom_tester/src/utils.rs
@@ -0,0 +1,298 @@
+//
+// 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,
+}
+
+#[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 harware write-protect is
+ // to {dis}connect the battery (and/or open the WP screw).
+ let s = if dis { "dis" } else { "" };
+ info!("Prompt for hardware WP {}able", s);
+ eprintln!(" > {}connect the battery (and/or open the WP screw)", s);
+ pause();
+ let wp = get_hardware_wp()?;
+ if wp && dis {
+ eprintln!("Hardware write protect is still ENABLED!");
+ return toggle_hw_wp(dis);
+ }
+ if !wp && !dis {
+ eprintln!("Hardware write protect is still DISABLED!");
+ return toggle_hw_wp(dis);
+ }
+ Ok(())
+}
+
+pub fn ac_power_warning() {
+ info!("*****************************");
+ info!("AC power *must be* connected!");
+ info!("*****************************");
+ pause();
+}
+
+fn pause() {
+ let mut stdout = std::io::stdout();
+ // We want the cursor to stay at the end of the line, so we print without a newline
+ // and flush manually.
+ stdout.write(b"Press any key to continue...").unwrap();
+ stdout.flush().unwrap();
+ std::io::stdin().read(&mut [0]).unwrap();
+}
+
+pub fn get_hardware_wp() -> std::result::Result<bool, String> {
+ let (_, wp) = parse_crosssystem(&collect_crosssystem()?)?;
+ Ok(wp)
+}
+
+pub fn collect_crosssystem() -> Result<String, String> {
+ let cmd = match Command::new("crossystem").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())
+}
+
+fn parse_crosssystem(s: &str) -> Result<(Vec<&str>, bool), &'static str> {
+ // grep -v 'fwid +=' | grep -v 'hwid +='
+ let sysinfo = s
+ .split_terminator("\n")
+ .filter(|s| !s.contains("fwid +=") && !s.contains("hwid +="));
+
+ let state_line = match sysinfo.clone().filter(|s| s.starts_with("wpsw_cur")).next() {
+ None => return Err("No wpsw_cur in system info"),
+ Some(line) => line,
+ };
+ let wp_s_val = state_line
+ .trim_start_matches("wpsw_cur")
+ .trim_start_matches(' ')
+ .trim_start_matches('=')
+ .trim_start_matches(' ')
+ .get(..1)
+ .unwrap()
+ .parse::<u32>();
+
+ match wp_s_val {
+ Ok(v) => {
+ if v == 1 {
+ return Ok((sysinfo.collect(), true));
+ } else if v == 0 {
+ return Ok((sysinfo.collect(), false));
+ } else {
+ return Err("Unknown state value");
+ }
+ }
+ Err(_) => return Err("Cannot parse state value"),
+ }
+}
+
+pub fn translate_command_error(output: &std::process::Output) -> std::io::Error {
+ use std::io::{Error, ErrorKind};
+ // There is two cases on failure;
+ // 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,
+ }
+ );
+ }
+
+ #[test]
+ fn parse_crosssystem() {
+ use super::parse_crosssystem;
+
+ assert_eq!(
+ parse_crosssystem("This is not the tool you are looking for").err(),
+ Some("No wpsw_cur in system info")
+ );
+
+ assert_eq!(
+ parse_crosssystem("wpsw_cur = ERROR").err(),
+ Some("Cannot parse state value")
+ );
+
+ assert_eq!(
+ parse_crosssystem("wpsw_cur = 3").err(),
+ Some("Unknown state value")
+ );
+
+ assert_eq!(
+ parse_crosssystem("wpsw_cur = 0"),
+ Ok((vec!["wpsw_cur = 0"], false))
+ );
+
+ assert_eq!(
+ parse_crosssystem("wpsw_cur = 1"),
+ Ok((vec!["wpsw_cur = 1"], true))
+ );
+
+ assert_eq!(
+ parse_crosssystem("wpsw_cur=1"),
+ Ok((vec!["wpsw_cur=1"], true))
+ );
+
+ assert_eq!(
+ parse_crosssystem(
+ "fwid += 123wpsw_cur\n\
+ hwid += aaaaa\n\
+ wpsw_boot = 0 # [RO/int]\n\
+ wpsw_cur = 1 # [RO/int]\n"
+ ),
+ Ok((
+ vec![
+ "wpsw_boot = 0 # [RO/int]",
+ "wpsw_cur = 1 # [RO/int]"
+ ],
+ true
+ ))
+ );
+ }
+}
--
To view, visit https://review.coreboot.org/c/flashrom/+/38951
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: Ic2905a76cad90b1546b9328d668bf8abbf8aed44
Gerrit-Change-Number: 38951
Gerrit-PatchSet: 1
Gerrit-Owner: Edward O'Callaghan <quasisec(a)chromium.org>
Gerrit-MessageType: newchange
David Hendricks has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/38788 )
Change subject: testing: Add blackbox test uber-script
......................................................................
testing: Add blackbox test uber-script
This modifies CB:23025 further to work with upstream as it is now,
without the syntax changes in the patch chain. I also gave it a new
name since this script is, well, uber.
Since flashrom currently only supports reading/writing ROM-sized files
we can't easily determine a targeted region offset and size except
when a layout file is used. Therefore, some extra arithmetic is needed
in the partial write test and the only modes allowed are clobber mode
and layout mode.
A few other changes:
- Update paths and script name
- Remove write-protect testing support
- Use ROM-sized files only, no region-sized files
- Return error if flashmap or ifd mode are used
Tested using a Raspberry Pi with a W25Q16
Change-Id: I1af55d5088c54ee33853009797adbd535a506b49
Signed-off-by: David Hendricks <david.hendricks(a)gmail.com>
---
A util/testing/cmd.sh
A util/testing/ubertest.sh
2 files changed, 1,074 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/88/38788/1
diff --git a/util/testing/cmd.sh b/util/testing/cmd.sh
new file mode 100644
index 0000000..1d1c47a
--- /dev/null
+++ b/util/testing/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/testing/ubertest.sh b/util/testing/ubertest.sh
new file mode 100755
index 0000000..f51aa05
--- /dev/null
+++ b/util/testing/ubertest.sh
@@ -0,0 +1,937 @@
+#!/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
+#
+# Documentation for this script currently resides at: https://goo.gl/qNwdmm
+
+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 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
+ 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 succesfully 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
--
To view, visit https://review.coreboot.org/c/flashrom/+/38788
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings
Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: I1af55d5088c54ee33853009797adbd535a506b49
Gerrit-Change-Number: 38788
Gerrit-PatchSet: 1
Gerrit-Owner: David Hendricks <david.hendricks(a)gmail.com>
Gerrit-MessageType: newchange