/** @file

  Stateful and implicitly initialized fw_cfg library implementation.

  Copyright (C) 2013, Red Hat, Inc.
  Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.<BR>
  Copyright (c) 2017, Advanced Micro Devices. All rights reserved.<BR>

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

#include <PiPei.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/IoLib.h>
#include <Library/QemuFwCfgLib.h>
#include <WorkArea.h>

#include "QemuFwCfgLibInternal.h"

/**
  Check if it is Tdx guest

  @retval    TRUE   It is Tdx guest
  @retval    FALSE  It is not Tdx guest
**/
STATIC
BOOLEAN
QemuFwCfgIsCcGuest (
  VOID
  )
{
  CONFIDENTIAL_COMPUTING_WORK_AREA_HEADER  *CcWorkAreaHeader;

  CcWorkAreaHeader = (CONFIDENTIAL_COMPUTING_WORK_AREA_HEADER *)FixedPcdGet32 (PcdOvmfWorkAreaBase);
  return (CcWorkAreaHeader != NULL && CcWorkAreaHeader->GuestType != CcGuestTypeNonEncrypted);
}

/**
  Returns a boolean indicating if the firmware configuration interface
  is available or not.

  This function may change fw_cfg state.

  @retval    TRUE   The interface is available
  @retval    FALSE  The interface is not available

**/
BOOLEAN
EFIAPI
QemuFwCfgIsAvailable (
  VOID
  )
{
  return InternalQemuFwCfgIsAvailable ();
}

STATIC
VOID
QemuFwCfgProbe (
  BOOLEAN  *Supported,
  BOOLEAN  *DmaSupported
  )
{
  UINT32   Signature;
  UINT32   Revision;
  BOOLEAN  CcGuest;

  // Use direct Io* calls for probing to avoid recursion.
  QemuFwCfgSelectItem (QemuFwCfgItemSignature);
  if ( InternalQemuFwCfgCacheReading ()) {
    InternalQemuFwCfgCacheReadBytes (sizeof Signature, &Signature);
  } else {
    IoReadFifo8 (FW_CFG_IO_DATA, sizeof Signature, &Signature);
  }

  QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion);
  if ( InternalQemuFwCfgCacheReading ()) {
    InternalQemuFwCfgCacheReadBytes (sizeof Revision, &Revision);
  } else {
    IoReadFifo8 (FW_CFG_IO_DATA, sizeof Revision, &Revision);
  }

  CcGuest = QemuFwCfgIsCcGuest ();

  *Supported    = FALSE;
  *DmaSupported = FALSE;
  if ((Signature == SIGNATURE_32 ('Q', 'E', 'M', 'U')) && (Revision >= 1)) {
    *Supported = TRUE;
    if ((Revision & FW_CFG_F_DMA) && !CcGuest) {
      *DmaSupported = TRUE;
    }
  }

  DEBUG ((
    DEBUG_INFO,
    "%a: Supported %d, DMA %d\n",
    __func__,
    *Supported,
    *DmaSupported
    ));
}

STATIC
EFI_HOB_PLATFORM_INFO *
QemuFwCfgGetPlatformInfo (
  VOID
  )
{
  EFI_HOB_PLATFORM_INFO  *PlatformInfoHob;
  EFI_HOB_GUID_TYPE      *GuidHob;

  GuidHob = GetFirstGuidHob (&gUefiOvmfPkgPlatformInfoGuid);
  if (GuidHob == NULL) {
    return NULL;
  }

  PlatformInfoHob = (EFI_HOB_PLATFORM_INFO *)GET_GUID_HOB_DATA (GuidHob);

  if (!PlatformInfoHob->QemuFwCfgChecked) {
    QemuFwCfgProbe (
      &PlatformInfoHob->QemuFwCfgWorkArea.QemuFwCfgSupported,
      &PlatformInfoHob->QemuFwCfgWorkArea.QemuFwCfgDmaSupported
      );
    PlatformInfoHob->QemuFwCfgChecked = TRUE;
  }

  return PlatformInfoHob;
}

RETURN_STATUS
EFIAPI
QemuFwCfgInitialize (
  VOID
  )
{
  return RETURN_SUCCESS;
}

/**
  Returns a boolean indicating if the firmware configuration interface is
  available for library-internal purposes.

  This function never changes fw_cfg state.

  @retval    TRUE   The interface is available internally.
  @retval    FALSE  The interface is not available internally.
**/
BOOLEAN
InternalQemuFwCfgIsAvailable (
  VOID
  )
{
  EFI_HOB_PLATFORM_INFO  *PlatformInfoHob = QemuFwCfgGetPlatformInfo ();

  return PlatformInfoHob->QemuFwCfgWorkArea.QemuFwCfgSupported;
}

