blob: e96a17fee1da9d3d34951f606df3b46b2a5a30ee [file] [log] [blame]
/* 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_MASK 0x0F
#define RECOVERY_OFFSET 2
#define LOCALIZATION_OFFSET 3
#define DEV_FLAGS_OFFSET 4
#define DEV_BOOT_USB_MASK 0x01
#define DEV_BOOT_SIGNED_ONLY_MASK 0x02
#define FIRMWARE_FLAGS_OFFSET 5
#define FIRMWARE_TEST_ERR_FUNC_MASK 0x38
#define FIRMWARE_TEST_ERR_FUNC_SHIFT 3
#define FIRMWARE_TEST_ERR_NUM_MASK 0x07
#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, VBNV_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_MASK;
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;
case VBNV_TEST_ERROR_FUNC:
*dest = (raw[FIRMWARE_FLAGS_OFFSET] & FIRMWARE_TEST_ERR_FUNC_MASK)
>> FIRMWARE_TEST_ERR_FUNC_SHIFT;
return 0;
case VBNV_TEST_ERROR_NUM:
*dest = raw[FIRMWARE_FLAGS_OFFSET] & FIRMWARE_TEST_ERR_NUM_MASK;
return 0;
case VBNV_DEV_BOOT_USB:
*dest = (raw[DEV_FLAGS_OFFSET] & DEV_BOOT_USB_MASK ? 1 : 0);
return 0;
case VBNV_DEV_BOOT_SIGNED_ONLY:
*dest = (raw[DEV_FLAGS_OFFSET] & DEV_BOOT_SIGNED_ONLY_MASK ? 1 : 0);
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:
/* Clip to valid range. */
if (value > BOOT_TRY_B_COUNT_MASK)
value = BOOT_TRY_B_COUNT_MASK;
raw[BOOT_OFFSET] &= ~BOOT_TRY_B_COUNT_MASK;
raw[BOOT_OFFSET] |= (uint8_t)value;
break;
case VBNV_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 = VBNV_RECOVERY_LEGACY;
raw[RECOVERY_OFFSET] = (uint8_t)value;
break;
case VBNV_LOCALIZATION_INDEX:
/* Map values outside the valid range to the default index. */
if (value > 0xFF)
value = 0;
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;
case VBNV_TEST_ERROR_FUNC:
raw[FIRMWARE_FLAGS_OFFSET] &= ~FIRMWARE_TEST_ERR_FUNC_MASK;
raw[FIRMWARE_FLAGS_OFFSET] |= (value << FIRMWARE_TEST_ERR_FUNC_SHIFT)
& FIRMWARE_TEST_ERR_FUNC_MASK;
break;
case VBNV_TEST_ERROR_NUM:
raw[FIRMWARE_FLAGS_OFFSET] &= ~FIRMWARE_TEST_ERR_NUM_MASK;
raw[FIRMWARE_FLAGS_OFFSET] |= (value & FIRMWARE_TEST_ERR_NUM_MASK);
break;
case VBNV_DEV_BOOT_USB:
if (value)
raw[DEV_FLAGS_OFFSET] |= DEV_BOOT_USB_MASK;
else
raw[DEV_FLAGS_OFFSET] &= ~DEV_BOOT_USB_MASK;
break;
case VBNV_DEV_BOOT_SIGNED_ONLY:
if (value)
raw[DEV_FLAGS_OFFSET] |= DEV_BOOT_SIGNED_ONLY_MASK;
else
raw[DEV_FLAGS_OFFSET] &= ~DEV_BOOT_SIGNED_ONLY_MASK;
break;
default:
return 1;
}
/* Need to regenerate CRC, since the value changed. */
context->regenerate_crc = 1;
return 0;
}