/* Copyright (c) 2012 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 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), 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_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 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_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;
}