/**
  Returns a boolean indicating whether QEMU provides the DMA-like access method
  for fw_cfg.

  @retval    TRUE   The DMA-like access method is available.
  @retval    FALSE  The DMA-like access method is unavailable.
**/
BOOLEAN
InternalQemuFwCfgDmaIsAvailable (
  VOID
  )
{
  EFI_HOB_PLATFORM_INFO  *PlatformInfoHob = QemuFwCfgGetPlatformInfo ();

  return PlatformInfoHob->QemuFwCfgWorkArea.QemuFwCfgDmaSupported;
}

/**
  Transfer an array of bytes, or skip a number of bytes, using the DMA
  interface.

  @param[in]     Size     Size in bytes to transfer or skip.

  @param[in,out] Buffer   Buffer to read data into or write data from. Ignored,
                          and may be NULL, if Size is zero, or Control is
                          FW_CFG_DMA_CTL_SKIP.

  @param[in]     Control  One of the following:
                          FW_CFG_DMA_CTL_WRITE - write to fw_cfg from Buffer.
                          FW_CFG_DMA_CTL_READ  - read from fw_cfg into Buffer.
                          FW_CFG_DMA_CTL_SKIP  - skip bytes in fw_cfg.
**/
VOID
InternalQemuFwCfgDmaBytes (
  IN     UINT32  Size,
  IN OUT VOID    *Buffer OPTIONAL,
  IN     UINT32  Control
  )
{
  volatile FW_CFG_DMA_ACCESS  Access;
  UINT32                      AccessHigh, AccessLow;
  UINT32                      Status;

  ASSERT (
    Control == FW_CFG_DMA_CTL_WRITE || Control == FW_CFG_DMA_CTL_READ ||
    Control == FW_CFG_DMA_CTL_SKIP
    );

  if (Size == 0) {
    return;
  }

  //
  // TDX does not support DMA operations in PEI stage, we should
  // not have reached here.
  //
  ASSERT (!QemuFwCfgIsCcGuest ());

  Access.Control = SwapBytes32 (Control);
  Access.Length  = SwapBytes32 (Size);
  Access.Address = SwapBytes64 ((UINTN)Buffer);

  //
  // Delimit the transfer from (a) modifications to Access, (b) in case of a
  // write, from writes to Buffer by the caller.
  //
  MemoryFence ();

  //
  // Start the transfer.
  //
  AccessHigh = (UINT32)RShiftU64 ((UINTN)&Access, 32);
  AccessLow  = (UINT32)(UINTN)&Access;
  IoWrite32 (FW_CFG_IO_DMA_ADDRESS, SwapBytes32 (AccessHigh));
  IoWrite32 (FW_CFG_IO_DMA_ADDRESS + 4, SwapBytes32 (AccessLow));

  //
  // Don't look at Access.Control before starting the transfer.
  //
  MemoryFence ();

  //
  // Wait for the transfer to complete.
  //
  do {
    Status = SwapBytes32 (Access.Control);
    ASSERT ((Status & FW_CFG_DMA_CTL_ERROR) == 0);
  } while (Status != 0);

  //
  // After a read, the caller will want to use Buffer.
  //
  MemoryFence ();
}

/**
  Get the pointer to the QEMU_FW_CFG_WORK_AREA. This data is used as the
  workarea to record the onging fw_cfg item and offset.
  @retval   QEMU_FW_CFG_WORK_AREA  Pointer to the QEMU_FW_CFG_WORK_AREA
  @retval   NULL                QEMU_FW_CFG_WORK_AREA doesn't exist
**/
QEMU_FW_CFG_WORK_AREA *
InternalQemuFwCfgCacheGetWorkArea (
  VOID
  )
{
  EFI_HOB_GUID_TYPE      *GuidHob;
  EFI_HOB_PLATFORM_INFO  *PlatformHobinfo;

  GuidHob = GetFirstGuidHob (&gUefiOvmfPkgPlatformInfoGuid);
  if (GuidHob == NULL) {
    return NULL;
  }

  PlatformHobinfo = (EFI_HOB_PLATFORM_INFO *)(VOID *)GET_GUID_HOB_DATA (GuidHob);
  return &(PlatformHobinfo->QemuFwCfgWorkArea);
}

/**
 OVMF reads configuration data from QEMU via fw_cfg.
 For Td-Guest VMM is out of TCB and the configuration data is untrusted.
 From the security perpective the configuration data shall be measured
 before it is consumed.
 This function reads the fw_cfg items and cached them. In the meanwhile these
 fw_cfg items are measured as well. This is to avoid changing the order when
 reading the fw_cfg process, which depends on multiple factors(depex, order in
 the Firmware volume).
 @retval  RETURN_SUCCESS   - Successfully cache with measurement
 @retval  Others           - As the error code indicates
 */
RETURN_STATUS
EFIAPI
QemuFwCfgInitCache (
  IN OUT EFI_HOB_PLATFORM_INFO  *PlatformInfoHob
  )
{
  if (EFI_ERROR (InternalQemuFwCfgInitCache (PlatformInfoHob))) {
    return RETURN_ABORTED;
  }

  DEBUG ((DEBUG_INFO, "QemuFwCfgInitCache Pass!!!\n"));
  return RETURN_SUCCESS;
}
