summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;
+}