diff options
Diffstat (limited to 'firmware/2lib/2nvstorage.c')
-rw-r--r-- | firmware/2lib/2nvstorage.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/firmware/2lib/2nvstorage.c b/firmware/2lib/2nvstorage.c new file mode 100644 index 00000000..3bfe151c --- /dev/null +++ b/firmware/2lib/2nvstorage.c @@ -0,0 +1,323 @@ +/* Copyright (c) 2014 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 "2sysincludes.h" +#include "2common.h" +#include "2crc8.h" +#include "2misc.h" +#include "2nvstorage.h" + +/* + * Constants for NV storage. We use this rather than structs and bitfields so + * the data format is consistent across platforms and compilers. Total NV + * storage size is VB2_NVDATA_SIZE = 16 bytes. + */ + +enum vb2_nv_offset { + VB2_NV_OFFS_HEADER = 0, + VB2_NV_OFFS_BOOT = 1, + VB2_NV_OFFS_RECOVERY = 2, + VB2_NV_OFFS_LOCALIZATION = 3, + VB2_NV_OFFS_DEV = 4, + VB2_NV_OFFS_TPM = 5, + VB2_NV_OFFS_RECOVERY_SUBCODE = 6, + VB2_NV_OFFS_BOOT2 = 7, + /* Offsets 7-10 are currently unused */ + VB2_NV_OFFS_KERNEL = 11, /* 11-14; field is 32 bits */ + /* CRC must be last field */ + VB2_NV_OFFS_CRC = 15 +}; + +/* Fields in VB2_NV_OFFS_HEADER (unused = 0x0f) */ +#define VB2_NV_HEADER_KERNEL_SETTINGS_RESET 0x10 +#define VB2_NV_HEADER_FW_SETTINGS_RESET 0x20 +#define VB2_NV_HEADER_SIGNATURE 0x40 +#define VB2_NV_HEADER_MASK 0xc0 + +/* Fields in VB2_NV_OFFS_BOOT */ +#define VB2_NV_BOOT_TRY_COUNT_MASK 0x0f +#define VB2_NV_BOOT_TRY_NEXT 0x10 +#define VB2_NV_BOOT_OPROM_NEEDED 0x20 +#define VB2_NV_BOOT_DISABLE_DEV 0x40 +#define VB2_NV_BOOT_DEBUG_RESET 0x80 + +/* Fields in VB2_NV_OFFS_BOOT2 (unused = 0xf8) */ +#define VB2_NV_BOOT2_RESULT_MASK 0x03 +#define VB2_NV_BOOT2_TRIED 0x04 + +/* Fields in VB2_NV_OFFS_DEV (unused = 0xf8) */ +#define VB2_NV_DEV_FLAG_USB 0x01 +#define VB2_NV_DEV_FLAG_SIGNED_ONLY 0x02 +#define VB2_NV_DEV_FLAG_LEGACY 0x04 + +/* Fields in VB2_NV_OFFS_TPM (unused = 0xfc) */ +#define VB2_NV_TPM_CLEAR_OWNER_REQUEST 0x01 +#define VB2_NV_TPM_CLEAR_OWNER_DONE 0x02 + +static void vb2_nv_regen_crc(struct vb2_context *ctx) +{ + ctx->nvdata[VB2_NV_OFFS_CRC] = vb2_crc8(ctx->nvdata, VB2_NV_OFFS_CRC); + ctx->flags |= VB2_CONTEXT_NVDATA_CHANGED; +} + +/** + * Check the CRC of the non-volatile storage context. + * + * Use this if reading from non-volatile storage may be flaky, and you want to + * retry reading it several times. + * + * This may be called before vb2_context_init(). + * + * @param ctx Context pointer + * @return VB2_SUCCESS, or non-zero error code if error. + */ +int vb2_nv_check_crc(const struct vb2_context *ctx) +{ + const uint8_t *p = ctx->nvdata; + + /* Check header */ + if (VB2_NV_HEADER_SIGNATURE != + (p[VB2_NV_OFFS_HEADER] & VB2_NV_HEADER_MASK)) + return VB2_ERROR_UNKNOWN; + + /* Check CRC */ + if (vb2_crc8(p, VB2_NV_OFFS_CRC) != p[VB2_NV_OFFS_CRC]) + return VB2_ERROR_UNKNOWN; + + return VB2_SUCCESS; +} + +void vb2_nv_init(struct vb2_context *ctx) +{ + struct vb2_shared_data *sd = vb2_get_sd(ctx); + uint8_t *p = ctx->nvdata; + + /* Check data for consistency */ + if (vb2_nv_check_crc(ctx) != VB2_SUCCESS) { + /* Data is inconsistent (bad CRC or header); reset defaults */ + memset(p, 0, VB2_NVDATA_SIZE); + p[VB2_NV_OFFS_HEADER] = (VB2_NV_HEADER_SIGNATURE | + VB2_NV_HEADER_FW_SETTINGS_RESET | + VB2_NV_HEADER_KERNEL_SETTINGS_RESET); + + /* Regenerate CRC */ + vb2_nv_regen_crc(ctx); + + /* Set status flag */ + sd->status |= VB2_SD_STATUS_NV_REINIT; + // TODO: unit test for status flag being set + } + + sd->status |= VB2_SD_STATUS_NV_INIT; +} + +/* Macro for vb2_nv_get() single-bit settings to reduce duplicate code. */ +#define GETBIT(offs, mask) (p[offs] & mask ? 1 : 0) + +uint32_t vb2_nv_get(struct vb2_context *ctx, enum vb2_nv_param param) +{ + const uint8_t *p = ctx->nvdata; + + /* + * TODO: We could reduce the binary size for this code by #ifdef'ing + * out the params not used by firmware verification. + */ + switch (param) { + case VB2_NV_FIRMWARE_SETTINGS_RESET: + return GETBIT(VB2_NV_OFFS_HEADER, + VB2_NV_HEADER_FW_SETTINGS_RESET); + + case VB2_NV_KERNEL_SETTINGS_RESET: + return GETBIT(VB2_NV_OFFS_HEADER, + VB2_NV_HEADER_KERNEL_SETTINGS_RESET); + + case VB2_NV_DEBUG_RESET_MODE: + return GETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_DEBUG_RESET); + + case VB2_NV_TRY_NEXT: + return GETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_TRY_NEXT); + + case VB2_NV_TRY_COUNT: + return p[VB2_NV_OFFS_BOOT] & VB2_NV_BOOT_TRY_COUNT_MASK; + + case VB2_NV_FW_TRIED: + return GETBIT(VB2_NV_OFFS_BOOT2, VB2_NV_BOOT2_TRIED); + + case VB2_NV_FW_RESULT: + return p[VB2_NV_OFFS_BOOT2] & VB2_NV_BOOT2_RESULT_MASK; + + case VB2_NV_RECOVERY_REQUEST: + return p[VB2_NV_OFFS_RECOVERY]; + + case VB2_NV_RECOVERY_SUBCODE: + return p[VB2_NV_OFFS_RECOVERY_SUBCODE]; + + case VB2_NV_LOCALIZATION_INDEX: + return p[VB2_NV_OFFS_LOCALIZATION]; + + case VB2_NV_KERNEL_FIELD: + return (p[VB2_NV_OFFS_KERNEL] + | (p[VB2_NV_OFFS_KERNEL + 1] << 8) + | (p[VB2_NV_OFFS_KERNEL + 2] << 16) + | (p[VB2_NV_OFFS_KERNEL + 3] << 24)); + + case VB2_NV_DEV_BOOT_USB: + return GETBIT(VB2_NV_OFFS_DEV, VB2_NV_DEV_FLAG_USB); + + case VB2_NV_DEV_BOOT_LEGACY: + return GETBIT(VB2_NV_OFFS_DEV, VB2_NV_DEV_FLAG_LEGACY); + + case VB2_NV_DEV_BOOT_SIGNED_ONLY: + return GETBIT(VB2_NV_OFFS_DEV, VB2_NV_DEV_FLAG_SIGNED_ONLY); + + case VB2_NV_DISABLE_DEV_REQUEST: + return GETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_DISABLE_DEV); + + case VB2_NV_OPROM_NEEDED: + return GETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_OPROM_NEEDED); + + case VB2_NV_CLEAR_TPM_OWNER_REQUEST: + return GETBIT(VB2_NV_OFFS_TPM, VB2_NV_TPM_CLEAR_OWNER_REQUEST); + + case VB2_NV_CLEAR_TPM_OWNER_DONE: + return GETBIT(VB2_NV_OFFS_TPM, VB2_NV_TPM_CLEAR_OWNER_DONE); + } + + /* + * Put default return outside the switch() instead of in default:, so + * that adding a new param will cause a compiler warning. + */ + return 0; +} + +#undef GETBIT + +/* Macro for vb2_nv_set() single-bit settings to reduce duplicate code. */ +#define SETBIT(offs, mask) \ + { if (value) p[offs] |= mask; else p[offs] &= ~mask; } + +void vb2_nv_set(struct vb2_context *ctx, + enum vb2_nv_param param, + uint32_t value) +{ + uint8_t *p = ctx->nvdata; + + /* If not changing the value, don't regenerate the CRC. */ + if (vb2_nv_get(ctx, param) == value) + return; + + /* + * TODO: We could reduce the binary size for this code by #ifdef'ing + * out the params not used by firmware verification. + */ + switch (param) { + case VB2_NV_FIRMWARE_SETTINGS_RESET: + SETBIT(VB2_NV_OFFS_HEADER, VB2_NV_HEADER_FW_SETTINGS_RESET); + break; + + case VB2_NV_KERNEL_SETTINGS_RESET: + SETBIT(VB2_NV_OFFS_HEADER, VB2_NV_HEADER_KERNEL_SETTINGS_RESET); + break; + + case VB2_NV_DEBUG_RESET_MODE: + SETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_DEBUG_RESET); + break; + + case VB2_NV_TRY_NEXT: + SETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_TRY_NEXT); + break; + + case VB2_NV_TRY_COUNT: + /* Clip to valid range. */ + if (value > VB2_NV_BOOT_TRY_COUNT_MASK) + value = VB2_NV_BOOT_TRY_COUNT_MASK; + + p[VB2_NV_OFFS_BOOT] &= ~VB2_NV_BOOT_TRY_COUNT_MASK; + p[VB2_NV_OFFS_BOOT] |= (uint8_t)value; + break; + + case VB2_NV_FW_TRIED: + SETBIT(VB2_NV_OFFS_BOOT2, VB2_NV_BOOT2_TRIED); + break; + + case VB2_NV_FW_RESULT: + /* Map out of range values to unknown */ + if (value > VB2_NV_BOOT2_RESULT_MASK) + value = VB2_FW_RESULT_UNKNOWN; + + p[VB2_NV_OFFS_BOOT2] &= ~VB2_NV_BOOT2_RESULT_MASK; + p[VB2_NV_OFFS_BOOT2] |= (uint8_t)value; + break; + + case VB2_NV_RECOVERY_REQUEST: + /* + * Map values outside the valid range to the legacy reason, + * since we can't determine if we're called from kernel or user + * mode. + */ + if (value > 0xff) + value = VB2_RECOVERY_LEGACY; + p[VB2_NV_OFFS_RECOVERY] = (uint8_t)value; + break; + + case VB2_NV_RECOVERY_SUBCODE: + p[VB2_NV_OFFS_RECOVERY_SUBCODE] = (uint8_t)value; + break; + + case VB2_NV_LOCALIZATION_INDEX: + /* Map values outside the valid range to the default index. */ + if (value > 0xFF) + value = 0; + p[VB2_NV_OFFS_LOCALIZATION] = (uint8_t)value; + break; + + case VB2_NV_KERNEL_FIELD: + p[VB2_NV_OFFS_KERNEL] = (uint8_t)(value); + p[VB2_NV_OFFS_KERNEL + 1] = (uint8_t)(value >> 8); + p[VB2_NV_OFFS_KERNEL + 2] = (uint8_t)(value >> 16); + p[VB2_NV_OFFS_KERNEL + 3] = (uint8_t)(value >> 24); + break; + + case VB2_NV_DEV_BOOT_USB: + SETBIT(VB2_NV_OFFS_DEV, VB2_NV_DEV_FLAG_USB); + break; + + case VB2_NV_DEV_BOOT_LEGACY: + SETBIT(VB2_NV_OFFS_DEV, VB2_NV_DEV_FLAG_LEGACY); + break; + + case VB2_NV_DEV_BOOT_SIGNED_ONLY: + SETBIT(VB2_NV_OFFS_DEV, VB2_NV_DEV_FLAG_SIGNED_ONLY); + break; + + case VB2_NV_DISABLE_DEV_REQUEST: + SETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_DISABLE_DEV); + break; + + case VB2_NV_OPROM_NEEDED: + SETBIT(VB2_NV_OFFS_BOOT, VB2_NV_BOOT_OPROM_NEEDED); + break; + + case VB2_NV_CLEAR_TPM_OWNER_REQUEST: + SETBIT(VB2_NV_OFFS_TPM, VB2_NV_TPM_CLEAR_OWNER_REQUEST); + break; + + case VB2_NV_CLEAR_TPM_OWNER_DONE: + SETBIT(VB2_NV_OFFS_TPM, VB2_NV_TPM_CLEAR_OWNER_DONE); + break; + } + + /* + * Note there is no default case. This causes a compiler warning if + * a new param is added to the enum without adding support here. + */ + + /* Need to regenerate CRC, since the value changed. */ + vb2_nv_regen_crc(ctx); +} + +#undef SETBIT |