summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Rosenthal <jrosenth@chromium.org>2020-05-27 08:59:18 -0600
committerCommit Bot <commit-bot@chromium.org>2020-06-09 16:30:57 +0000
commitc95ea3f2cb7b9aaf64d8b2d7ede13783191e0b73 (patch)
treed7ca19aa9b9df2490c035cbf2b423d30da73e9c3
parent1978c84e51cc8eb99f55167c6b5b6cc85439f832 (diff)
downloadvboot-c95ea3f2cb7b9aaf64d8b2d7ede13783191e0b73.tar.gz
crossystem: add functions to read and write VBNV via flashrom
This will replace the usage of "mosys nvram vboot {read,write}" on x86 platforms, and all ARM platforms except veyron (chromebooks only) and nyan_kitty (which use VBNV storage in the ChromeOS EC, deprecated for new platforms). These affected ARM devices will be going AUE sometime this summer, and we can expect to remove the mosys usage in crossystem later this year. The code to find the active VBNV in SPI flash was modeled to match the logic in mosys (see mosys/lib/vbnv/vbnv_flash.c). BUG=chromium:1032351,chromium:1030473,chromium:789276 BRANCH=none TEST=provided unit tests Signed-off-by: Jack Rosenthal <jrosenth@chromium.org> Change-Id: I4f42af2f9a6b0703302635f8d8ebb2d7599d9847 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2218889
-rw-r--r--Makefile1
-rw-r--r--host/include/crossystem_vbnv.h14
-rw-r--r--host/lib/crossystem.c102
-rw-r--r--tests/vb2_host_nvdata_flashrom_tests.c279
4 files changed, 396 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 19d586a3..b98f9edb 100644
--- a/Makefile
+++ b/Makefile
@@ -711,6 +711,7 @@ TEST2X_NAMES = \
tests/vb2_gbb_tests \
tests/vb2_host_flashrom_tests \
tests/vb2_host_key_tests \
+ tests/vb2_host_nvdata_flashrom_tests \
tests/vb2_kernel_tests \
tests/vb2_misc_tests \
tests/vb2_nvstorage_tests \
diff --git a/host/include/crossystem_vbnv.h b/host/include/crossystem_vbnv.h
index 64b6b1d6..d61506c2 100644
--- a/host/include/crossystem_vbnv.h
+++ b/host/include/crossystem_vbnv.h
@@ -15,6 +15,20 @@ extern "C" {
struct vb2_context;
/**
+ * Attempt to read non-volatile storage using flashrom.
+ *
+ * Returns 0 if success, non-zero if error.
+ */
+int vb2_read_nv_storage_flashrom(struct vb2_context *ctx);
+
+/**
+ * Attempt to write non-volatile storage using flashrom.
+ *
+ * Returns 0 if success, non-zero if error.
+ */
+int vb2_write_nv_storage_flashrom(struct vb2_context* ctx);
+
+/**
* Attempt to read non-volatile storage using mosys.
*
* Returns 0 if success, non-zero if error.
diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c
index cf7600e4..c5c72cfe 100644
--- a/host/lib/crossystem.c
+++ b/host/lib/crossystem.c
@@ -16,6 +16,7 @@
#include "crossystem.h"
#include "crossystem_vbnv.h"
#include "host_common.h"
+#include "flashrom.h"
#include "subprocess.h"
#include "vboot_struct.h"
@@ -655,6 +656,107 @@ int VbSetSystemPropertyString(const char* name, const char* value)
return -1;
}
+/**
+ * Get index of the last valid VBNV entry in an EEPROM.
+ *
+ * @param buf Pointer to the beginning of the EEPROM.
+ * @param buf_sz Size of the EEPROM.
+ * @param vbnv_size The size of a single VBNV entry for this device.
+ *
+ * @return The index of the last valid VBNV entry on success, or -1 on
+ * failure.
+ */
+static int vb2_nv_index(const uint8_t *buf, uint32_t buf_sz, int vbnv_size)
+{
+ int index;
+ uint8_t blank[VB2_NVDATA_SIZE_V2];
+
+ /* The size of the buffer should be an even multiple of the
+ VBNV size. */
+ if (buf_sz % vbnv_size != 0) {
+ VB2_DIE("The VBNV in flash (%u bytes) is not an even multiple "
+ "of the VBNV size (%u bytes). This is likely a "
+ "firmware bug.\n", buf_sz, vbnv_size);
+ }
+
+ memset(blank, 0xff, sizeof(blank));
+ for (index = 0; index < buf_sz / vbnv_size; index++) {
+ if (!memcmp(blank, &buf[index * vbnv_size], vbnv_size))
+ break;
+ }
+
+ if (!index || index == buf_sz / vbnv_size) {
+ fprintf(stderr, "VBNV is either uninitialized or corrupted.\n");
+ return -1;
+ }
+
+ return index - 1;
+}
+
+#define VBNV_FMAP_REGION "RW_NVRAM"
+
+int vb2_read_nv_storage_flashrom(struct vb2_context *ctx)
+{
+ int index;
+ int vbnv_size = vb2_nv_get_size(ctx);
+ uint8_t *flash_buf;
+ uint32_t flash_size;
+
+ if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION,
+ &flash_buf, &flash_size))
+ return -1;
+
+ index = vb2_nv_index(flash_buf, flash_size, vbnv_size);
+ if (index < 0) {
+ free(flash_buf);
+ return -1;
+ }
+
+ memcpy(ctx->nvdata, &flash_buf[index * vbnv_size], vbnv_size);
+ free(flash_buf);
+ return 0;
+}
+
+int vb2_write_nv_storage_flashrom(struct vb2_context *ctx)
+{
+ int rv = 0;
+ int current_index;
+ int next_index;
+ int vbnv_size = vb2_nv_get_size(ctx);
+ uint8_t *flash_buf;
+ uint32_t flash_size;
+
+ if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION,
+ &flash_buf, &flash_size))
+ return -1;
+
+ current_index = vb2_nv_index(flash_buf, flash_size, vbnv_size);
+ if (current_index < 0) {
+ rv = -1;
+ goto exit;
+ }
+
+ next_index = current_index + 1;
+ if ((next_index + 1) * vbnv_size == flash_size) {
+ /* VBNV is full. Erase and write at beginning. */
+ memset(flash_buf, 0xff, flash_size);
+ next_index = 0;
+ }
+
+ memcpy(&flash_buf[next_index * vbnv_size], ctx->nvdata, vbnv_size);
+ if (flashrom_write(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION,
+ flash_buf, flash_size)) {
+ rv = -1;
+ goto exit;
+ }
+
+ exit:
+ free(flash_buf);
+ return rv;
+}
+
+/* TODO(crbug.com/1090803): remove these mosys nvdata functions
+ (approx. October 2020). */
int vb2_read_nv_storage_mosys(struct vb2_context *ctx)
{
/* Reserve extra 32 bytes */
diff --git a/tests/vb2_host_nvdata_flashrom_tests.c b/tests/vb2_host_nvdata_flashrom_tests.c
new file mode 100644
index 00000000..7a147856
--- /dev/null
+++ b/tests/vb2_host_nvdata_flashrom_tests.c
@@ -0,0 +1,279 @@
+/* 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 crossystem flashrom-based nvdata functions.
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "2api.h"
+#include "2common.h"
+#include "2constants.h"
+#include "2nvstorage.h"
+#include "2return_codes.h"
+#include "crossystem_vbnv.h"
+#include "flashrom.h"
+#include "test_common.h"
+
+/* Mocked flashrom only supports host programmer, and RW_NVRAM
+ region. */
+static void assert_mock_params(const char *programmer, const char *region)
+{
+ TEST_STR_EQ(programmer, FLASHROM_PROGRAMMER_INTERNAL_AP,
+ "Using internal AP programmer");
+ TEST_STR_EQ(region, "RW_NVRAM", "Using NVRAM region");
+}
+
+static bool mock_flashrom_fail;
+
+/* To support both 16-byte and 64-byte nvdata with the same fake
+ eeprom, we can size the flash chip to be 16x64. So, for 16-byte
+ nvdata, this is a flash chip with 64 entries, and for 64-byte
+ nvdata, this is a flash chip with 16 entries. */
+static uint8_t fake_flash_region[VB2_NVDATA_SIZE * VB2_NVDATA_SIZE_V2];
+static int fake_flash_entry_count;
+
+static const uint8_t test_nvdata_16b[] = {
+ 0x60, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x4e,
+ 0x00, 0xfe, 0xff, 0x00, 0x00, 0xff, 0xff, 0x5e,
+};
+
+static const uint8_t test_nvdata2_16b[] = {
+ 0x60, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x4c,
+ 0x00, 0xfe, 0xff, 0x00, 0x00, 0xff, 0xff, 0x78,
+};
+
+static void reset_test_data(struct vb2_context *ctx, int nvdata_size)
+{
+ /* Initialize the context value. */
+ ctx->flags = 0;
+
+ switch (nvdata_size) {
+ case VB2_NVDATA_SIZE:
+ fake_flash_entry_count = VB2_NVDATA_SIZE_V2;
+ memcpy(ctx->nvdata, test_nvdata_16b, sizeof(test_nvdata_16b));
+ break;
+ case VB2_NVDATA_SIZE_V2:
+ ctx->flags |= VB2_CONTEXT_NVDATA_V2;
+ fake_flash_entry_count = VB2_NVDATA_SIZE;
+ /* TODO: create some test data for 64-byte nvdata and
+ put it here. Right now, this only tests 16-byte
+ nvdata. */
+ break;
+ default:
+ /* This is not valid. */
+ TEST_TRUE(false, "Test failed, invalid nvdata size");
+ fake_flash_entry_count = 0;
+ break;
+ }
+
+ /* Clear the fake flash chip. */
+ memset(fake_flash_region, 0xff, sizeof(fake_flash_region));
+
+ /* Flashrom succeeds unless the test says otherwise. */
+ mock_flashrom_fail = false;
+}
+
+/* Mocked flashrom_read for tests. */
+vb2_error_t flashrom_read(const char *programmer, const char *region,
+ uint8_t **data_out, uint32_t *size_out)
+{
+ if (mock_flashrom_fail) {
+ *data_out = NULL;
+ *size_out = 0;
+ return VB2_ERROR_FLASHROM;
+ }
+
+ assert_mock_params(programmer, region);
+
+ *data_out = malloc(sizeof(fake_flash_region));
+ *size_out = sizeof(fake_flash_region);
+ memcpy(*data_out, fake_flash_region, sizeof(fake_flash_region));
+ return VB2_SUCCESS;
+}
+
+/* Mocked flashrom_write for tests. */
+vb2_error_t flashrom_write(const char *programmer, const char *region,
+ uint8_t *data, uint32_t data_size)
+{
+ if (mock_flashrom_fail)
+ return VB2_ERROR_FLASHROM;
+
+ assert_mock_params(programmer, region);
+
+ TEST_EQ(data_size, sizeof(fake_flash_region),
+ "The flash size is correct");
+ memcpy(fake_flash_region, data, data_size);
+ return VB2_SUCCESS;
+}
+
+static void test_read_ok_beginning(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region, test_nvdata2_16b, sizeof(test_nvdata2_16b));
+
+ TEST_EQ(vb2_read_nv_storage_flashrom(&ctx), 0,
+ "Reading storage succeeds");
+ TEST_EQ(memcmp(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)),
+ 0, "The nvdata in the vb2_context was updated from flash");
+}
+
+static void test_read_ok_2ndentry(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region + VB2_NVDATA_SIZE, test_nvdata2_16b,
+ sizeof(test_nvdata2_16b));
+
+ TEST_EQ(vb2_read_nv_storage_flashrom(&ctx), 0,
+ "Reading storage succeeds");
+ TEST_EQ(memcmp(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)),
+ 0, "The nvdata in the vb2_context was updated from flash");
+}
+
+static void test_read_ok_full(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+
+ for (int entry = 0; entry < fake_flash_entry_count - 2; entry++)
+ memcpy(fake_flash_region + (entry * VB2_NVDATA_SIZE),
+ test_nvdata_16b, sizeof(test_nvdata_16b));
+
+ memcpy(fake_flash_region +
+ ((fake_flash_entry_count - 2) * VB2_NVDATA_SIZE),
+ test_nvdata2_16b, sizeof(test_nvdata2_16b));
+
+ TEST_EQ(vb2_read_nv_storage_flashrom(&ctx), 0,
+ "Reading storage succeeds");
+ TEST_EQ(memcmp(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)),
+ 0, "The nvdata in the vb2_context was updated from flash");
+}
+
+static void test_read_fail_uninitialized(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+
+ TEST_NEQ(vb2_read_nv_storage_flashrom(&ctx), 0,
+ "Reading storage fails when flash is erased");
+}
+
+static void test_read_fail_flashrom(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b));
+ mock_flashrom_fail = true;
+
+ TEST_NEQ(vb2_read_nv_storage_flashrom(&ctx), 0,
+ "Reading storage fails when flashrom fails");
+}
+
+static void test_write_ok_beginning(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b));
+ memcpy(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b));
+
+ TEST_EQ(vb2_write_nv_storage_flashrom(&ctx), 0,
+ "Writing storage succeeds");
+ TEST_EQ(memcmp(fake_flash_region + VB2_NVDATA_SIZE, test_nvdata2_16b,
+ sizeof(test_nvdata2_16b)),
+ 0, "The flash was updated with a new entry");
+}
+
+static void test_write_ok_2ndentry(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region + VB2_NVDATA_SIZE, test_nvdata_16b,
+ sizeof(test_nvdata_16b));
+ memcpy(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b));
+
+ TEST_EQ(vb2_write_nv_storage_flashrom(&ctx), 0,
+ "Writing storage succeeds");
+ TEST_EQ(memcmp(fake_flash_region + (2 * VB2_NVDATA_SIZE),
+ test_nvdata2_16b, sizeof(test_nvdata2_16b)),
+ 0, "The flash was updated with a new entry");
+}
+
+static void test_write_ok_full(void)
+{
+ struct vb2_context ctx;
+ uint8_t expected_flash[sizeof(fake_flash_region)];
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+
+ for (int entry = 0; entry < fake_flash_entry_count - 1; entry++)
+ memcpy(fake_flash_region + (entry * VB2_NVDATA_SIZE),
+ test_nvdata_16b, sizeof(test_nvdata_16b));
+
+ memcpy(expected_flash, test_nvdata2_16b, sizeof(test_nvdata2_16b));
+ memset(expected_flash + VB2_NVDATA_SIZE, 0xff,
+ sizeof(expected_flash) - VB2_NVDATA_SIZE);
+ memcpy(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b));
+
+ TEST_EQ(vb2_write_nv_storage_flashrom(&ctx), 0,
+ "Writing storage succeeds");
+ TEST_EQ(memcmp(fake_flash_region, expected_flash,
+ sizeof(expected_flash)),
+ 0,
+ "The flash was erased and the new entry was placed at "
+ "the beginning");
+}
+
+static void test_write_fail_uninitialized(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+
+ TEST_NEQ(vb2_write_nv_storage_flashrom(&ctx), 0,
+ "Writing storage fails when the flash is erased");
+}
+
+static void test_write_fail_flashrom(void)
+{
+ struct vb2_context ctx;
+
+ reset_test_data(&ctx, sizeof(test_nvdata_16b));
+ memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b));
+ mock_flashrom_fail = true;
+
+ TEST_NEQ(vb2_write_nv_storage_flashrom(&ctx), 0,
+ "Writing storage fails when flashrom fails");
+}
+
+int main(int argc, char *argv[])
+{
+ test_read_ok_beginning();
+ test_read_ok_2ndentry();
+ test_read_ok_full();
+ test_read_fail_uninitialized();
+ test_read_fail_flashrom();
+ test_write_ok_beginning();
+ test_write_ok_2ndentry();
+ test_write_ok_full();
+ test_write_fail_uninitialized();
+ test_write_fail_flashrom();
+
+ return gTestSuccess ? 0 : 255;
+}