/** @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", __FUNCTION__,
    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", __FUNCTION__, 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", __FUNCTION__,
    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", __FUNCTION__, 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", __FUNCTION__,
        &Header->Guid, Buffer));
    }
  }
  return RETURN_SUCCESS;
}
