/** @file
  Library that implements the Arm CCA helper functions.

  Copyright (c) 2022 - 2026, Arm Limited. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

  @par Glossary:
    - Rsi or RSI   - Realm Service Interface
    - IPA          - Intermediate Physical Address
    - RIPAS        - Realm IPA state
**/
#include <Base.h>

#include <Library/ArmCcaLib.h>
#include <Library/ArmCcaRsiLib.h>
#include <Library/ArmLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Uefi/UefiMultiPhase.h>
#include <Uefi/UefiSpec.h>
#include <Pi/PiBootMode.h>

#include <Pi/PiHob.h>
#include <Library/HobLib.h>

/**
  Queries to see if running in a Realm.

    @retval TRUE    The execution is within the context of a Realm.
    @retval FALSE   The execution is not within the context of a Realm.
**/
STATIC
BOOLEAN
EFIAPI
ArmCcaReadIsRealm (
  VOID
  )
{
  RETURN_STATUS  Status;
  UINT32         UefiImpl;
  UINT32         RmmImplLow;
  UINT32         RmmImplHigh;

  // Query the RSI_GET_VERSION. If it is implemented then
  // RMM is present and we must be running in a Realm.
  Status = ArmCcaRsiGetVersion (
             &UefiImpl,
             &RmmImplLow,
             &RmmImplHigh
             );
  if (!RETURN_ERROR (Status)) {
    return TRUE;
  }

  return FALSE;
}

/**
  Check if running in a Realm.

    @retval TRUE    The execution is within the context of a Realm.
    @retval FALSE   The execution is not within the context of a Realm.
**/
BOOLEAN
EFIAPI
ArmCcaIsRealm (
  VOID
  )
{
  BOOLEAN  RealmWorld;
  VOID     *HobList;

  VOID    *Hob;
  UINT64  *IsRealmHobData;

  if (!ArmHasRme ()) {
    return FALSE;
  }

  HobList = GetHobList ();
  if (HobList == NULL) {
    // HOB not initialised.
    return ArmCcaReadIsRealm ();
  }

  RealmWorld = FALSE;
  Hob        = GetFirstGuidHob (&gArmCcaIsRealmGuid);
  if ((Hob == NULL) ||
      (GET_GUID_HOB_DATA_SIZE (Hob) < sizeof (*IsRealmHobData)))
  {
    // The gArmCcaIsRealmGuid is created in the PlatformPeim() which
    // may not have been called for caching the IsRealm value yet.
    // So, read the Realm value.
    RealmWorld = ArmCcaReadIsRealm ();
  } else {
    // Read the value from the HOB
    IsRealmHobData = GET_GUID_HOB_DATA (Hob);
    RealmWorld     = *IsRealmHobData;
  }

  return RealmWorld;
}

/**
  Read the IPA width of the Realm.

  The IPA width of the Realm is used to configure the protection attribute
  for memory regions, see ArmCcaSetMemoryProtectionAttribute().

    @param [out] IpaWidth  IPA width of the Realm.

    @retval RETURN_SUCCESS            Success.
    @retval RETURN_INVALID_PARAMETER  A parameter is invalid.
    @retval RETURN_OUT_OF_RESOURCES   Out of resources.

**/
STATIC
RETURN_STATUS
EFIAPI
ArmCcaReadIpaWidth (
  OUT UINT64  *IpaWidth
  )
{
  RETURN_STATUS         Status;
  ARM_CCA_REALM_CONFIG  *Config;

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

  Config = AllocateAlignedPages (
             EFI_SIZE_TO_PAGES (sizeof (ARM_CCA_REALM_CONFIG)),
             ARM_CCA_REALM_GRANULE_SIZE
             );
  if (Config == NULL) {
    ASSERT (0);
    return RETURN_OUT_OF_RESOURCES;
  }

  ZeroMem (Config, sizeof (ARM_CCA_REALM_CONFIG));

  Status = ArmCcaRsiGetRealmConfig (Config);
  if (RETURN_ERROR (Status)) {
    ASSERT (0);
    goto ExitHandler;
  }

  if (Config->IpaWidth > ArmGetPhysicalAddressBits ()) {
    ASSERT (0);
    Status = RETURN_INVALID_PARAMETER;
    goto ExitHandler;
  }

  *IpaWidth = Config->IpaWidth;

ExitHandler:
  FreeAlignedPages (Config, EFI_SIZE_TO_PAGES (sizeof (ARM_CCA_REALM_CONFIG)));
  return Status;
}

