/** @file
  Common variable non-volatile store routines.

Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "VariableNonVolatile.h"
#include "VariableParsing.h"

extern VARIABLE_MODULE_GLOBAL  *mVariableModuleGlobal;

/**
  Get non-volatile maximum variable size.

  @return Non-volatile maximum variable size.

**/
UINTN
GetNonVolatileMaxVariableSize (
  VOID
  )
{
  if (PcdGet32 (PcdHwErrStorageSize) != 0) {
    return MAX (MAX (PcdGet32 (PcdMaxVariableSize), PcdGet32 (PcdMaxAuthVariableSize)),
                PcdGet32 (PcdMaxHardwareErrorVariableSize));
  } else {
    return MAX (PcdGet32 (PcdMaxVariableSize), PcdGet32 (PcdMaxAuthVariableSize));
  }
}

/**
  Init emulated non-volatile variable store.

  @param[out] VariableStoreBase Output pointer to emulated non-volatile variable store base.

  @retval EFI_SUCCESS           Function successfully executed.
  @retval EFI_OUT_OF_RESOURCES  Fail to allocate enough memory resource.

**/
EFI_STATUS
InitEmuNonVolatileVariableStore (
  OUT EFI_PHYSICAL_ADDRESS  *VariableStoreBase
  )
{
  VARIABLE_STORE_HEADER *VariableStore;
  UINT32                VariableStoreLength;
  BOOLEAN               FullyInitializeStore;
  UINT32                HwErrStorageSize;

  FullyInitializeStore = TRUE;

  VariableStoreLength = PcdGet32 (PcdVariableStoreSize);
  ASSERT (sizeof (VARIABLE_STORE_HEADER) <= VariableStoreLength);

  //
  // Allocate memory for variable store.
  //
  if (PcdGet64 (PcdEmuVariableNvStoreReserved) == 0) {
    VariableStore = (VARIABLE_STORE_HEADER *) AllocateRuntimePool (VariableStoreLength);
    if (VariableStore == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  } else {
    //
    // A memory location has been reserved for the NV variable store.  Certain
    // platforms may be able to preserve a memory range across system resets,
    // thereby providing better NV variable emulation.
    //
    VariableStore =
      (VARIABLE_STORE_HEADER *)(VOID*)(UINTN)
        PcdGet64 (PcdEmuVariableNvStoreReserved);
    if ((VariableStore->Size == VariableStoreLength) &&
        (CompareGuid (&VariableStore->Signature, &gEfiAuthenticatedVariableGuid) ||
         CompareGuid (&VariableStore->Signature, &gEfiVariableGuid)) &&
        (VariableStore->Format == VARIABLE_STORE_FORMATTED) &&
        (VariableStore->State == VARIABLE_STORE_HEALTHY)) {
      DEBUG((
        DEBUG_INFO,
        "Variable Store reserved at %p appears to be valid\n",
        VariableStore
        ));
      FullyInitializeStore = FALSE;
    }
  }

  if (FullyInitializeStore) {
    SetMem (VariableStore, VariableStoreLength, 0xff);
    //
    // Use gEfiAuthenticatedVariableGuid for potential auth variable support.
    //
    CopyGuid (&VariableStore->Signature, &gEfiAuthenticatedVariableGuid);
    VariableStore->Size       = VariableStoreLength;
    VariableStore->Format     = VARIABLE_STORE_FORMATTED;
    VariableStore->State      = VARIABLE_STORE_HEALTHY;
    VariableStore->Reserved   = 0;
    VariableStore->Reserved1  = 0;
  }

  *VariableStoreBase = (EFI_PHYSICAL_ADDRESS) (UINTN) VariableStore;

  HwErrStorageSize = PcdGet32 (PcdHwErrStorageSize);

  //
  // Note that in EdkII variable driver implementation, Hardware Error Record type variable
  // is stored with common variable in the same NV region. So the platform integrator should
  // ensure that the value of PcdHwErrStorageSize is less than the value of
  // (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)).
  //
  ASSERT (HwErrStorageSize < (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)));

  mVariableModuleGlobal->CommonVariableSpace = ((UINTN) VariableStoreLength - sizeof (VARIABLE_STORE_HEADER) - HwErrStorageSize);
  mVariableModuleGlobal->CommonMaxUserVariableSpace = mVariableModuleGlobal->CommonVariableSpace;
  mVariableModuleGlobal->CommonRuntimeVariableSpace = mVariableModuleGlobal->CommonVariableSpace;

  return EFI_SUCCESS;
}

