/** @file

  Library implementing the LockBox interface for OVMF

  Copyright (C) 2013, Red Hat, Inc.
  Copyright (c) 2010 - 2019, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/LockBoxLib.h>
#include <Library/PcdLib.h>
#include <LockBoxLib.h>

#pragma pack(1)
typedef struct {
  EFI_GUID                Guid;
  EFI_PHYSICAL_ADDRESS    OrigAddress;
  EFI_PHYSICAL_ADDRESS    CopyAddress;
  UINT32                  Size;
  UINT64                  Attributes;
} LOCK_BOX_ENTRY;
#pragma pack()

LOCK_BOX_GLOBAL        *mLockBoxGlobal = NULL;
STATIC LOCK_BOX_ENTRY  *StartOfEntries = NULL;
STATIC LOCK_BOX_ENTRY  *EndOfEntries   = NULL;

RETURN_STATUS
EFIAPI
LockBoxLibInitialize (
  VOID
  )
{
  UINTN  NumEntries;

  ASSERT (!FeaturePcdGet (PcdSmmSmramRequire));

  if (PcdGet32 (PcdOvmfLockBoxStorageSize) < sizeof (LOCK_BOX_GLOBAL)) {
    return RETURN_UNSUPPORTED;
  }

  mLockBoxGlobal = (LOCK_BOX_GLOBAL *)(UINTN)PcdGet32 (PcdOvmfLockBoxStorageBase);
  StartOfEntries = ((LOCK_BOX_ENTRY *)(mLockBoxGlobal + 1));
  NumEntries     = ((PcdGet32 (PcdOvmfLockBoxStorageSize) - sizeof (LOCK_BOX_GLOBAL)) /
                    sizeof (LOCK_BOX_ENTRY));
  EndOfEntries = StartOfEntries + NumEntries;
  if (mLockBoxGlobal->Signature != LOCK_BOX_GLOBAL_SIGNATURE) {
    //
    // Note: This code depends on the lock box being cleared in early
    // PEI before usage, so the SubPageBuffer and SubPageRemaining
    // fields don't need to be set to 0.
    //
    mLockBoxGlobal->Signature = LOCK_BOX_GLOBAL_SIGNATURE;
  }

  return RETURN_SUCCESS;
}

/**
  Find LockBox entry based on GUID.

  @param[in] Guid  The GUID to search for.

  @return  Address of the LOCK_BOX_ENTRY found.

           If NULL, then the item was not found, and there is no space
           left to store a new item.

           If non-NULL and LOCK_BOX_ENTRY.Size == 0, then the item was not
           found, but a new item can be inserted at the returned location.

           If non-NULL and LOCK_BOX_ENTRY.Size > 0, then the item was found.
**/
STATIC
LOCK_BOX_ENTRY *
EFIAPI
FindHeaderByGuid (
  IN CONST EFI_GUID  *Guid
  )
{
  LOCK_BOX_ENTRY  *Header;

  for (Header = StartOfEntries; Header < EndOfEntries; Header++) {
    if ((Header->Size == 0) || CompareGuid (Guid, &Header->Guid)) {
      return Header;
    }
  }

  return NULL;
}

