diff options
author | Randall Spangler <rspangler@chromium.org> | 2011-02-17 15:57:39 -0800 |
---|---|---|
committer | Randall Spangler <rspangler@chromium.org> | 2011-02-17 15:57:39 -0800 |
commit | b944534edd3799b3353f73bcb8ee90161d640c2b (patch) | |
tree | cb30ae5a006d35d536ad0e51073f2570ded483ff | |
parent | a7209ee2de570a1404e6df247df4a68c72d16245 (diff) | |
download | vboot-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/Makefile | 3 | ||||
-rw-r--r-- | firmware/include/vboot_nvstorage.h | 73 | ||||
-rw-r--r-- | firmware/lib/vboot_nvstorage.c | 186 | ||||
-rw-r--r-- | tests/Makefile | 1 | ||||
-rw-r--r-- | tests/vboot_nvstorage_test.c | 210 |
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, ¤t) && 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; +} |