/** @file
  Variable Cache HOB Library implementation file.

  This library builds a variable cache HOB consumed by the variable driver.

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2017 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/

#include <Library/DebugLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/HobLib.h>
#include <Library/VariableCacheHobLib.h>

#include <Guid/PeiVariableCacheHobGuid.h>
#include <Guid/VariableFormat.h>

/**

  Gets the pointer to the first variable header in given variable store area.

  @param VarStoreHeader  Pointer to the Variable Store Header.

  @return Pointer to the first variable header

**/
VARIABLE_HEADER *
GetStartPointer (
  IN VARIABLE_STORE_HEADER       *VarStoreHeader
  )
{
  //
  // The end of variable store
  //
  return (VARIABLE_HEADER *) HEADER_ALIGN (VarStoreHeader + 1);
}


/**
  This code gets the pointer to the last variable memory pointer byte.

  @param  VarStoreHeader  Pointer to the Variable Store Header.

  @return VARIABLE_HEADER* pointer to last unavailable Variable Header.

**/
VARIABLE_HEADER *
GetEndPointer (
  IN VARIABLE_STORE_HEADER       *VarStoreHeader
  )
{
  //
  // The end of variable store
  //
  return (VARIABLE_HEADER *) HEADER_ALIGN ((UINTN) VarStoreHeader + VarStoreHeader->Size);
}


/**
  This code checks if variable header is valid or not.

  @param  Variable  Pointer to the Variable Header.

  @retval TRUE      Variable header is valid.
  @retval FALSE     Variable header is not valid.

**/
BOOLEAN
IsValidVariableHeader (
  IN  VARIABLE_HEADER   *Variable
  )
{
  if (Variable == NULL || Variable->StartId != VARIABLE_DATA ) {
    return FALSE;
  }

  return TRUE;
}

/**
  This code gets the size of variable header.

  @param AuthFlag   Authenticated variable flag.

  @return Size of variable header in bytes in type UINTN.

**/
UINTN
GetVariableHeaderSize (
  IN  BOOLEAN       AuthFlag
  )
{
  UINTN Value;

  if (AuthFlag) {
    Value = sizeof (AUTHENTICATED_VARIABLE_HEADER);
  } else {
    Value = sizeof (VARIABLE_HEADER);
  }

  return Value;
}

/**
  This code gets the size of name of variable.

  @param  Variable  Pointer to the Variable Header.
  @param  AuthFlag  Authenticated variable flag.

  @return Size of variable in bytes in type UINTN.

**/
UINTN
NameSizeOfVariable (
  IN  VARIABLE_HEADER   *Variable,
  IN  BOOLEAN           AuthFlag
  )
{
  AUTHENTICATED_VARIABLE_HEADER *AuthVariable;

  AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *) Variable;
  if (AuthFlag) {
    if (AuthVariable->State == (UINT8) (-1) ||
       AuthVariable->DataSize == (UINT32) (-1) ||
       AuthVariable->NameSize == (UINT32) (-1) ||
       AuthVariable->Attributes == (UINT32) (-1)) {
      return 0;
    }
    return (UINTN) AuthVariable->NameSize;
  } else {
    if (Variable->State == (UINT8) (-1) ||
       Variable->DataSize == (UINT32) (-1) ||
       Variable->NameSize == (UINT32) (-1) ||
       Variable->Attributes == (UINT32) (-1)) {
      return 0;
    }
    return (UINTN) Variable->NameSize;
  }
}

/**
  This code sets the size of name of variable.

  @param[in] Variable   Pointer to the Variable Header.
  @param[in] NameSize   Name size to set.

**/
VOID
SetNameSizeOfVariable (
  IN VARIABLE_HEADER    *Variable,
  IN UINTN              NameSize,
  IN BOOLEAN            AuthFlag
  )
{
  AUTHENTICATED_VARIABLE_HEADER *AuthVariable;

  AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *) Variable;
  if (AuthFlag) {
    AuthVariable->NameSize = (UINT32) NameSize;
  } else {
    Variable->NameSize = (UINT32) NameSize;
  }
}