/**
  Return the IPA width of the Realm.

  The IPA width of the Realm is used to configure the protection attribute
  for memory regions, see ArmCcaSetMemoryProtectionAttribute().

    @param [out] IpaWidth  IPA width of the Realm.

    @retval RETURN_SUCCESS            Success.
    @retval RETURN_INVALID_PARAMETER  A parameter is invalid.
    @retval RETURN_UNSUPPORTED        The execution is not within the
                                      context of a Realm.
    @retval RETURN_OUT_OF_RESOURCES   Out of resources.
**/
RETURN_STATUS
EFIAPI
ArmCcaGetIpaWidth (
  OUT UINT64  *IpaWidth
  )
{
  RETURN_STATUS  Status;
  VOID           *HobList;
  VOID           *Hob;
  UINT64         *CcaIpaWidth;

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

  if (!ArmCcaIsRealm ()) {
    *IpaWidth = 0;
    return RETURN_UNSUPPORTED;
  }

  HobList = GetHobList ();
  if (HobList == NULL) {
    // HOB not initialised.
    return ArmCcaReadIpaWidth (IpaWidth);
  }

  Hob = GetFirstGuidHob (&gArmCcaIpaWidthGuid);
  if ((Hob == NULL) ||
      (GET_GUID_HOB_DATA_SIZE (Hob) < sizeof (UINT64)))
  {
    // The gArmCcaIpaWidthGuid is created in the PlatformPeim() which
    // may not have been called for caching the IpaWidth value yet.
    // So, read the IPA Width value.
    Status = ArmCcaReadIpaWidth (IpaWidth);
    if (RETURN_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  } else {
    // Read the value from the HOB.
    CcaIpaWidth = GET_GUID_HOB_DATA (Hob);
    if ((UINT64)*CcaIpaWidth == 0) {
      return RETURN_NOT_FOUND;
    }

    *IpaWidth = *CcaIpaWidth;
  }

  return RETURN_SUCCESS;
}

/** Check if the address range is protected MMIO

  @param [in]   BaseAddress      Base address of the device MMIO region.
  @param [in]   Length           Length of the device MMIO region.
  @param [out]  IsProtectedMmio  TRUE - if the RIPAS for the address range is
                                        protected MMIO.
                                 FALSE - if the RIPAS for the address range is
                                         not protected MMIO.

  @retval RETURN_SUCCESS            Success.
  @retval RETURN_INVALID_PARAMETER  A parameter is invalid.
  @retval RETURN_UNSUPPORTED        The request is not initiated in a
                                    Realm.
**/
RETURN_STATUS
EFIAPI
ArmCcaMemRangeIsProtectedMmio (
  IN  UINT64   BaseAddress,
  IN  UINT64   Length,
  OUT BOOLEAN  *IsProtectedMmio
  )
{
  RETURN_STATUS  RetStatus;
  UINT64         *Base;
  UINT64         *Top;
  UINT64         *End;
  ARM_CCA_RIPAS  State;

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

  Base = (UINT64 *)BaseAddress;
  End  = (UINT64 *)(BaseAddress + Length);

  while (Base < End) {
    Top       = End;
    RetStatus = ArmCcaRsiGetIpaState (
                  Base,
                  &Top,
                  &State
                  );
    if (RETURN_ERROR (RetStatus)) {
      // An error occurred.
      return RetStatus;
    }

    if (State != RipasDev) {
      // The address range is not protected MMIO.
      *IsProtectedMmio = FALSE;
      return RetStatus;
    }

    Base = Top;
  } // while

  *IsProtectedMmio = TRUE;
  return RETURN_SUCCESS;
}
