summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2011-02-17 15:57:39 -0800
committerRandall Spangler <rspangler@chromium.org>2011-02-17 15:57:39 -0800
commitb944534edd3799b3353f73bcb8ee90161d640c2b (patch)
treecb30ae5a006d35d536ad0e51073f2570ded483ff
parenta7209ee2de570a1404e6df247df4a68c72d16245 (diff)
downloadvboot-b944534edd3799b3353f73bcb8ee90161d640c2b.tar.gz
Add NV storage API to vboot reference
BUG=chromium-os:12282 TEST=make && make runtests Review URL: http://codereview.chromium.org/6532040 Change-Id: I57099de54ed56aa722f1944568bbb58b71b14379
-rw-r--r--firmware/Makefile3
-rw-r--r--firmware/include/vboot_nvstorage.h73
-rw-r--r--firmware/lib/vboot_nvstorage.c186
-rw-r--r--tests/Makefile1
-rw-r--r--tests/vboot_nvstorage_test.c210
5 files changed, 472 insertions, 1 deletions
diff --git a/firmware/Makefile b/firmware/Makefile
index d17e1366..fcff8bb7 100644
--- a/firmware/Makefile
+++ b/firmware/Makefile
@@ -45,7 +45,8 @@ LIB_SRCS = \
./lib/utility.c \
./lib/vboot_common.c \
./lib/vboot_firmware.c \
- ./lib/vboot_kernel.c
+ ./lib/vboot_kernel.c \
+ ./lib/vboot_nvstorage.c
LIB_OBJS = $(LIB_SRCS:%.c=${BUILD_ROOT}/%.o)
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h
new file mode 100644
index 00000000..cd4f89dd
--- /dev/null
+++ b/firmware/include/vboot_nvstorage.h
@@ -0,0 +1,73 @@
+/* Copyright (c) 2011 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.
+ */
+
+/* Non-volatile storage routines.
+ */
+
+#ifndef VBOOT_REFERENCE_NVSTORAGE_H_
+#define VBOOT_REFERENCE_NVSTORAGE_H_
+
+#define NV_BLOCK_SIZE 16 /* Size of NV storage block in bytes */
+
+typedef struct VbNvContext {
+ /* Raw NV data. Caller must fill this before calling VbNvSetup(). */
+ uint8_t raw[NV_BLOCK_SIZE];
+ /* Flag indicating whether raw data has changed. Set by VbNvTeardown() if
+ * the raw data has changed and needs to be stored to the underlying
+ * non-volatile data store. */
+ int raw_changed;
+
+ /* Internal data for NV storage routines. Caller should not touch
+ * these fields. */
+ int regenerate_crc;
+
+} VbNvContext;
+
+
+/* Parameter type for VbNvGet(), VbNvSet(). */
+typedef enum VbNvParam {
+ VBNV_FIRMWARE_SETTINGS_RESET = 0,
+ VBNV_KERNEL_SETTINGS_RESET,
+ VBNV_DEBUG_RESET_MODE,
+ VBNV_TRY_B_COUNT,
+ VBNV_RECOVERY_REQUEST,
+ VBNV_LOCALIZATION_INDEX,
+ VBNV_KERNEL_FIELD,
+} VbNvParam;
+
+
+/* Initialize the NV storage library. This must be called before any
+ * other functions in this library. Returns 0 if success, non-zero if
+ * error.
+ *
+ * If you have access to global variables, you may want to wrap this
+ * in your own VbNvOpen() function which allocates a context, acquires
+ * a lock to prevent race conditions accessing the underlying storage,
+ * reads the raw data from underlying storage, and calls VbNvSetup().
+ * We don't do that in here because there are no global variables in
+ * UEFI BIOS during PEI phase. */
+int VbNvSetup(VbNvContext* context);
+
+/* Clean up and flush changes back to the raw data. This must be
+ * called after other functions in this library. Caller must check
+ * context.raw_changed after calling this function. Returns 0 if
+ * success, non-zero if error.
+ *
+ * If you have access to global variables, you may want to wrap this
+ * in your own VbNvClose() function which calls VbNvTeardown(), writes
+ * the underlying storage if context.raw_changed, releases the lock
+ * acquired in VbNvOpen, and frees the context. */
+int VbNvTeardown(VbNvContext* context);
+
+/* Read a NV storage parameter into *dest. Returns 0 if success,
+ * non-zero if error. */
+int VbNvGet(VbNvContext* context, VbNvParam param, uint32_t* dest);
+
+/* Set a NV storage param to a new value. Returns 0 if success,
+ * non-zero if error. */
+int VbNvSet(VbNvContext* context, VbNvParam param, uint32_t value);
+
+
+#endif /* VBOOT_REFERENCE_NVSTORAGE_H_ */
diff --git a/firmware/lib/vboot_nvstorage.c b/firmware/lib/vboot_nvstorage.c
new file mode 100644
index 00000000..81816ac4
--- /dev/null
+++ b/firmware/lib/vboot_nvstorage.c
@@ -0,0 +1,186 @@
+/* Copyright (c) 2011 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.
+ */
+
+/* Non-volatile storage routines.
+ */
+
+#include "utility.h"
+#include "vboot_common.h"
+#include "vboot_nvstorage.h"
+
+/* Constants for NV storage. We use this rather than structs and
+ * bitfields so the data format is consistent across platforms and
+ * compilers. */
+#define HEADER_OFFSET 0
+#define HEADER_MASK 0xC0
+#define HEADER_SIGNATURE 0x40
+#define HEADER_FIRMWARE_SETTINGS_RESET 0x20
+#define HEADER_KERNEL_SETTINGS_RESET 0x10
+
+#define BOOT_OFFSET 1
+#define BOOT_DEBUG_RESET_MODE 0x80
+#define BOOT_TRY_B_COUNT 0x0F
+
+#define RECOVERY_OFFSET 2
+#define LOCALIZATION_OFFSET 3
+#define KERNEL_FIELD_OFFSET 11
+#define CRC_OFFSET 15
+
+
+/* Return CRC-8 of the data, using x^8 + x^2 + x + 1 polynomial. A
+ * table-based algorithm would be faster, but for only 15 bytes isn't
+ * worth the code size. */
+static uint8_t Crc8(const uint8_t* data, int len) {
+ unsigned crc = 0;
+ int i, j;
+
+ for (j = len; j; j--, data++) {
+ crc ^= (*data << 8);
+ for(i = 8; i; i--) {
+ if (crc & 0x8000)
+ crc ^= (0x1070 << 3);
+ crc <<= 1;
+ }
+ }
+
+ return (uint8_t)(crc >> 8);
+}
+
+
+int VbNvSetup(VbNvContext* context) {
+ uint8_t* raw = context->raw;
+
+ /* Nothing has changed yet. */
+ context->raw_changed = 0;
+ context->regenerate_crc = 0;
+
+ /* Check data for consistency */
+ if ((HEADER_SIGNATURE != (raw[HEADER_OFFSET] & HEADER_MASK))
+ || (Crc8(raw, CRC_OFFSET) != raw[CRC_OFFSET])) {
+
+ /* Data is inconsistent (bad CRC or header), so reset defaults */
+ Memset(raw, 0, NV_BLOCK_SIZE);
+ raw[HEADER_OFFSET] = (HEADER_SIGNATURE | HEADER_FIRMWARE_SETTINGS_RESET |
+ HEADER_KERNEL_SETTINGS_RESET);
+
+ /* Regenerate CRC on exit */
+ context->regenerate_crc = 1;
+ }
+
+ return 0;
+}
+
+
+int VbNvTeardown(VbNvContext* context) {
+
+ if (context->regenerate_crc) {
+ context->raw[CRC_OFFSET] = Crc8(context->raw, CRC_OFFSET);
+ context->regenerate_crc = 0;
+ context->raw_changed = 1;
+ }
+
+ return 0;
+}
+
+
+int VbNvGet(VbNvContext* context, VbNvParam param, uint32_t* dest) {
+ const uint8_t* raw = context->raw;
+
+ switch (param) {
+ case VBNV_FIRMWARE_SETTINGS_RESET:
+ *dest = (raw[HEADER_OFFSET] & HEADER_FIRMWARE_SETTINGS_RESET ? 1 : 0);
+ return 0;
+
+ case VBNV_KERNEL_SETTINGS_RESET:
+ *dest = (raw[HEADER_OFFSET] & HEADER_KERNEL_SETTINGS_RESET ? 1 : 0);
+ return 0;
+
+ case VBNV_DEBUG_RESET_MODE:
+ *dest = (raw[BOOT_OFFSET] & BOOT_DEBUG_RESET_MODE ? 1 : 0);
+ return 0;
+
+ case VBNV_TRY_B_COUNT:
+ *dest = raw[BOOT_OFFSET] & BOOT_TRY_B_COUNT;
+ return 0;
+
+ case VBNV_RECOVERY_REQUEST:
+ *dest = raw[RECOVERY_OFFSET];
+ return 0;
+
+ case VBNV_LOCALIZATION_INDEX:
+ *dest = raw[LOCALIZATION_OFFSET];
+ return 0;
+
+ case VBNV_KERNEL_FIELD:
+ *dest = (raw[KERNEL_FIELD_OFFSET]
+ | (raw[KERNEL_FIELD_OFFSET + 1] << 8)
+ | (raw[KERNEL_FIELD_OFFSET + 2] << 16)
+ | (raw[KERNEL_FIELD_OFFSET + 3] << 24));
+ return 0;
+
+ default:
+ return 1;
+ }
+}
+
+
+int VbNvSet(VbNvContext* context, VbNvParam param, uint32_t value) {
+ uint8_t* raw = context->raw;
+ uint32_t current;
+
+ /* If we're not changing the value, we don't need to regenerate the CRC. */
+ if (0 == VbNvGet(context, param, &current) && current == value)
+ return 0;
+
+ switch (param) {
+ case VBNV_FIRMWARE_SETTINGS_RESET:
+ if (value)
+ raw[HEADER_OFFSET] |= HEADER_FIRMWARE_SETTINGS_RESET;
+ else
+ raw[HEADER_OFFSET] &= ~HEADER_FIRMWARE_SETTINGS_RESET;
+ break;
+
+ case VBNV_KERNEL_SETTINGS_RESET:
+ if (value)
+ raw[HEADER_OFFSET] |= HEADER_KERNEL_SETTINGS_RESET;
+ else
+ raw[HEADER_OFFSET] &= ~HEADER_KERNEL_SETTINGS_RESET;
+ break;
+
+ case VBNV_DEBUG_RESET_MODE:
+ if (value)
+ raw[BOOT_OFFSET] |= BOOT_DEBUG_RESET_MODE;
+ else
+ raw[BOOT_OFFSET] &= BOOT_DEBUG_RESET_MODE;
+ break;
+
+ case VBNV_TRY_B_COUNT:
+ raw[BOOT_OFFSET] &= ~BOOT_TRY_B_COUNT;
+ raw[BOOT_OFFSET] |= (uint8_t)(value & BOOT_TRY_B_COUNT);
+ break;
+
+ case VBNV_RECOVERY_REQUEST:
+ raw[RECOVERY_OFFSET] = (uint8_t)value;
+ break;
+
+ case VBNV_LOCALIZATION_INDEX:
+ raw[LOCALIZATION_OFFSET] = (uint8_t)value;
+ break;
+
+ case VBNV_KERNEL_FIELD:
+ raw[KERNEL_FIELD_OFFSET] = (uint8_t)(value);
+ raw[KERNEL_FIELD_OFFSET + 1] = (uint8_t)(value >> 8);
+ raw[KERNEL_FIELD_OFFSET + 2] = (uint8_t)(value >> 16);
+ raw[KERNEL_FIELD_OFFSET + 3] = (uint8_t)(value >> 24);
+ break;
+
+ default:
+ return 1;
+ }
+
+ /* Need to regenerate CRC, since the value changed. */
+ context->regenerate_crc = 1;
+ return 0;
+}
diff --git a/tests/Makefile b/tests/Makefile
index 17da312b..e97bbe11 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -18,6 +18,7 @@ TEST_NAMES = cgptlib_test \
vboot_common_tests \
vboot_common2_tests \
vboot_common3_tests \
+ vboot_nvstorage_test
TEST_BINS = $(addprefix ${BUILD_ROOT}/,$(TEST_NAMES))
diff --git a/tests/vboot_nvstorage_test.c b/tests/vboot_nvstorage_test.c
new file mode 100644
index 00000000..8f62317b
--- /dev/null
+++ b/tests/vboot_nvstorage_test.c
@@ -0,0 +1,210 @@
+/* Copyright (c) 2010 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 firmware image library.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "test_common.h"
+#include "vboot_common.h"
+#include "vboot_nvstorage.h"
+
+#if 0
+
+/* Initialize the NV storage library. This must be called before any
+ * other functions in this library. Returns 0 if success, non-zero if
+ * error. */
+int VbNvSetup(VbNvContext* context);
+
+/* Clean up and flush changes back to the raw data. This must be
+ * called after other functions in this library. Caller must check
+ * context.raw_changed after calling tis function. Returns 0 if
+ * success, non-zero if error. */
+int VbNvTeardown(VbNvContext* context);
+
+/* Read a NV storage parameter into *dest. Returns 0 if success,
+ * non-zero if error. */
+int VbNvGet(VbNvContext* context, VbNvParam param, uint32_t* dest);
+
+/* Set a NV storage param to a new value. Returns 0 if success,
+ * non-zero if error. */
+int VbNvSet(VbNvContext* context, VbNvParam param, uint32_t value);
+
+typedef struct VbNvContext {
+ /* Raw NV data. Caller must fill this before calling VbNvStart(). */
+ uint8_t raw[NV_BLOCK_SIZE];
+ /* Flag indicating whether raw data has changed. Set by VbNvStop() if
+ * the raw data has changed and needs to be stored to the underlying
+ * non-volatile data store. */
+ int raw_changed;
+
+ /* Internal data for NV storage routines. Caller should not touch
+ * these fields. */
+ int regenerate_crc;
+
+} VbNvContext;
+
+#endif
+
+static void VbStorageTest(void) {
+
+ VbNvContext c;
+ uint8_t goodcrc;
+ uint32_t data;
+
+ memset(&c, 0xA6, sizeof(c));
+
+ /* Open with invalid data should set defaults */
+ TEST_EQ(VbNvSetup(&c), 0, "VbNvSetup()");
+ TEST_EQ(c.raw[0], 0x70, "VbNvSetup() reset header byte");
+ /* Close then regenerates the CRC */
+ TEST_EQ(VbNvTeardown(&c), 0, "VbNvTeardown()");
+ TEST_NEQ(c.raw[15], 0, "VbNvTeardown() CRC");
+ TEST_EQ(c.raw_changed, 1, "VbNvTeardown() changed");
+ goodcrc = c.raw[15];
+ /* Another open-close pair should not cause further changes */
+ VbNvSetup(&c);
+ VbNvTeardown(&c);
+ TEST_EQ(c.raw_changed, 0, "VbNvTeardown() didn't change");
+ TEST_EQ(c.raw[15], goodcrc, "VbNvTeardown() CRC same");
+
+ /* Perturbing the header should force defaults */
+ c.raw[0] ^= 0x40;
+ VbNvSetup(&c);
+ TEST_EQ(c.raw[0], 0x70, "VbNvSetup() reset header byte again");
+ /* Close then regenerates the CRC */
+ VbNvTeardown(&c);
+ TEST_EQ(c.raw_changed, 1, "VbNvTeardown() changed again");
+ TEST_EQ(c.raw[15], goodcrc, "VbNvTeardown() CRC same again");
+
+ /* So should perturbing some other byte */
+ TEST_EQ(c.raw[11], 0, "Kernel byte starts at 0");
+ c.raw[11] = 12;
+ VbNvSetup(&c);
+ TEST_EQ(c.raw[11], 0, "VbNvSetup() reset kernel byte");
+ /* Close then regenerates the CRC */
+ VbNvTeardown(&c);
+ TEST_EQ(c.raw_changed, 1, "VbNvTeardown() changed again");
+ TEST_EQ(c.raw[15], goodcrc, "VbNvTeardown() CRC same again");
+
+ /* Clear the kernel and firmware flags */
+ VbNvSetup(&c);
+ TEST_EQ(VbNvGet(&c, VBNV_FIRMWARE_SETTINGS_RESET, &data), 0,
+ "Get firmware settings reset");
+ TEST_EQ(data, 1, "Firmware settings are reset");
+ TEST_EQ(VbNvSet(&c, VBNV_FIRMWARE_SETTINGS_RESET, 0), 0,
+ "Clear firmware settings reset");
+ VbNvGet(&c, VBNV_FIRMWARE_SETTINGS_RESET, &data);
+ TEST_EQ(data, 0, "Firmware settings are clear");
+
+ TEST_EQ(VbNvGet(&c, VBNV_KERNEL_SETTINGS_RESET, &data), 0,
+ "Get kernel settings reset");
+ TEST_EQ(data, 1, "Kernel settings are reset");
+ TEST_EQ(VbNvSet(&c, VBNV_KERNEL_SETTINGS_RESET, 0), 0,
+ "Clear kernel settings reset");
+ VbNvGet(&c, VBNV_KERNEL_SETTINGS_RESET, &data);
+ TEST_EQ(data, 0, "Kernel settings are clear");
+ TEST_EQ(c.raw[0], 0x40, "Header byte now just has the header bit");
+ VbNvTeardown(&c);
+ /* That should have changed the CRC */
+ TEST_NEQ(c.raw[15], goodcrc, "VbNvTeardown() CRC changed due to flags clear");
+
+ /* Test debug reset mode field */
+ VbNvSetup(&c);
+ TEST_EQ(VbNvGet(&c, VBNV_DEBUG_RESET_MODE, &data), 0,
+ "Get debug reset mode");
+ TEST_EQ(data, 0, "Debug reset mode default");
+ TEST_EQ(VbNvSet(&c, VBNV_DEBUG_RESET_MODE, 1), 0,
+ "Set debug reset mode");
+ VbNvGet(&c, VBNV_DEBUG_RESET_MODE, &data);
+ TEST_EQ(data, 1, "Debug reset mode set");
+ VbNvTeardown(&c);
+
+ /* Test try B count */
+ VbNvSetup(&c);
+ TEST_EQ(VbNvGet(&c, VBNV_TRY_B_COUNT, &data), 0, "Get try b count");
+ TEST_EQ(data, 0, "Try b count default");
+ TEST_EQ(VbNvSet(&c, VBNV_TRY_B_COUNT, 6), 0, "Set try b count");
+ VbNvGet(&c, VBNV_TRY_B_COUNT, &data);
+ TEST_EQ(data, 6, "Try b count set");
+ VbNvSet(&c, VBNV_TRY_B_COUNT, 15);
+ VbNvGet(&c, VBNV_TRY_B_COUNT, &data);
+ TEST_EQ(data, 15, "Try b count set 2");
+ VbNvTeardown(&c);
+
+ /* Test recovery request */
+ VbNvSetup(&c);
+ TEST_EQ(VbNvGet(&c, VBNV_RECOVERY_REQUEST, &data), 0, "Get recovery request");
+ TEST_EQ(data, 0, "Default recovery request");
+ TEST_EQ(VbNvSet(&c, VBNV_RECOVERY_REQUEST, 0x42), 0, "Set recovery request");
+ VbNvGet(&c, VBNV_RECOVERY_REQUEST, &data);
+ TEST_EQ(data, 0x42, "Set recovery request");
+ VbNvSet(&c, VBNV_RECOVERY_REQUEST, 0xED);
+ VbNvGet(&c, VBNV_RECOVERY_REQUEST, &data);
+ TEST_EQ(data, 0xED, "Set recovery request 2");
+ VbNvTeardown(&c);
+
+ /* Test localization index */
+ VbNvSetup(&c);
+ TEST_EQ(VbNvGet(&c, VBNV_LOCALIZATION_INDEX, &data), 0,
+ "Get localization index");
+ TEST_EQ(data, 0, "Default localization index");
+ TEST_EQ(VbNvSet(&c, VBNV_LOCALIZATION_INDEX, 0x69), 0,
+ "Set localization index");
+ VbNvGet(&c, VBNV_LOCALIZATION_INDEX, &data);
+ TEST_EQ(data, 0x69, "Set localization index");
+ VbNvSet(&c, VBNV_LOCALIZATION_INDEX, 0xB0);
+ VbNvGet(&c, VBNV_LOCALIZATION_INDEX, &data);
+ TEST_EQ(data, 0xB0, "Set localization index 2");
+ VbNvTeardown(&c);
+
+ /* Test kernel field */
+ VbNvSetup(&c);
+ TEST_EQ(VbNvGet(&c, VBNV_KERNEL_FIELD, &data), 0, "Get kernel field");
+ TEST_EQ(data, 0, "Default kernel field");
+ TEST_EQ(VbNvSet(&c, VBNV_KERNEL_FIELD, 0x12345678), 0, "Set kernel field");
+ VbNvGet(&c, VBNV_KERNEL_FIELD, &data);
+ TEST_EQ(data, 0x12345678, "Set kernel field");
+ VbNvSet(&c, VBNV_KERNEL_FIELD, 0xFEDCBA98);
+ VbNvGet(&c, VBNV_KERNEL_FIELD, &data);
+ TEST_EQ(data, 0xFEDCBA98, "Set kernel field 2");
+ VbNvTeardown(&c);
+
+ /* None of those changes should have caused a reset to defaults */
+ VbNvSetup(&c);
+ VbNvGet(&c, VBNV_FIRMWARE_SETTINGS_RESET, &data);
+ TEST_EQ(data, 0, "Firmware settings are still clear");
+ VbNvGet(&c, VBNV_KERNEL_SETTINGS_RESET, &data);
+ TEST_EQ(data, 0, "Kernel settings are still clear");
+ VbNvTeardown(&c);
+
+ /* Verify writing identical settings doesn't cause the CRC to regenerate */
+ VbNvSetup(&c);
+ TEST_EQ(c.regenerate_crc, 0, "No regen CRC on open");
+ VbNvSet(&c, VBNV_DEBUG_RESET_MODE, 1);
+ VbNvSet(&c, VBNV_RECOVERY_REQUEST, 0xED);
+ VbNvSet(&c, VBNV_LOCALIZATION_INDEX, 0xB0);
+ VbNvSet(&c, VBNV_KERNEL_FIELD, 0xFEDCBA98);
+ TEST_EQ(c.regenerate_crc, 0, "No regen CRC if data not changed");
+ VbNvTeardown(&c);
+ TEST_EQ(c.raw_changed, 0, "No raw change if data not changed");
+}
+
+
+/* disable MSVC warnings on unused arguments */
+__pragma(warning (disable: 4100))
+
+int main(int argc, char* argv[]) {
+ int error_code = 0;
+
+ VbStorageTest();
+
+ if (!gTestSuccess)
+ error_code = 255;
+
+ return error_code;
+}