/**
  This code gets the size of data of variable.

  @param  Variable  Pointer to the Variable Header.
  @param  AuthFlag  Authenticated variable flag.

  @return Size of variable in bytes in type UINTN.

**/
UINTN
DataSizeOfVariable (
  IN  VARIABLE_HEADER   *Variable,
  IN  BOOLEAN           AuthFlag
  )
{
  AUTHENTICATED_VARIABLE_HEADER *AuthVariable;

  AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *) Variable;
  if (AuthFlag) {
    if (AuthVariable->State == (UINT8) (-1) ||
       AuthVariable->DataSize == (UINT32) (-1) ||
       AuthVariable->NameSize == (UINT32) (-1) ||
       AuthVariable->Attributes == (UINT32) (-1)) {
      return 0;
    }
    return (UINTN) AuthVariable->DataSize;
  } else {
    if (Variable->State == (UINT8) (-1) ||
       Variable->DataSize == (UINT32) (-1) ||
       Variable->NameSize == (UINT32) (-1) ||
       Variable->Attributes == (UINT32) (-1)) {
      return 0;
    }
    return (UINTN) Variable->DataSize;
  }
}

/**
  This code sets the size of variable data.

  @param[in] Variable   Pointer to the Variable Header.
  @param[in] DataSize   Data size to set.

**/
VOID
SetDataSizeOfVariable (
  IN VARIABLE_HEADER    *Variable,
  IN UINTN              DataSize,
  IN BOOLEAN            AuthFlag
  )
{
  AUTHENTICATED_VARIABLE_HEADER *AuthVariable;

  AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *) Variable;
  if (AuthFlag) {
    AuthVariable->DataSize = (UINT32) DataSize;
  } else {
    Variable->DataSize = (UINT32) DataSize;
  }
}

/**
  This code gets the pointer to the variable name.

  @param   Variable  Pointer to the Variable Header.
  @param   AuthFlag  Authenticated variable flag.

  @return  A CHAR16* pointer to Variable Name.

**/
CHAR16 *
GetVariableNamePtr (
  IN VARIABLE_HEADER    *Variable,
  IN BOOLEAN            AuthFlag
  )
{
  return (CHAR16 *) ((UINTN) Variable + GetVariableHeaderSize (AuthFlag));
}

/**
  This code gets the pointer to the variable guid.

  @param Variable   Pointer to the Variable Header.
  @param AuthFlag   Authenticated variable flag.

  @return A EFI_GUID* pointer to Vendor Guid.

**/
EFI_GUID *
GetVendorGuidPtr (
  IN VARIABLE_HEADER    *Variable,
  IN BOOLEAN            AuthFlag
  )
{
  AUTHENTICATED_VARIABLE_HEADER *AuthVariable;

  AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *) Variable;
  if (AuthFlag) {
    return &AuthVariable->VendorGuid;
  } else {
    return &Variable->VendorGuid;
  }
}

/**
  This code gets the pointer to the variable data.

  @param   Variable         Pointer to the Variable Header.
  @param   AuthFlag         Authenticated variable flag.

  @return  A UINT8* pointer to Variable Data.

**/
UINT8 *
GetVariableDataPtr (
  IN  VARIABLE_HEADER   *Variable,
  IN  BOOLEAN           AuthFlag
  )
{
  UINTN Value;

  //
  // Be careful about pad size for alignment
  //
  Value =  (UINTN) GetVariableNamePtr (Variable, AuthFlag);
  Value += NameSizeOfVariable (Variable, AuthFlag);
  Value += GET_PAD_SIZE (NameSizeOfVariable (Variable, AuthFlag));

  return (UINT8 *) Value;
}