/**
  This function will save confidential information to lockbox.

  @param Guid       the guid to identify the confidential information
  @param Buffer     the address of the confidential information
  @param Length     the length of the confidential information

  @retval RETURN_SUCCESS            the information is saved successfully.
  @retval RETURN_INVALID_PARAMETER  the Guid is NULL, or Buffer is NULL, or
                                    Length is 0
  @retval RETURN_ALREADY_STARTED    the requested GUID already exist.
  @retval RETURN_OUT_OF_RESOURCES   no enough resource to save the information.
  @retval RETURN_ACCESS_DENIED      it is too late to invoke this interface
  @retval RETURN_NOT_STARTED        it is too early to invoke this interface
  @retval RETURN_UNSUPPORTED        the service is not supported by
                                    implementaion.
**/
RETURN_STATUS
EFIAPI
SaveLockBox (
  IN  GUID   *Guid,
  IN  VOID   *Buffer,
  IN  UINTN  Length
  )
{
  LOCK_BOX_ENTRY  *Header;
  VOID            *CopyBuffer;

  DEBUG ((
    DEBUG_VERBOSE,
    "%a: Guid=%g Buffer=%p Length=0x%x\n",
    __func__,
    Guid,
    Buffer,
    (UINT32)Length
    ));

  if ((Guid == NULL) || (Buffer == NULL) || (Length == 0)) {
    return RETURN_INVALID_PARAMETER;
  }

  if (Length > 0xFFFFFFFF) {
    return RETURN_OUT_OF_RESOURCES;
  }

  Header = FindHeaderByGuid (Guid);
  if (Header == NULL) {
    return RETURN_OUT_OF_RESOURCES;
  }

  if (Header->Size > 0) {
    return RETURN_ALREADY_STARTED;
  }

  CopyBuffer = AllocateAcpiNvsPool (Length);
  if (CopyBuffer == NULL) {
    return RETURN_OUT_OF_RESOURCES;
  }

  //
  // overwrite the current terminator header with new metadata
  //
  CopyGuid (&Header->Guid, Guid);
  Header->OrigAddress = (UINTN)Buffer;
  Header->CopyAddress = (UINTN)CopyBuffer;
  Header->Size        = (UINT32)Length;
  Header->Attributes  = 0;

  //
  // copy contents
  //
  CopyMem (CopyBuffer, Buffer, Length);

  return RETURN_SUCCESS;
}

