/* Copyright (c) 2013 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 "crc8.h"
#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_DISABLE_DEV_REQUEST        0x40
#define BOOT_OPROM_NEEDED               0x20
#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 DEV_BOOT_LEGACY_MASK            0x04

#define TPM_FLAGS_OFFSET             5
#define TPM_CLEAR_OWNER_REQUEST         0x01
#define TPM_CLEAR_OWNER_DONE            0x02

#define RECOVERY_SUBCODE_OFFSET      6

#define KERNEL_FIELD_OFFSET         11
#define CRC_OFFSET                  15

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); 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_RECOVERY_SUBCODE:
		*dest = raw[RECOVERY_SUBCODE_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_DEV_BOOT_USB:
		*dest = (raw[DEV_FLAGS_OFFSET] & DEV_BOOT_USB_MASK ? 1 : 0);
		return 0;

	case VBNV_DEV_BOOT_LEGACY:
		*dest = (raw[DEV_FLAGS_OFFSET] & DEV_BOOT_LEGACY_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;

	case VBNV_DISABLE_DEV_REQUEST:
		*dest = (raw[BOOT_OFFSET] & BOOT_DISABLE_DEV_REQUEST ? 1 : 0);
		return 0;

	case VBNV_OPROM_NEEDED:
		*dest = (raw[BOOT_OFFSET] & BOOT_OPROM_NEEDED ? 1 : 0);
		return 0;

	case VBNV_CLEAR_TPM_OWNER_REQUEST:
		*dest = (raw[TPM_FLAGS_OFFSET] & TPM_CLEAR_OWNER_REQUEST ?
			 1 : 0);
		return 0;

	case VBNV_CLEAR_TPM_OWNER_DONE:
		*dest = (raw[TPM_FLAGS_OFFSET] & TPM_CLEAR_OWNER_DONE ? 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 not changing the value, don't 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_RECOVERY_SUBCODE:
		raw[RECOVERY_SUBCODE_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_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_LEGACY:
		if (value)
			raw[DEV_FLAGS_OFFSET] |= DEV_BOOT_LEGACY_MASK;
		else
			raw[DEV_FLAGS_OFFSET] &= ~DEV_BOOT_LEGACY_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;

	case VBNV_DISABLE_DEV_REQUEST:
		if (value)
			raw[BOOT_OFFSET] |= BOOT_DISABLE_DEV_REQUEST;
		else
			raw[BOOT_OFFSET] &= ~BOOT_DISABLE_DEV_REQUEST;
		break;

	case VBNV_OPROM_NEEDED:
		if (value)
			raw[BOOT_OFFSET] |= BOOT_OPROM_NEEDED;
		else
			raw[BOOT_OFFSET] &= ~BOOT_OPROM_NEEDED;
		break;

	case VBNV_CLEAR_TPM_OWNER_REQUEST:
		if (value)
			raw[TPM_FLAGS_OFFSET] |= TPM_CLEAR_OWNER_REQUEST;
		else
			raw[TPM_FLAGS_OFFSET] &= ~TPM_CLEAR_OWNER_REQUEST;
		break;

	case VBNV_CLEAR_TPM_OWNER_DONE:
		if (value)
			raw[TPM_FLAGS_OFFSET] |= TPM_CLEAR_OWNER_DONE;
		else
			raw[TPM_FLAGS_OFFSET] &= ~TPM_CLEAR_OWNER_DONE;
		break;

	default:
		return 1;
	}

	/* Need to regenerate CRC, since the value changed. */
	context->regenerate_crc = 1;
	return 0;
}
