/** @file
  File managing the MMU for ARMv8 architecture in S-EL0

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

  @par Reference(s):
  - [1] SPM based on the MM interface.
        (https://trustedfirmware-a.readthedocs.io/en/latest/components/
         secure-partition-manager-mm.html)
  - [2] Arm Firmware Framework for Armv8-A, DEN0077A, version 1.0
        (https://developer.arm.com/documentation/den0077/a)
**/

#include <Uefi.h>
#include <IndustryStandard/ArmMmSvc.h>
#include <IndustryStandard/ArmFfaSvc.h>

#include <Library/ArmLib.h>
#include <Library/ArmMmuLib.h>
#include <Library/ArmSvcLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>

/** Send memory permission request to target.

  @param [in, out]  SvcArgs     Pointer to SVC arguments to send. On
                                return it contains the response parameters.
  @param [out]      RetVal      Pointer to return the response value.

  @retval EFI_SUCCESS           Request successfull.
  @retval EFI_INVALID_PARAMETER A parameter is invalid.
  @retval EFI_NOT_READY         Callee is busy or not in a state to handle
                                this request.
  @retval EFI_UNSUPPORTED       This function is not implemented by the
                                callee.
  @retval EFI_ABORTED           Message target ran into an unexpected error
                                and has aborted.
  @retval EFI_ACCESS_DENIED     Access denied.
  @retval EFI_OUT_OF_RESOURCES  Out of memory to perform operation.
**/
STATIC
EFI_STATUS
SendMemoryPermissionRequest (
  IN OUT  ARM_SVC_ARGS  *SvcArgs,
  OUT  INT32            *RetVal
  )
{
  if ((SvcArgs == NULL) || (RetVal == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  ArmCallSvc (SvcArgs);
  if (FeaturePcdGet (PcdFfaEnable)) {
    // Get/Set memory attributes is an atomic call, with
    // StandaloneMm at S-EL0 being the caller and the SPM
    // core being the callee. Thus there won't be a
    // FFA_INTERRUPT or FFA_SUCCESS response to the Direct
    // Request sent above. This will have to be considered
    // for other Direct Request calls which are not atomic
    // We therefore check only for Direct Response by the
    // callee.
    if (SvcArgs->Arg0 == ARM_SVC_ID_FFA_MSG_SEND_DIRECT_RESP) {
      // A Direct Response means FF-A success
      // Now check the payload for errors
      // The callee sends back the return value
      // in Arg3
      *RetVal = SvcArgs->Arg3;
    } else {
      // If Arg0 is not a Direct Response, that means we
      // have an FF-A error. We need to check Arg2 for the
      // FF-A error code.
      // See [2], Table 10.8: FFA_ERROR encoding.
      *RetVal = SvcArgs->Arg2;
      switch (*RetVal) {
        case ARM_FFA_SPM_RET_INVALID_PARAMETERS:
          return EFI_INVALID_PARAMETER;

        case ARM_FFA_SPM_RET_DENIED:
          return EFI_ACCESS_DENIED;

        case ARM_FFA_SPM_RET_NOT_SUPPORTED:
          return EFI_UNSUPPORTED;

        case ARM_FFA_SPM_RET_BUSY:
          return EFI_NOT_READY;

        case ARM_FFA_SPM_RET_ABORTED:
          return EFI_ABORTED;

        default:
          // Undefined error code received.
          ASSERT (0);
          return EFI_INVALID_PARAMETER;
      }
    }
  } else {
    *RetVal = SvcArgs->Arg0;
  }

  // Check error response from Callee.
  if ((*RetVal & BIT31) != 0) {
    // Bit 31 set means there is an error returned
    // See [1], Section 13.5.5.1 MM_SP_MEMORY_ATTRIBUTES_GET_AARCH64 and
    // Section 13.5.5.2 MM_SP_MEMORY_ATTRIBUTES_SET_AARCH64.
    switch (*RetVal) {
      case ARM_SVC_SPM_RET_NOT_SUPPORTED:
        return EFI_UNSUPPORTED;

      case ARM_SVC_SPM_RET_INVALID_PARAMS:
        return EFI_INVALID_PARAMETER;

      case ARM_SVC_SPM_RET_DENIED:
        return EFI_ACCESS_DENIED;

      case ARM_SVC_SPM_RET_NO_MEMORY:
        return EFI_OUT_OF_RESOURCES;

      default:
        // Undefined error code received.
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
    }
  }

  return EFI_SUCCESS;
}

/** Request the permission attributes of a memory region from S-EL0.

  @param [in]   BaseAddress          Base address for the memory region.
  @param [out]  MemoryAttributes     Pointer to return the memory attributes.

  @retval EFI_SUCCESS             Request successfull.
  @retval EFI_INVALID_PARAMETER   A parameter is invalid.
  @retval EFI_NOT_READY           Callee is busy or not in a state to handle
                                  this request.
  @retval EFI_UNSUPPORTED         This function is not implemented by the
                                  callee.
  @retval EFI_ABORTED             Message target ran into an unexpected error
                                  and has aborted.
  @retval EFI_ACCESS_DENIED       Access denied.
  @retval EFI_OUT_OF_RESOURCES    Out of memory to perform operation.
**/
STATIC
EFI_STATUS
GetMemoryPermissions (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress,
  OUT UINT32                *MemoryAttributes
  )
{
  EFI_STATUS    Status;
  INT32         Ret;
  ARM_SVC_ARGS  SvcArgs;

  if (MemoryAttributes == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  // Prepare the message parameters.
  // See [1], Section 13.5.5.1 MM_SP_MEMORY_ATTRIBUTES_GET_AARCH64.
  ZeroMem (&SvcArgs, sizeof (ARM_SVC_ARGS));
  if (FeaturePcdGet (PcdFfaEnable)) {
    // See [2], Section 10.2 FFA_MSG_SEND_DIRECT_REQ.
    SvcArgs.Arg0 = ARM_SVC_ID_FFA_MSG_SEND_DIRECT_REQ;
    SvcArgs.Arg1 = ARM_FFA_DESTINATION_ENDPOINT_ID;
    SvcArgs.Arg2 = 0;
    SvcArgs.Arg3 = ARM_SVC_ID_SP_GET_MEM_ATTRIBUTES;
    SvcArgs.Arg4 = BaseAddress;
  } else {
    SvcArgs.Arg0 = ARM_SVC_ID_SP_GET_MEM_ATTRIBUTES;
    SvcArgs.Arg1 = BaseAddress;
    SvcArgs.Arg2 = 0;
    SvcArgs.Arg3 = 0;
  }

  Status = SendMemoryPermissionRequest (&SvcArgs, &Ret);
  if (EFI_ERROR (Status)) {
    *MemoryAttributes = 0;
    return Status;
  }

  *MemoryAttributes = Ret;
  return Status;
}

/** Set the permission attributes of a memory region from S-EL0.

  @param [in]  BaseAddress     Base address for the memory region.
  @param [in]  Length          Length of the memory region.
  @param [in]  Permissions     Memory access controls attributes.

  @retval EFI_SUCCESS             Request successfull.
  @retval EFI_INVALID_PARAMETER   A parameter is invalid.
  @retval EFI_NOT_READY           Callee is busy or not in a state to handle
                                  this request.
  @retval EFI_UNSUPPORTED         This function is not implemented by the
                                  callee.
  @retval EFI_ABORTED             Message target ran into an unexpected error
                                  and has aborted.
  @retval EFI_ACCESS_DENIED       Access denied.
  @retval EFI_OUT_OF_RESOURCES    Out of memory to perform operation.
**/
STATIC
EFI_STATUS
RequestMemoryPermissionChange (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN  UINT64                Length,
  IN  UINT32                Permissions
  )
{
  INT32         Ret;
  ARM_SVC_ARGS  SvcArgs;

  // Prepare the message parameters.
  // See [1], Section 13.5.5.2 MM_SP_MEMORY_ATTRIBUTES_SET_AARCH64.
  ZeroMem (&SvcArgs, sizeof (ARM_SVC_ARGS));
  if (FeaturePcdGet (PcdFfaEnable)) {
    // See [2], Section 10.2 FFA_MSG_SEND_DIRECT_REQ.
    SvcArgs.Arg0 = ARM_SVC_ID_FFA_MSG_SEND_DIRECT_REQ;
    SvcArgs.Arg1 = ARM_FFA_DESTINATION_ENDPOINT_ID;
    SvcArgs.Arg2 = 0;
    SvcArgs.Arg3 = ARM_SVC_ID_SP_SET_MEM_ATTRIBUTES;
    SvcArgs.Arg4 = BaseAddress;
    SvcArgs.Arg5 = EFI_SIZE_TO_PAGES (Length);
    SvcArgs.Arg6 = Permissions;
  } else {
    SvcArgs.Arg0 = ARM_SVC_ID_SP_SET_MEM_ATTRIBUTES;
    SvcArgs.Arg1 = BaseAddress;
    SvcArgs.Arg2 = EFI_SIZE_TO_PAGES (Length);
    SvcArgs.Arg3 = Permissions;
  }

  return SendMemoryPermissionRequest (&SvcArgs, &Ret);
}

EFI_STATUS
ArmSetMemoryRegionNoExec (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN  UINT64                Length
  )
{
  EFI_STATUS  Status;
  UINT32      MemoryAttributes;
  UINT32      CodePermission;

  Status = GetMemoryPermissions (BaseAddress, &MemoryAttributes);
  if (!EFI_ERROR (Status)) {
    CodePermission = SET_MEM_ATTR_CODE_PERM_XN << SET_MEM_ATTR_CODE_PERM_SHIFT;
    return RequestMemoryPermissionChange (
             BaseAddress,
             Length,
             MemoryAttributes | CodePermission
             );
  }

  return Status;
}

EFI_STATUS
ArmClearMemoryRegionNoExec (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN  UINT64                Length
  )
{
  EFI_STATUS  Status;
  UINT32      MemoryAttributes;
  UINT32      CodePermission;

  Status = GetMemoryPermissions (BaseAddress, &MemoryAttributes);
  if (!EFI_ERROR (Status)) {
    CodePermission = SET_MEM_ATTR_CODE_PERM_XN << SET_MEM_ATTR_CODE_PERM_SHIFT;
    return RequestMemoryPermissionChange (
             BaseAddress,
             Length,
             MemoryAttributes & ~CodePermission
             );
  }

  return Status;
}

EFI_STATUS
ArmSetMemoryRegionReadOnly (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN  UINT64                Length
  )
{
  EFI_STATUS  Status;
  UINT32      MemoryAttributes;
  UINT32      DataPermission;

  Status = GetMemoryPermissions (BaseAddress, &MemoryAttributes);
  if (!EFI_ERROR (Status)) {
    DataPermission = SET_MEM_ATTR_DATA_PERM_RO << SET_MEM_ATTR_DATA_PERM_SHIFT;
    return RequestMemoryPermissionChange (
             BaseAddress,
             Length,
             MemoryAttributes | DataPermission
             );
  }

  return Status;
}

EFI_STATUS
ArmClearMemoryRegionReadOnly (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN  UINT64                Length
  )
{
  EFI_STATUS  Status;
  UINT32      MemoryAttributes;
  UINT32      PermissionRequest;

  Status = GetMemoryPermissions (BaseAddress, &MemoryAttributes);
  if (!EFI_ERROR (Status)) {
    PermissionRequest = SET_MEM_ATTR_MAKE_PERM_REQUEST (
                          SET_MEM_ATTR_DATA_PERM_RW,
                          MemoryAttributes
                          );
    return RequestMemoryPermissionChange (
             BaseAddress,
             Length,
             PermissionRequest
             );
  }

  return Status;
}