/**
  Init real non-volatile variable store.

  @param[out] VariableStoreBase Output pointer to real non-volatile variable store base.

  @retval EFI_SUCCESS           Function successfully executed.
  @retval EFI_OUT_OF_RESOURCES  Fail to allocate enough memory resource.
  @retval EFI_VOLUME_CORRUPTED  Variable Store or Firmware Volume for Variable Store is corrupted.

**/
EFI_STATUS
InitRealNonVolatileVariableStore (
  OUT EFI_PHYSICAL_ADDRESS              *VariableStoreBase
  )
{
  EFI_FIRMWARE_VOLUME_HEADER            *FvHeader;
  VARIABLE_STORE_HEADER                 *VariableStore;
  UINT32                                VariableStoreLength;
  EFI_HOB_GUID_TYPE                     *GuidHob;
  EFI_PHYSICAL_ADDRESS                  NvStorageBase;
  UINT8                                 *NvStorageData;
  UINT32                                NvStorageSize;
  FAULT_TOLERANT_WRITE_LAST_WRITE_DATA  *FtwLastWriteData;
  UINT32                                BackUpOffset;
  UINT32                                BackUpSize;
  UINT32                                HwErrStorageSize;
  UINT32                                MaxUserNvVariableSpaceSize;
  UINT32                                BoottimeReservedNvVariableSpaceSize;
  EFI_STATUS                            Status;
  VOID                                  *FtwProtocol;

  mVariableModuleGlobal->FvbInstance = NULL;

  //
  // Allocate runtime memory used for a memory copy of the FLASH region.
  // Keep the memory and the FLASH in sync as updates occur.
  //
  NvStorageSize = PcdGet32 (PcdFlashNvStorageVariableSize);
  NvStorageData = AllocateRuntimeZeroPool (NvStorageSize);
  if (NvStorageData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  NvStorageBase = NV_STORAGE_VARIABLE_BASE;
  ASSERT (NvStorageBase != 0);

  //
  // Copy NV storage data to the memory buffer.
  //
  CopyMem (NvStorageData, (UINT8 *) (UINTN) NvStorageBase, NvStorageSize);

  Status = GetFtwProtocol ((VOID **)&FtwProtocol);
  //
  // If FTW protocol has been installed, no need to check FTW last write data hob.
  //
  if (EFI_ERROR (Status)) {
    //
    // Check the FTW last write data hob.
    //
    GuidHob = GetFirstGuidHob (&gEdkiiFaultTolerantWriteGuid);
    if (GuidHob != NULL) {
      FtwLastWriteData = (FAULT_TOLERANT_WRITE_LAST_WRITE_DATA *) GET_GUID_HOB_DATA (GuidHob);
      if (FtwLastWriteData->TargetAddress == NvStorageBase) {
        DEBUG ((DEBUG_INFO, "Variable: NV storage is backed up in spare block: 0x%x\n", (UINTN) FtwLastWriteData->SpareAddress));
        //
        // Copy the backed up NV storage data to the memory buffer from spare block.
        //
        CopyMem (NvStorageData, (UINT8 *) (UINTN) (FtwLastWriteData->SpareAddress), NvStorageSize);
      } else if ((FtwLastWriteData->TargetAddress > NvStorageBase) &&
                 (FtwLastWriteData->TargetAddress < (NvStorageBase + NvStorageSize))) {
        //
        // Flash NV storage from the Offset is backed up in spare block.
        //
        BackUpOffset = (UINT32) (FtwLastWriteData->TargetAddress - NvStorageBase);
        BackUpSize = NvStorageSize - BackUpOffset;
        DEBUG ((DEBUG_INFO, "Variable: High partial NV storage from offset: %x is backed up in spare block: 0x%x\n", BackUpOffset, (UINTN) FtwLastWriteData->SpareAddress));
        //
        // Copy the partial backed up NV storage data to the memory buffer from spare block.
        //
        CopyMem (NvStorageData + BackUpOffset, (UINT8 *) (UINTN) FtwLastWriteData->SpareAddress, BackUpSize);
      }
    }
  }

  FvHeader = (EFI_FIRMWARE_VOLUME_HEADER *) NvStorageData;

  //
  // Check if the Firmware Volume is not corrupted
  //
  if ((FvHeader->Signature != EFI_FVH_SIGNATURE) || (!CompareGuid (&gEfiSystemNvDataFvGuid, &FvHeader->FileSystemGuid))) {
    FreePool (NvStorageData);
    DEBUG ((DEBUG_ERROR, "Firmware Volume for Variable Store is corrupted\n"));
    return EFI_VOLUME_CORRUPTED;
  }

  VariableStore = (VARIABLE_STORE_HEADER *) ((UINTN) FvHeader + FvHeader->HeaderLength);
  VariableStoreLength = NvStorageSize - FvHeader->HeaderLength;
  ASSERT (sizeof (VARIABLE_STORE_HEADER) <= VariableStoreLength);
  ASSERT (VariableStore->Size == VariableStoreLength);

  //
  // Check if the Variable Store header is not corrupted
  //
  if (GetVariableStoreStatus (VariableStore) != EfiValid) {
    FreePool (NvStorageData);
    DEBUG((DEBUG_ERROR, "Variable Store header is corrupted\n"));
    return EFI_VOLUME_CORRUPTED;
  }

  mNvFvHeaderCache = FvHeader;

  *VariableStoreBase = (EFI_PHYSICAL_ADDRESS) (UINTN) VariableStore;

  HwErrStorageSize = PcdGet32 (PcdHwErrStorageSize);
  MaxUserNvVariableSpaceSize = PcdGet32 (PcdMaxUserNvVariableSpaceSize);
  BoottimeReservedNvVariableSpaceSize = PcdGet32 (PcdBoottimeReservedNvVariableSpaceSize);

  //
  // Note that in EdkII variable driver implementation, Hardware Error Record type variable
  // is stored with common variable in the same NV region. So the platform integrator should
  // ensure that the value of PcdHwErrStorageSize is less than the value of
  // (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)).
  //
  ASSERT (HwErrStorageSize < (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)));
  //
  // Ensure that the value of PcdMaxUserNvVariableSpaceSize is less than the value of
  // (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)) - PcdGet32 (PcdHwErrStorageSize).
  //
  ASSERT (MaxUserNvVariableSpaceSize < (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER) - HwErrStorageSize));
  //
  // Ensure that the value of PcdBoottimeReservedNvVariableSpaceSize is less than the value of
  // (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)) - PcdGet32 (PcdHwErrStorageSize).
  //
  ASSERT (BoottimeReservedNvVariableSpaceSize < (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER) - HwErrStorageSize));

  mVariableModuleGlobal->CommonVariableSpace = ((UINTN) VariableStoreLength - sizeof (VARIABLE_STORE_HEADER) - HwErrStorageSize);
  mVariableModuleGlobal->CommonMaxUserVariableSpace = ((MaxUserNvVariableSpaceSize != 0) ? MaxUserNvVariableSpaceSize : mVariableModuleGlobal->CommonVariableSpace);
  mVariableModuleGlobal->CommonRuntimeVariableSpace = mVariableModuleGlobal->CommonVariableSpace - BoottimeReservedNvVariableSpaceSize;

  DEBUG ((
    DEBUG_INFO,
    "Variable driver common space: 0x%x 0x%x 0x%x\n",
    mVariableModuleGlobal->CommonVariableSpace,
    mVariableModuleGlobal->CommonMaxUserVariableSpace,
    mVariableModuleGlobal->CommonRuntimeVariableSpace
    ));

  //
  // The max NV variable size should be < (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)).
  //
  ASSERT (GetNonVolatileMaxVariableSize () < (VariableStoreLength - sizeof (VARIABLE_STORE_HEADER)));

  return EFI_SUCCESS;
}

