summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Rosenthal <jrosenth@chromium.org>2020-05-27 07:30:24 -0600
committerCommit Bot <commit-bot@chromium.org>2020-06-09 16:30:56 +0000
commit1978c84e51cc8eb99f55167c6b5b6cc85439f832 (patch)
tree6d16f00d1022c19559031285ca53786817b18c66
parent5ae4df3bbf2efd4471c98d5a185544f589155f87 (diff)
downloadvboot-1978c84e51cc8eb99f55167c6b5b6cc85439f832.tar.gz
host/lib: add lightweight flashrom wrapper library
Lightweight wrapper around flashrom, exposing two APIs: flashrom_read(programmer, region, data_out, size_out) flashrom_write(programmer, region, data, size) |region| can be NULL, in which case operate on the whole flash chip. The intended usage of this wrapper library is to read/write VBNV from SPI flash directly, avoiding the call thru mosys (which has deprecated the command). Bringing this logic into crossystem directly will also help with expanding VBNV to 64-bytes. BUG=chromium:1032351,chromium:1030473,chromium:789276 BRANCH=none TEST=provided unit tests Change-Id: I3997bd03a2db7e58e4e76fc200c637dd3b5b20a4 Signed-off-by: Jack Rosenthal <jrosenth@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2218888
-rw-r--r--Makefile4
-rw-r--r--firmware/2lib/include/2return_codes.h3
-rw-r--r--host/lib/flashrom.c158
-rw-r--r--host/lib/include/flashrom.h50
-rw-r--r--tests/vb2_host_flashrom_tests.c238
5 files changed, 453 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index d69a63a3..19d586a3 100644
--- a/Makefile
+++ b/Makefile
@@ -450,6 +450,7 @@ UTILLIB_SRCS = \
host/lib/crossystem.c \
host/lib/crypto.c \
host/lib/file_keys.c \
+ host/lib/flashrom.c \
host/lib/fmap.c \
host/lib/host_common.c \
host/lib/host_key2.c \
@@ -509,9 +510,11 @@ HOSTLIB_SRCS = \
host/lib/crossystem.c \
host/lib/crypto.c \
host/lib/extract_vmlinuz.c \
+ host/lib/flashrom.c \
host/lib/fmap.c \
host/lib/host_misc.c \
host/lib/subprocess.c \
+ host/lib21/host_misc.c \
${TLCL_SRCS}
HOSTLIB_OBJS = ${HOSTLIB_SRCS:%.c=${BUILD}/%.o}
@@ -706,6 +709,7 @@ TEST2X_NAMES = \
tests/vb2_crypto_tests \
tests/vb2_ec_sync_tests \
tests/vb2_gbb_tests \
+ tests/vb2_host_flashrom_tests \
tests/vb2_host_key_tests \
tests/vb2_kernel_tests \
tests/vb2_misc_tests \
diff --git a/firmware/2lib/include/2return_codes.h b/firmware/2lib/include/2return_codes.h
index 6c0bd164..8d69847c 100644
--- a/firmware/2lib/include/2return_codes.h
+++ b/firmware/2lib/include/2return_codes.h
@@ -805,6 +805,9 @@ enum vb2_return_code {
/* Unable to convert string to struct vb_id */
VB2_ERROR_STR_TO_ID,
+ /* Flashrom exited with failure status */
+ VB2_ERROR_FLASHROM,
+
/**********************************************************************
* Errors generated by host library key functions
*/
diff --git a/host/lib/flashrom.c b/host/lib/flashrom.c
new file mode 100644
index 00000000..061c5b84
--- /dev/null
+++ b/host/lib/flashrom.c
@@ -0,0 +1,158 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* For strdup */
+#define _POSIX_C_SOURCE 200809L
+
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "2api.h"
+#include "2return_codes.h"
+#include "host_misc.h"
+#include "flashrom.h"
+#include "subprocess.h"
+
+#define FLASHROM_EXEC_NAME "flashrom"
+
+/**
+ * Helper to create a temporary file, and optionally write some data
+ * into it.
+ *
+ * @param data If data needs to be written to the file, a
+ * pointer to the buffer. Pass NULL to just
+ * create an empty temporary file.
+ * @param data_size The size of the buffer to write, if applicable.
+ * @param path_out An output pointer for the filename. Caller
+ * should free.
+ *
+ * @return VB2_SUCCESS on success, or a relevant error.
+ */
+static vb2_error_t write_temp_file(const uint8_t *data, uint32_t data_size,
+ char **path_out)
+{
+ int fd;
+ ssize_t write_rv;
+ vb2_error_t rv;
+ char *path;
+
+ *path_out = NULL;
+ path = strdup(P_tmpdir "/vb2_flashrom.XXXXXX");
+
+ fd = mkstemp(path);
+ if (fd < 0) {
+ rv = VB2_ERROR_WRITE_FILE_OPEN;
+ goto fail;
+ }
+
+ while (data && data_size > 0) {
+ write_rv = write(fd, data, data_size);
+ if (write_rv < 0) {
+ close(fd);
+ unlink(path);
+ rv = VB2_ERROR_WRITE_FILE_DATA;
+ goto fail;
+ }
+
+ data_size -= write_rv;
+ data += write_rv;
+ }
+
+ close(fd);
+ *path_out = path;
+ return VB2_SUCCESS;
+
+ fail:
+ free(path);
+ return rv;
+}
+
+static vb2_error_t run_flashrom(const char *const argv[])
+{
+ int status = subprocess_run(argv, &subprocess_null, &subprocess_null,
+ &subprocess_null);
+ if (status) {
+ fprintf(stderr, "Flashrom invocation failed (exit status %d):",
+ status);
+
+ for (const char *const *argp = argv; *argp; argp++)
+ fprintf(stderr, " %s", *argp);
+
+ fprintf(stderr, "\n");
+ return VB2_ERROR_FLASHROM;
+ }
+
+ return VB2_SUCCESS;
+}
+
+vb2_error_t flashrom_read(const char *programmer, const char *region,
+ uint8_t **data_out, uint32_t *size_out)
+{
+ char *tmpfile;
+ char region_param[PATH_MAX];
+ vb2_error_t rv;
+
+ *data_out = NULL;
+ *size_out = 0;
+
+ VB2_TRY(write_temp_file(NULL, 0, &tmpfile));
+
+ if (region)
+ snprintf(region_param, sizeof(region_param), "%s:%s", region,
+ tmpfile);
+
+ const char *const argv[] = {
+ FLASHROM_EXEC_NAME,
+ "-p",
+ programmer,
+ "-r",
+ region ? "-i" : tmpfile,
+ region ? region_param : NULL,
+ NULL,
+ };
+
+ rv = run_flashrom(argv);
+ if (rv == VB2_SUCCESS)
+ rv = vb2_read_file(tmpfile, data_out, size_out);
+
+ unlink(tmpfile);
+ free(tmpfile);
+ return rv;
+}
+
+vb2_error_t flashrom_write(const char *programmer, const char *region,
+ uint8_t *data, uint32_t size)
+{
+ char *tmpfile;
+ char region_param[PATH_MAX];
+ vb2_error_t rv;
+
+ VB2_TRY(write_temp_file(data, size, &tmpfile));
+
+ if (region)
+ snprintf(region_param, sizeof(region_param), "%s:%s", region,
+ tmpfile);
+
+ const char *const argv[] = {
+ FLASHROM_EXEC_NAME,
+ "-p",
+ programmer,
+ "-w",
+ region ? "-i" : tmpfile,
+ region ? region_param : NULL,
+ NULL,
+ };
+
+ rv = run_flashrom(argv);
+ unlink(tmpfile);
+ free(tmpfile);
+ return rv;
+}
diff --git a/host/lib/include/flashrom.h b/host/lib/include/flashrom.h
new file mode 100644
index 00000000..560fbb0e
--- /dev/null
+++ b/host/lib/include/flashrom.h
@@ -0,0 +1,50 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Host utilites to execute flashrom command.
+ */
+
+#include <stdint.h>
+
+#include "2return_codes.h"
+
+#define FLASHROM_PROGRAMMER_INTERNAL_AP "host"
+#define FLASHROM_PROGRAMMER_INTERNAL_EC "ec"
+
+/**
+ * Read using flashrom into an allocated buffer.
+ *
+ * @param programmer The name of the programmer to use. There are
+ * named constants FLASHROM_PROGRAMMER_INTERNAL_AP
+ * and FLASHROM_PROGRAMMER_INTERNAL_EC available
+ * for the AP and EC respectively, or a custom
+ * programmer string can be provided.
+ * @param region The name of the fmap region to read, or NULL to
+ * read the entire flash chip.
+ * @param data_out Output parameter of allocated buffer to read into.
+ * The caller should free the buffer.
+ * @param size_out Output parameter of buffer size.
+ *
+ * @return VB2_SUCCESS on success, or a relevant error.
+ */
+vb2_error_t flashrom_read(const char *programmer, const char *region,
+ uint8_t **data_out, uint32_t *size_out);
+
+/**
+ * Write using flashrom from a buffer.
+ *
+ * @param programmer The name of the programmer to use. There are
+ * named constants FLASHROM_PROGRAMMER_INTERNAL_AP
+ * and FLASHROM_PROGRAMMER_INTERNAL_EC available
+ * for the AP and EC respectively, or a custom
+ * programmer string can be provided.
+ * @param region The name of the fmap region to write, or NULL to
+ * write the entire flash chip.
+ * @param data The buffer to write.
+ * @param size The size of the buffer to write.
+ *
+ * @return VB2_SUCCESS on success, or a relevant error.
+ */
+vb2_error_t flashrom_write(const char *programmer, const char *region,
+ uint8_t *data, uint32_t size);
diff --git a/tests/vb2_host_flashrom_tests.c b/tests/vb2_host_flashrom_tests.c
new file mode 100644
index 00000000..f8dbd442
--- /dev/null
+++ b/tests/vb2_host_flashrom_tests.c
@@ -0,0 +1,238 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Tests for host flashrom utilities.
+ */
+
+/* For strdup */
+#define _POSIX_C_SOURCE 200809L
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "2common.h"
+#include "2return_codes.h"
+#include "host_misc.h"
+#include "flashrom.h"
+#include "subprocess.h"
+#include "test_common.h"
+
+#define MOCK_TMPFILE_NAME "/tmp/vb2_unittest"
+#define MOCK_ROM_CONTENTS "bloop123"
+
+static bool flashrom_mock_success = true;
+static enum { FLASHROM_NONE, FLASHROM_READ, FLASHROM_WRITE } captured_operation;
+static const char *captured_op_filename;
+static const char *captured_region_param;
+static const char *captured_programmer;
+static uint8_t *captured_rom_contents;
+static uint32_t captured_rom_size;
+
+/* Mocked mkstemp for tests. */
+int mkstemp(char *template_name)
+{
+ strncpy(template_name, MOCK_TMPFILE_NAME, strlen(template_name));
+ return open(template_name, O_RDWR | O_CREAT | O_TRUNC, 0666);
+}
+
+/* Mocked subprocess_run for tests. */
+int subprocess_run(const char *const argv[],
+ struct subprocess_target *input,
+ struct subprocess_target *output,
+ struct subprocess_target *error)
+{
+ int argc;
+ int opt;
+ int rv;
+
+ /* Reset static variables to their defaults. */
+ captured_operation = FLASHROM_NONE;
+ captured_op_filename = NULL;
+ captured_region_param = NULL;
+ captured_programmer = NULL;
+ captured_rom_contents = NULL;
+ captured_rom_size = 0;
+ optind = 0;
+
+ /* Count the number of arguments, a required formalism for
+ getopt. */
+ for (argc = 0; argv[argc]; argc++)
+ continue;
+
+ /* We only understand the subset of arguments used by the
+ wrapper library. If it's updated to support more modes of
+ operation, this unit test code should be updated too. */
+ while ((opt = getopt(argc, (char *const *)argv, ":p:r:w:i:")) != -1) {
+ /* Always consume the next argument if it does not
+ start with a dash. We have to muck with getopt's
+ global variables to make this happen. */
+ if (opt == ':' && argv[optind] && argv[optind][0] != '-') {
+ optarg = strdup(argv[optind]);
+ optind++;
+ opt = optopt;
+ } else if (optarg && optarg[0] == '-') {
+ optarg = NULL;
+ optind--;
+ } else if (optarg) {
+ optarg = strdup(optarg);
+ }
+
+ switch (opt) {
+ case 'p':
+ captured_programmer = optarg;
+ break;
+ case 'r':
+ captured_operation = FLASHROM_READ;
+ captured_op_filename = optarg;
+ break;
+ case 'w':
+ captured_operation = FLASHROM_WRITE;
+ captured_op_filename = optarg;
+ break;
+ case 'i':
+ captured_region_param = optarg;
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ if (optind != argc) {
+ /* Extra arguments we don't understand. */
+ return 1;
+ }
+
+ rv = !flashrom_mock_success;
+
+ if (captured_operation == FLASHROM_READ) {
+ /* Write the mocked string we read from the ROM. */
+ rv |= vb2_write_file(MOCK_TMPFILE_NAME, MOCK_ROM_CONTENTS,
+ strlen(MOCK_ROM_CONTENTS));
+ } else if (captured_operation == FLASHROM_WRITE) {
+ /* Capture the buffer contents we wrote to the ROM. */
+ rv |= vb2_read_file(MOCK_TMPFILE_NAME, &captured_rom_contents,
+ &captured_rom_size);
+ }
+
+ return rv;
+}
+
+static void test_read_whole_chip(void)
+{
+ uint8_t *buf;
+ uint32_t buf_sz;
+
+ TEST_SUCC(flashrom_read("someprog", NULL, &buf, &buf_sz),
+ "Flashrom read succeeds");
+ TEST_STR_EQ(captured_programmer, "someprog",
+ "Using specified programmer");
+ TEST_EQ(captured_operation, FLASHROM_READ, "Doing a read operation");
+ TEST_STR_EQ(captured_op_filename, MOCK_TMPFILE_NAME,
+ "Reading to correct file");
+ TEST_PTR_EQ(captured_region_param, NULL, "Not operating on a region");
+ TEST_EQ(buf_sz, strlen(MOCK_ROM_CONTENTS), "Contents correct size");
+ TEST_SUCC(memcmp(buf, MOCK_ROM_CONTENTS, buf_sz),
+ "Buffer has correct contents");
+
+ free(buf);
+}
+
+static void test_read_region(void)
+{
+ uint8_t *buf;
+ uint32_t buf_sz;
+
+ TEST_SUCC(flashrom_read("someprog", "SOME_REGION", &buf, &buf_sz),
+ "Flashrom read succeeds");
+ TEST_STR_EQ(captured_programmer, "someprog",
+ "Using specified programmer");
+ TEST_EQ(captured_operation, FLASHROM_READ, "Doing a read operation");
+ TEST_PTR_EQ(captured_op_filename, NULL,
+ "Not doing a read of the whole ROM");
+ TEST_STR_EQ(captured_region_param, "SOME_REGION:" MOCK_TMPFILE_NAME,
+ "Reading to correct file and from correct region");
+ TEST_EQ(buf_sz, strlen(MOCK_ROM_CONTENTS), "Contents correct size");
+ TEST_SUCC(memcmp(buf, MOCK_ROM_CONTENTS, buf_sz),
+ "Buffer has correct contents");
+
+ free(buf);
+}
+
+static void test_read_failure(void)
+{
+ uint8_t *buf;
+ uint32_t buf_sz;
+
+ flashrom_mock_success = false;
+ TEST_NEQ(flashrom_read("someprog", "SOME_REGION", &buf, &buf_sz),
+ VB2_SUCCESS, "Flashrom read fails");
+ flashrom_mock_success = true;
+}
+
+static void test_write_whole_chip(void)
+{
+ uint8_t buf[sizeof(MOCK_ROM_CONTENTS) - 1];
+
+ memcpy(buf, MOCK_ROM_CONTENTS, sizeof(buf));
+
+ TEST_SUCC(flashrom_write("someprog", NULL, buf, sizeof(buf)),
+ "Flashrom write succeeds");
+ TEST_STR_EQ(captured_programmer, "someprog",
+ "Using specified programmer");
+ TEST_EQ(captured_operation, FLASHROM_WRITE, "Doing a write operation");
+ TEST_STR_EQ(captured_op_filename, MOCK_TMPFILE_NAME,
+ "Writing to correct file");
+ TEST_PTR_EQ(captured_region_param, NULL, "Not operating on a region");
+ TEST_EQ(captured_rom_size, strlen(MOCK_ROM_CONTENTS),
+ "Contents correct size");
+ TEST_SUCC(memcmp(captured_rom_contents, MOCK_ROM_CONTENTS,
+ captured_rom_size), "Buffer has correct contents");
+}
+
+static void test_write_region(void)
+{
+ uint8_t buf[sizeof(MOCK_ROM_CONTENTS) - 1];
+
+ memcpy(buf, MOCK_ROM_CONTENTS, sizeof(buf));
+
+ TEST_SUCC(flashrom_write("someprog", "SOME_REGION", buf, sizeof(buf)),
+ "Flashrom write succeeds");
+ TEST_STR_EQ(captured_programmer, "someprog",
+ "Using specified programmer");
+ TEST_EQ(captured_operation, FLASHROM_WRITE, "Doing a write operation");
+ TEST_PTR_EQ(captured_op_filename, NULL,
+ "Not doing a write of the whole ROM");
+ TEST_STR_EQ(captured_region_param, "SOME_REGION:" MOCK_TMPFILE_NAME,
+ "Writing to correct file and from correct region");
+ TEST_EQ(captured_rom_size, strlen(MOCK_ROM_CONTENTS),
+ "Contents correct size");
+ TEST_SUCC(memcmp(captured_rom_contents, MOCK_ROM_CONTENTS,
+ captured_rom_size), "Buffer has correct contents");
+}
+
+static void test_write_failure(void)
+{
+ uint8_t buf[20] = { 0 };
+
+ flashrom_mock_success = false;
+ TEST_NEQ(flashrom_write("someprog", "SOME_REGION", buf, sizeof(buf)),
+ VB2_SUCCESS, "Flashrom write fails");
+ flashrom_mock_success = true;
+}
+
+int main(int argc, char *argv[])
+{
+ test_read_whole_chip();
+ test_read_region();
+ test_read_failure();
+ test_write_whole_chip();
+ test_write_region();
+ test_write_failure();
+
+ return gTestSuccess ? 0 : 255;
+}