/**
  This code gets the pointer to the next variable header.

  @param  StoreInfo         Pointer to variable store info structure.
  @param  Variable          Pointer to the Variable Header.

  @return  A VARIABLE_HEADER* pointer to next variable header.

**/
VARIABLE_HEADER *
GetNextVariablePtr (
  IN  VARIABLE_HEADER       *Variable,
  IN  BOOLEAN               AuthFlag
  )
{
  UINTN   Value;

  Value =  (UINTN) GetVariableDataPtr (Variable, AuthFlag);
  Value += DataSizeOfVariable (Variable, AuthFlag);
  Value += GET_PAD_SIZE (DataSizeOfVariable (Variable, AuthFlag));
  //
  // Be careful about pad size for alignment
  //
  Value = HEADER_ALIGN (Value);

  return (VARIABLE_HEADER *) Value;
}

/**
  This code determines how much space a variable with the given name size, data size, and format would use

  @param[in]  VariablesToCache          Table of variables name/guid pairs to cache
  @param[in]  VariableNameSize          The size of the variable's name in bytes
  @param[in]  VariableDataSize          The size of the variable's data in bytes
  @param[in]  AuthFlag                  Authenticated variable flag.

  @return     The size of the variable in bytes

**/
UINTN
GetVariableSize (
  IN  UINTN             VariableNameSize,
  IN  UINTN             VariableDataSize,
  IN  BOOLEAN           AuthFlag
  )
{
  UINTN   VariableSize;

  VariableSize  = GetVariableHeaderSize (AuthFlag);
  VariableSize += VariableNameSize;
  VariableSize += GET_PAD_SIZE (VariableNameSize);
  VariableSize += VariableDataSize;
  VariableSize += GET_PAD_SIZE (VariableDataSize);

  return HEADER_ALIGN (VariableSize);
}

