/** @file

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

**/

#include <PiDxe.h>
#include <Protocol/FirmwareVolumeBlock.h>
#include <Library/PcdLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Guid/FirmwareFileSystem2.h>
#include <Guid/SystemNvDataGuid.h>
#include <Guid/NvVariableInfoGuid.h>
#include <Library/HobLib.h>

#define FVB_MEDIA_BLOCK_SIZE        0x1000

typedef struct {
  EFI_FIRMWARE_VOLUME_HEADER        FvInfo;
  EFI_FV_BLOCK_MAP_ENTRY            End[1];
} EFI_FVB2_MEDIA_INFO;

//
// This data structure contains a template of FV header which is used to restore
// Fv header if it's corrupted.
//
EFI_FVB2_MEDIA_INFO mFvbMediaInfo = {
  {
    {0,},           // ZeroVector[16]
    EFI_SYSTEM_NV_DATA_FV_GUID,
    0,
    EFI_FVH_SIGNATURE,
    0x0004feff,     // check PiFirmwareVolume.h for details on EFI_FVB_ATTRIBUTES_2
    sizeof (EFI_FIRMWARE_VOLUME_HEADER) + sizeof (EFI_FV_BLOCK_MAP_ENTRY),
    0,              // CheckSum which will be calucated dynamically.
    0,              // ExtHeaderOffset
    {0,},
    EFI_FVH_REVISION,
    {
      {
        0,
        FVB_MEDIA_BLOCK_SIZE,
      }
    }
  },
  {
    {
      0,
      0
    }
  }
};

/**
  Initialize the variable store

  @retval     EFI_SUCCESS if initialize the store success.

**/
EFI_STATUS
InitVariableStore (
  VOID
  )
{
  EFI_STATUS          Status;
  UINT32              NvStorageBase;
  UINT32              NvStorageSize;
  UINT32              NvVariableSize;
  UINT32              FtwWorkingSize;
  UINT32              FtwSpareSize;
  EFI_HOB_GUID_TYPE   *GuidHob;
  NV_VARIABLE_INFO    *NvVariableInfo;

  //
  // Find SPI flash variable hob
  //
  GuidHob = GetFirstGuidHob (&gNvVariableInfoGuid);
  if (GuidHob == NULL) {
    ASSERT (FALSE);
    return EFI_NOT_FOUND;
  }
  NvVariableInfo = (NV_VARIABLE_INFO *) GET_GUID_HOB_DATA (GuidHob);

  //
  // Get variable region base and size.
  //
  NvStorageSize = NvVariableInfo->VariableStoreSize;
  NvStorageBase = NvVariableInfo->VariableStoreBase;

  //
  // NvStorageBase needs to be 4KB aligned, NvStorageSize needs to be 8KB * n
  //
  if (((NvStorageBase & (SIZE_4KB - 1)) != 0) || ((NvStorageSize & (SIZE_8KB - 1)) != 0)) {
    return EFI_INVALID_PARAMETER;
  }

  FtwSpareSize   = NvStorageSize / 2;
  FtwWorkingSize = 0x2000;
  NvVariableSize = NvStorageSize / 2 - FtwWorkingSize;
  DEBUG ((DEBUG_INFO, "NvStorageBase:0x%x, NvStorageSize:0x%x\n", NvStorageBase, NvStorageSize));

  if (NvVariableSize >= 0x80000000) {
    return EFI_INVALID_PARAMETER;
  }
  Status = PcdSet32S(PcdFlashNvStorageVariableSize,   NvVariableSize);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet32S(PcdFlashNvStorageVariableBase,   NvStorageBase);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet64S(PcdFlashNvStorageVariableBase64, NvStorageBase);
  ASSERT_EFI_ERROR (Status);

  Status = PcdSet32S(PcdFlashNvStorageFtwWorkingSize, FtwWorkingSize);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet32S(PcdFlashNvStorageFtwWorkingBase, NvStorageBase + NvVariableSize);
  ASSERT_EFI_ERROR (Status);

  Status = PcdSet32S(PcdFlashNvStorageFtwSpareSize,   FtwSpareSize);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet32S(PcdFlashNvStorageFtwSpareBase,   NvStorageBase + FtwSpareSize);
  ASSERT_EFI_ERROR (Status);

  return EFI_SUCCESS;
}


/**
  Get a heathy FV header used for variable store recovery

  @retval     The FV header.

**/
EFI_FIRMWARE_VOLUME_HEADER *
GetFvHeaderTemplate (
  VOID
  )
{
  EFI_FIRMWARE_VOLUME_HEADER        *FvHeader;
  UINTN                             FvSize;

  FvSize = PcdGet32(PcdFlashNvStorageFtwSpareSize) * 2;
  FvHeader                        = &mFvbMediaInfo.FvInfo;
  FvHeader->FvLength              = FvSize;
  FvHeader->BlockMap[0].NumBlocks = (UINT32) (FvSize / FvHeader->BlockMap[0].Length);
  FvHeader->Checksum = 0;
  FvHeader->Checksum = CalculateCheckSum16 ((UINT16 *) FvHeader, FvHeader->HeaderLength);

  return FvHeader;
}