/**
  Init non-volatile variable store.

  @retval EFI_SUCCESS           Function successfully executed.
  @retval EFI_OUT_OF_RESOURCES  Fail to allocate enough memory resource.
  @retval EFI_VOLUME_CORRUPTED  Variable Store or Firmware Volume for Variable Store is corrupted.

**/
EFI_STATUS
InitNonVolatileVariableStore (
  VOID
  )
{
  VARIABLE_HEADER                       *Variable;
  VARIABLE_HEADER                       *NextVariable;
  EFI_PHYSICAL_ADDRESS                  VariableStoreBase;
  UINTN                                 VariableSize;
  EFI_STATUS                            Status;

  if (PcdGetBool (PcdEmuVariableNvModeEnable)) {
    Status = InitEmuNonVolatileVariableStore (&VariableStoreBase);
    if (EFI_ERROR (Status)) {
      return Status;
    }
    mVariableModuleGlobal->VariableGlobal.EmuNvMode = TRUE;
    DEBUG ((DEBUG_INFO, "Variable driver will work at emulated non-volatile variable mode!\n"));
  } else {
    Status = InitRealNonVolatileVariableStore (&VariableStoreBase);
    if (EFI_ERROR (Status)) {
      return Status;
    }
    mVariableModuleGlobal->VariableGlobal.EmuNvMode = FALSE;
  }

  mVariableModuleGlobal->VariableGlobal.NonVolatileVariableBase = VariableStoreBase;
  mNvVariableCache = (VARIABLE_STORE_HEADER *) (UINTN) VariableStoreBase;
  mVariableModuleGlobal->VariableGlobal.AuthFormat = (BOOLEAN)(CompareGuid (&mNvVariableCache->Signature, &gEfiAuthenticatedVariableGuid));

  mVariableModuleGlobal->MaxVariableSize = PcdGet32 (PcdMaxVariableSize);
  mVariableModuleGlobal->MaxAuthVariableSize = ((PcdGet32 (PcdMaxAuthVariableSize) != 0) ? PcdGet32 (PcdMaxAuthVariableSize) : mVariableModuleGlobal->MaxVariableSize);

  //
  // Parse non-volatile variable data and get last variable offset.
  //
  Variable  = GetStartPointer (mNvVariableCache);
  while (IsValidVariableHeader (Variable, GetEndPointer (mNvVariableCache))) {
    NextVariable = GetNextVariablePtr (Variable, mVariableModuleGlobal->VariableGlobal.AuthFormat);
    VariableSize = (UINTN) NextVariable - (UINTN) Variable;
    if ((Variable->Attributes & (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_HARDWARE_ERROR_RECORD)) == (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_HARDWARE_ERROR_RECORD)) {
      mVariableModuleGlobal->HwErrVariableTotalSize += VariableSize;
    } else {
      mVariableModuleGlobal->CommonVariableTotalSize += VariableSize;
    }

    Variable = NextVariable;
  }
  mVariableModuleGlobal->NonVolatileLastVariableOffset = (UINTN) Variable - (UINTN) mNvVariableCache;

  return EFI_SUCCESS;
}