EFI_STATUS
EFIAPI
InitializeVariableCacheStore (
  IN VARIABLE_STORE_HEADER  *VariableCacheStore,
  IN UINTN                  CacheHobSize
  )
{
  if (VariableCacheStore == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Initialize the variable store
  //
  CopyGuid (&VariableCacheStore->Signature, &gEfiVariableGuid);
  VariableCacheStore->Format = VARIABLE_STORE_FORMATTED;
  VariableCacheStore->State  = VARIABLE_STORE_HEALTHY;
  VariableCacheStore->Size   = (UINT32)CacheHobSize;

  return EFI_SUCCESS;
}

/**
  Creates the PEI variable cache HOB.

  @param[in]  VariablesToCache          Table of variables name/guid pairs to cache
  @param[in]  VariablesToCacheLength    The number of entries in the VariablesToCache table

  @retval EFI_SUCCESS           The variable cache HOB was created successfully
  @retval EFI_NOT_FOUND         One or more of the variables in the VariablesToCache table could not be found.
  @retval EFI_OUT_OF_RESOURCES  Unable to allocate a GUID HOB large enough to fit the variables
  @retval EFI_INVALID_PARAMETER VariablesToCache is NULL.

**/
EFI_STATUS
EFIAPI
CreateVariableCacheHob (
  IN  VARIABLE_NAME_TABLE_ENTRY   *VariablesToCache,
  IN  UINTN                       VariablesToCacheLength
  )
{
  EFI_PEI_READ_ONLY_VARIABLE2_PPI   *VariablePpi;
  VARIABLE_STORE_HEADER             *VariableStoreHeader;
  VARIABLE_HEADER                   *VariableHeader;
  EFI_GUID                          *VendorGuid;
  CHAR16                            *VariableNamePtr;
  EFI_STATUS                        Status;
  UINTN                             CacheHobSize;
  UINTN                             DataSize;
  UINTN                             Index;
  UINT32                            Attributes;
  UINT32                            NameSize;
  BOOLEAN                           AuthFlag;

  //
  // We don't support authenticated variable writes in PEI for now, so the cache HOB doesn't need authentication data
  //
  AuthFlag = FALSE;
  Status = PeiServicesLocatePpi (&gEfiPeiReadOnlyVariable2PpiGuid, 0, NULL, (VOID **)&VariablePpi);
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return Status;
  }

  //
  // First step is to compute how large the cache HOB will need to be
  //
  CacheHobSize = sizeof (VARIABLE_STORE_HEADER) + 1;
  for (Index = 0; Index < VariablesToCacheLength; Index++) {
    DataSize = 0;
    Status = VariablePpi->GetVariable (
                            VariablePpi,
                            VariablesToCache[Index].VariableName,
                            VariablesToCache[Index].VendorGuid,
                            NULL,
                            &DataSize,
                            NULL
                            );
    if (!EFI_ERROR (Status) || Status == EFI_BUFFER_TOO_SMALL) {
      CacheHobSize += GetVariableSize (StrSize (VariablesToCache[Index].VariableName), DataSize, AuthFlag);
    }
  }
  DEBUG ((DEBUG_INFO, "Total PEI variable cache HOB size = %d bytes.\n", CacheHobSize));

  //
  // Ensure the PEI variable cache size does not exceed the platform defined limit
  //
  if (CacheHobSize > PcdGet32 (PcdVariableCacheMaximumSize)) {
    DEBUG ((DEBUG_ERROR, "The space required for the variable cache exceeds the maximum cache size specified. Actual = 0x%x Maximum = 0x%x\n",
      CacheHobSize,
      PcdGet32 (PcdVariableCacheMaximumSize)));

    return EFI_OUT_OF_RESOURCES;
  }
  //
  // Create the variable cache HOB
  //
  VariableStoreHeader = BuildGuidHob (&gPeiVariableCacheHobGuid, CacheHobSize);
  if (VariableStoreHeader == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  SetMem (VariableStoreHeader, CacheHobSize, 0xff);
  ZeroMem (VariableStoreHeader, sizeof (VARIABLE_STORE_HEADER));
  DEBUG ((DEBUG_INFO, "VariableCacheStore HOB is allocated at 0x%x\n", VariableStoreHeader));
  InitializeVariableCacheStore (VariableStoreHeader, CacheHobSize);
  //
  // Populate the cache HOB with the given variables
  //
  VariableHeader = GetStartPointer (VariableStoreHeader);
  for (Index = 0; Index < VariablesToCacheLength; Index++) {
    DataSize = 0;
    Status = VariablePpi->GetVariable (
                            VariablePpi,
                            VariablesToCache[Index].VariableName,
                            VariablesToCache[Index].VendorGuid,
                            NULL,
                            &DataSize,
                            NULL
                            );
    if (!EFI_ERROR (Status) || Status == EFI_BUFFER_TOO_SMALL) {
      ZeroMem (VariableHeader, sizeof (VARIABLE_HEADER));
      NameSize              = StrSize (VariablesToCache[Index].VariableName);
      VariableHeader->State = VAR_HEADER_VALID_ONLY;
      SetNameSizeOfVariable (VariableHeader, NameSize, AuthFlag);
      SetDataSizeOfVariable (VariableHeader, DataSize, AuthFlag);
      VendorGuid = GetVendorGuidPtr (VariableHeader, AuthFlag);
      CopyGuid (VendorGuid, &(VariablesToCache[Index].VendorGuid));
      if (GetNextVariablePtr (VariableHeader, AuthFlag) >= GetEndPointer (VariableStoreHeader)) {
        DEBUG ((DEBUG_WARN, "Variable cache HOB ran out of space\n"));
        break;
      }
      Status = VariablePpi->GetVariable (
                              VariablePpi,
                              VariablesToCache[Index].VariableName,
                              VariablesToCache[Index].VendorGuid,
                              &Attributes,
                              &DataSize,
                              (VOID *) GetVariableDataPtr (VariableHeader, AuthFlag)
                              );
      if (!EFI_ERROR (Status)) {
        VariableHeader->StartId     = VARIABLE_DATA;
        VariableHeader->Attributes  = Attributes;
        VariableHeader->State       = VAR_ADDED;
        VariableNamePtr = GetVariableNamePtr (VariableHeader, AuthFlag);
        CopyMem (VariableNamePtr, VariablesToCache[Index].VariableName, NameSize);
        VariableHeader  = GetNextVariablePtr (VariableHeader, AuthFlag);
      }
    }
  }
  return EFI_SUCCESS;
}