/**
  This function will set lockbox attributes.

  @param Guid       the guid to identify the confidential information
  @param Attributes the attributes of the lockbox

  @retval RETURN_SUCCESS            the information is saved successfully.
  @retval RETURN_INVALID_PARAMETER  attributes is invalid.
  @retval RETURN_NOT_FOUND          the requested GUID not found.
  @retval RETURN_ACCESS_DENIED      it is too late to invoke this interface
  @retval RETURN_NOT_STARTED        it is too early to invoke this interface
  @retval RETURN_UNSUPPORTED        the service is not supported by
                                    implementaion.
**/
RETURN_STATUS
EFIAPI
SetLockBoxAttributes (
  IN  GUID    *Guid,
  IN  UINT64  Attributes
  )
{
  LOCK_BOX_ENTRY  *Header;

  DEBUG ((
    DEBUG_VERBOSE,
    "%a: Guid=%g Attributes=0x%Lx\n",
    __func__,
    Guid,
    Attributes
    ));

  if (Guid == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  Header = FindHeaderByGuid (Guid);
  if (!Header || (Header->Size == 0)) {
    return RETURN_NOT_FOUND;
  }

  Header->Attributes = Attributes;

  return RETURN_SUCCESS;
}

/**
  This function will update confidential information to lockbox.

  @param Guid   the guid to identify the original confidential information
  @param Offset the offset of the original confidential information
  @param Buffer the address of the updated confidential information
  @param Length the length of the updated confidential information

  @retval RETURN_SUCCESS            the information is saved successfully.
  @retval RETURN_INVALID_PARAMETER  the Guid is NULL, or Buffer is NULL, or
                                    Length is 0.
  @retval RETURN_NOT_FOUND          the requested GUID not found.
  @retval RETURN_BUFFER_TOO_SMALL   for lockbox without attribute
                                    LOCK_BOX_ATTRIBUTE_RESTORE_IN_S3_ONLY, the
                                    original buffer to too small to hold new
                                    information.
  @retval RETURN_OUT_OF_RESOURCES   for lockbox with attribute
                                    LOCK_BOX_ATTRIBUTE_RESTORE_IN_S3_ONLY, no
                                    enough resource to save the information.
  @retval RETURN_ACCESS_DENIED      it is too late to invoke this interface
  @retval RETURN_NOT_STARTED        it is too early to invoke this interface
  @retval RETURN_UNSUPPORTED        the service is not supported by
                                    implementaion.
**/
RETURN_STATUS
EFIAPI
UpdateLockBox (
  IN  GUID   *Guid,
  IN  UINTN  Offset,
  IN  VOID   *Buffer,
  IN  UINTN  Length
  )
{
  LOCK_BOX_ENTRY  *Header;

  DEBUG ((
    DEBUG_VERBOSE,
    "%a: Guid=%g Offset=0x%x Length=0x%x\n",
    __func__,
    Guid,
    (UINT32)Offset,
    (UINT32)Length
    ));

  if ((Guid == NULL) || (Buffer == NULL) || (Length == 0)) {
    return RETURN_INVALID_PARAMETER;
  }

  Header = FindHeaderByGuid (Guid);
  if (!Header || (Header->Size == 0)) {
    return RETURN_NOT_FOUND;
  }

  if ((Header->Size < Offset) ||
      (Length > Header->Size - Offset))
  {
    return RETURN_BUFFER_TOO_SMALL;
  }

  CopyMem ((UINT8 *)(UINTN)(Header->CopyAddress) + Offset, Buffer, Length);

  return RETURN_SUCCESS;
}

/**
  This function will restore confidential information from lockbox.

  @param Guid   the guid to identify the confidential information
  @param Buffer the address of the restored confidential information
                NULL means restored to original address, Length MUST be NULL at
                same time.
  @param Length the length of the restored confidential information

  @retval RETURN_SUCCESS            the information is restored successfully.
  @retval RETURN_INVALID_PARAMETER  the Guid is NULL, or one of Buffer and
                                    Length is NULL.
  @retval RETURN_WRITE_PROTECTED    Buffer and Length are NULL, but the LockBox
                                    has no LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE
                                    attribute.
  @retval RETURN_BUFFER_TOO_SMALL   the Length is too small to hold the
                                    confidential information.
  @retval RETURN_NOT_FOUND          the requested GUID not found.
  @retval RETURN_NOT_STARTED        it is too early to invoke this interface
  @retval RETURN_ACCESS_DENIED      not allow to restore to the address
  @retval RETURN_UNSUPPORTED        the service is not supported by
                                    implementaion.
**/
RETURN_STATUS
EFIAPI
RestoreLockBox (
  IN  GUID       *Guid,
  IN  VOID       *Buffer  OPTIONAL,
  IN  OUT UINTN  *Length  OPTIONAL
  )
{
  LOCK_BOX_ENTRY  *Header;

  DEBUG ((
    DEBUG_VERBOSE,
    "%a: Guid=%g Buffer=%p\n",
    __func__,
    Guid,
    Buffer
    ));

  if ((Guid == NULL) ||
      ((Buffer == NULL) && (Length != NULL)) ||
      ((Buffer != NULL) && (Length == NULL)))
  {
    return EFI_INVALID_PARAMETER;
  }

  Header = FindHeaderByGuid (Guid);
  if (!Header || (Header->Size == 0)) {
    return RETURN_NOT_FOUND;
  }

  if (Buffer == NULL) {
    if (!(Header->Attributes & LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE)) {
      return RETURN_WRITE_PROTECTED;
    }

    if (Header->OrigAddress + (Header->Size - 1) > MAX_ADDRESS) {
      return RETURN_UNSUPPORTED;
    }

    Buffer = (VOID *)(UINTN)Header->OrigAddress;
  }

  //
  // Set RestoreLength
  //
  if (Length != NULL) {
    if (Header->Size > *Length) {
      //
      // Input buffer is too small to hold all data.
      //
      *Length = Header->Size;
      return EFI_BUFFER_TOO_SMALL;
    }

    *Length = Header->Size;
  }

  CopyMem (Buffer, (VOID *)(UINTN)Header->CopyAddress, Header->Size);

  return RETURN_SUCCESS;
}

/**
  This function will restore confidential information from all lockbox which
  have RestoreInPlace attribute.

  @retval RETURN_SUCCESS            the information is restored successfully.
  @retval RETURN_NOT_STARTED        it is too early to invoke this interface
  @retval RETURN_UNSUPPORTED        the service is not supported by
                                    implementaion.
**/
RETURN_STATUS
EFIAPI
RestoreAllLockBoxInPlace (
  VOID
  )
{
  LOCK_BOX_ENTRY  *Header;

  for (Header = StartOfEntries;
       Header < EndOfEntries && Header->Size > 0;
       Header++)
  {
    if (Header->Attributes & LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE) {
      VOID  *Buffer;

      if (Header->OrigAddress + (Header->Size - 1) > MAX_ADDRESS) {
        return RETURN_UNSUPPORTED;
      }

      Buffer = (VOID *)(UINTN)Header->OrigAddress;
      CopyMem (Buffer, (VOID *)(UINTN)Header->CopyAddress, Header->Size);
      DEBUG ((
        DEBUG_VERBOSE,
        "%a: Guid=%g Buffer=%p\n",
        __func__,
        &Header->Guid,
        Buffer
        ));
    }
  }

  return RETURN_SUCCESS;
}
