/*++
Copyright (c) 2009, Hewlett-Packard Company. All rights reserved.
Portions copyright (c) 2010, Apple Inc. All rights reserved.
Portions copyright (c) 2011-2021, Arm Limited. All rights reserved.
Copyright (c) 2017, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
--*/
#include 
#include "CpuDxe.h"
#define INVALID_ENTRY  ((UINT32)~0)
#define MIN_T0SZ        16
#define BITS_PER_LEVEL  9
STATIC
VOID
GetRootTranslationTableInfo (
  IN  UINTN  T0SZ,
  OUT UINTN  *RootTableLevel,
  OUT UINTN  *RootTableEntryCount
  )
{
  *RootTableLevel      = (T0SZ - MIN_T0SZ) / BITS_PER_LEVEL;
  *RootTableEntryCount = TT_ENTRY_COUNT >> (T0SZ - MIN_T0SZ) % BITS_PER_LEVEL;
}
STATIC
UINT64
PageAttributeToGcdAttribute (
  IN UINT64  PageAttributes
  )
{
  UINT64  GcdAttributes;
  switch (PageAttributes & TT_ATTR_INDX_MASK) {
    case TT_ATTR_INDX_DEVICE_MEMORY:
      GcdAttributes = EFI_MEMORY_UC;
      break;
    case TT_ATTR_INDX_MEMORY_NON_CACHEABLE:
      GcdAttributes = EFI_MEMORY_WC;
      break;
    case TT_ATTR_INDX_MEMORY_WRITE_THROUGH:
      GcdAttributes = EFI_MEMORY_WT;
      break;
    case TT_ATTR_INDX_MEMORY_WRITE_BACK:
      GcdAttributes = EFI_MEMORY_WB;
      break;
    default:
      DEBUG ((
        DEBUG_ERROR,
        "PageAttributeToGcdAttribute: PageAttributes:0x%lX not supported.\n",
        PageAttributes
        ));
      ASSERT (0);
      // The Global Coherency Domain (GCD) value is defined as a bit set.
      // Returning 0 means no attribute has been set.
      GcdAttributes = 0;
  }
  // Determine protection attributes
  if (((PageAttributes & TT_AP_MASK) == TT_AP_NO_RO) ||
      ((PageAttributes & TT_AP_MASK) == TT_AP_RO_RO))
  {
    // Read only cases map to write-protect
    GcdAttributes |= EFI_MEMORY_RO;
  }
  // Process eXecute Never attribute
  if ((PageAttributes & (TT_PXN_MASK | TT_UXN_MASK)) != 0) {
    GcdAttributes |= EFI_MEMORY_XP;
  }
  return GcdAttributes;
}
STATIC
UINT64
GetFirstPageAttribute (
  IN UINT64  *FirstLevelTableAddress,
  IN UINTN   TableLevel
  )
{
  UINT64  FirstEntry;
  // Get the first entry of the table
  FirstEntry = *FirstLevelTableAddress;
  if ((TableLevel != 3) && ((FirstEntry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY)) {
    // Only valid for Levels 0, 1 and 2
    // Get the attribute of the subsequent table
    return GetFirstPageAttribute ((UINT64 *)(FirstEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE), TableLevel + 1);
  } else if (((FirstEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY) ||
             ((TableLevel == 3) && ((FirstEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY_LEVEL3)))
  {
    return FirstEntry & TT_ATTR_INDX_MASK;
  } else {
    return INVALID_ENTRY;
  }
}
STATIC
UINT64
GetNextEntryAttribute (
  IN     UINT64  *TableAddress,
  IN     UINTN   EntryCount,
  IN     UINTN   TableLevel,
  IN     UINT64  BaseAddress,
  IN OUT UINT32  *PrevEntryAttribute,
  IN OUT UINT64  *StartGcdRegion
  )
{
  UINTN                            Index;
  UINT64                           Entry;
  UINT32                           EntryAttribute;
  UINT32                           EntryType;
  EFI_STATUS                       Status;
  UINTN                            NumberOfDescriptors;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR  *MemorySpaceMap;
  // Get the memory space map from GCD
  MemorySpaceMap = NULL;
  Status         = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);
  ASSERT_EFI_ERROR (Status);
  // We cannot get more than 3-level page table
  ASSERT (TableLevel <= 3);
  // While the top level table might not contain TT_ENTRY_COUNT entries;
  // the subsequent ones should be filled up
  for (Index = 0; Index < EntryCount; Index++) {
    Entry          = TableAddress[Index];
    EntryType      = Entry & TT_TYPE_MASK;
    EntryAttribute = Entry  & TT_ATTR_INDX_MASK;
    // If Entry is a Table Descriptor type entry then go through the sub-level table
    if ((EntryType == TT_TYPE_BLOCK_ENTRY) ||
        ((TableLevel == 3) && (EntryType == TT_TYPE_BLOCK_ENTRY_LEVEL3)))
    {
      if ((*PrevEntryAttribute == INVALID_ENTRY) || (EntryAttribute != *PrevEntryAttribute)) {
        if (*PrevEntryAttribute != INVALID_ENTRY) {
          // Update GCD with the last region
          SetGcdMemorySpaceAttributes (
            MemorySpaceMap,
            NumberOfDescriptors,
            *StartGcdRegion,
            (BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))) - *StartGcdRegion,
            PageAttributeToGcdAttribute (*PrevEntryAttribute)
            );
        }
        // Start of the new region
        *StartGcdRegion     = BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel));
        *PrevEntryAttribute = EntryAttribute;
      } else {
        continue;
      }
    } else if (EntryType == TT_TYPE_TABLE_ENTRY) {
      // Table Entry type is only valid for Level 0, 1, 2
      ASSERT (TableLevel < 3);
      // Increase the level number and scan the sub-level table
      GetNextEntryAttribute (
        (UINT64 *)(Entry & TT_ADDRESS_MASK_DESCRIPTION_TABLE),
        TT_ENTRY_COUNT,
        TableLevel + 1,
        (BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))),
        PrevEntryAttribute,
        StartGcdRegion
        );
    } else {
      if (*PrevEntryAttribute != INVALID_ENTRY) {
        // Update GCD with the last region
        SetGcdMemorySpaceAttributes (
          MemorySpaceMap,
          NumberOfDescriptors,
          *StartGcdRegion,
          (BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))) - *StartGcdRegion,
          PageAttributeToGcdAttribute (*PrevEntryAttribute)
          );
        // Start of the new region
        *StartGcdRegion     = BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel));
        *PrevEntryAttribute = INVALID_ENTRY;
      }
    }
  }
  FreePool (MemorySpaceMap);
  return BaseAddress + (EntryCount * TT_ADDRESS_AT_LEVEL (TableLevel));
}
EFI_STATUS
SyncCacheConfig (
  IN  EFI_CPU_ARCH_PROTOCOL  *CpuProtocol
  )
{
  EFI_STATUS                       Status;
  UINT32                           PageAttribute;
  UINT64                           *FirstLevelTableAddress;
  UINTN                            TableLevel;
  UINTN                            TableCount;
  UINTN                            NumberOfDescriptors;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR  *MemorySpaceMap;
  UINTN                            Tcr;
  UINTN                            T0SZ;
  UINT64                           BaseAddressGcdRegion;
  UINT64                           EndAddressGcdRegion;
  // This code assumes MMU is enabled and filed with section translations
  ASSERT (ArmMmuEnabled ());
  //
  // Get the memory space map from GCD
  //
  MemorySpaceMap = NULL;
  Status         = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);
  ASSERT_EFI_ERROR (Status);
  // The GCD implementation maintains its own copy of the state of memory space attributes.  GCD needs
  // to know what the initial memory space attributes are.  The CPU Arch. Protocol does not provide a
  // GetMemoryAttributes function for GCD to get this so we must resort to calling GCD (as if we were
  // a client) to update its copy of the attributes.  This is bad architecture and should be replaced
  // with a way for GCD to query the CPU Arch. driver of the existing memory space attributes instead.
  // Obtain page table base
  FirstLevelTableAddress = (UINT64 *)(ArmGetTTBR0BaseAddress ());
  // Get Translation Control Register value
  Tcr = ArmGetTCR ();
  // Get Address Region Size
  T0SZ = Tcr & TCR_T0SZ_MASK;
  // Get the level of the first table for the indicated Address Region Size
  GetRootTranslationTableInfo (T0SZ, &TableLevel, &TableCount);
  // First Attribute of the Page Tables
  PageAttribute = GetFirstPageAttribute (FirstLevelTableAddress, TableLevel);
  // We scan from the start of the memory map (ie: at the address 0x0)
  BaseAddressGcdRegion = 0x0;
  EndAddressGcdRegion  = GetNextEntryAttribute (
                           FirstLevelTableAddress,
                           TableCount,
                           TableLevel,
                           BaseAddressGcdRegion,
                           &PageAttribute,
                           &BaseAddressGcdRegion
                           );
  // Update GCD with the last region if valid
  if (PageAttribute != INVALID_ENTRY) {
    SetGcdMemorySpaceAttributes (
      MemorySpaceMap,
      NumberOfDescriptors,
      BaseAddressGcdRegion,
      EndAddressGcdRegion - BaseAddressGcdRegion,
      PageAttributeToGcdAttribute (PageAttribute)
      );
  }
  FreePool (MemorySpaceMap);
  return EFI_SUCCESS;
}
UINT64
EfiAttributeToArmAttribute (
  IN UINT64  EfiAttributes
  )
{
  UINT64  ArmAttributes;
  switch (EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) {
    case EFI_MEMORY_UC:
      if (ArmReadCurrentEL () == AARCH64_EL2) {
        ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY | TT_XN_MASK;
      } else {
        ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY | TT_UXN_MASK | TT_PXN_MASK;
      }
      break;
    case EFI_MEMORY_WC:
      ArmAttributes = TT_ATTR_INDX_MEMORY_NON_CACHEABLE;
      break;
    case EFI_MEMORY_WT:
      ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;
      break;
    case EFI_MEMORY_WB:
      ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;
      break;
    default:
      ArmAttributes = TT_ATTR_INDX_MASK;
  }
  // Set the access flag to match the block attributes
  ArmAttributes |= TT_AF;
  // Determine protection attributes
  if ((EfiAttributes & EFI_MEMORY_RO) != 0) {
    ArmAttributes |= TT_AP_NO_RO;
  }
  // Process eXecute Never attribute
  if ((EfiAttributes & EFI_MEMORY_XP) != 0) {
    ArmAttributes |= TT_PXN_MASK;
  }
  return ArmAttributes;
}
// This function will recursively go down the page table to find the first block address linked to 'BaseAddress'.
// And then the function will identify the size of the region that has the same page table attribute.
EFI_STATUS
GetMemoryRegionRec (
  IN     UINT64  *TranslationTable,
  IN     UINTN   TableLevel,
  IN     UINT64  *LastBlockEntry,
  IN OUT UINTN   *BaseAddress,
  OUT    UINTN   *RegionLength,
  OUT    UINTN   *RegionAttributes
  )
{
  EFI_STATUS  Status;
  UINT64      *NextTranslationTable;
  UINT64      *BlockEntry;
  UINT64      BlockEntryType;
  UINT64      EntryType;
  if (TableLevel != 3) {
    BlockEntryType = TT_TYPE_BLOCK_ENTRY;
  } else {
    BlockEntryType = TT_TYPE_BLOCK_ENTRY_LEVEL3;
  }
  // Find the block entry linked to the Base Address
  BlockEntry = (UINT64 *)TT_GET_ENTRY_FOR_ADDRESS (TranslationTable, TableLevel, *BaseAddress);
  EntryType  = *BlockEntry & TT_TYPE_MASK;
  if ((TableLevel < 3) && (EntryType == TT_TYPE_TABLE_ENTRY)) {
    NextTranslationTable = (UINT64 *)(*BlockEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE);
    // The entry is a page table, so we go to the next level
    Status = GetMemoryRegionRec (
               NextTranslationTable, // Address of the next level page table
               TableLevel + 1,       // Next Page Table level
               (UINTN *)TT_LAST_BLOCK_ADDRESS (NextTranslationTable, TT_ENTRY_COUNT),
               BaseAddress,
               RegionLength,
               RegionAttributes
               );
    // In case of 'Success', it means the end of the block region has been found into the upper
    // level translation table
    if (!EFI_ERROR (Status)) {
      return EFI_SUCCESS;
    }
    // Now we processed the table move to the next entry
    BlockEntry++;
  } else if (EntryType == BlockEntryType) {
    // We have found the BlockEntry attached to the address. We save its start address (the start
    // address might be before the 'BaseAddress') and attributes
    *BaseAddress      = *BaseAddress & ~(TT_ADDRESS_AT_LEVEL (TableLevel) - 1);
    *RegionLength     = 0;
    *RegionAttributes = *BlockEntry & TT_ATTRIBUTES_MASK;
  } else {
    // We have an 'Invalid' entry
    return EFI_UNSUPPORTED;
  }
  while (BlockEntry <= LastBlockEntry) {
    if ((*BlockEntry & TT_ATTRIBUTES_MASK) == *RegionAttributes) {
      *RegionLength = *RegionLength + TT_BLOCK_ENTRY_SIZE_AT_LEVEL (TableLevel);
    } else {
      // In case we have found the end of the region we return success
      return EFI_SUCCESS;
    }
    BlockEntry++;
  }
  // If we have reached the end of the TranslationTable and we have not found the end of the region then
  // we return EFI_NOT_FOUND.
  // The caller will continue to look for the memory region at its level
  return EFI_NOT_FOUND;
}
EFI_STATUS
GetMemoryRegion (
  IN OUT UINTN  *BaseAddress,
  OUT    UINTN  *RegionLength,
  OUT    UINTN  *RegionAttributes
  )
{
  EFI_STATUS  Status;
  UINT64      *TranslationTable;
  UINTN       TableLevel;
  UINTN       EntryCount;
  UINTN       T0SZ;
  ASSERT ((BaseAddress != NULL) && (RegionLength != NULL) && (RegionAttributes != NULL));
  TranslationTable = ArmGetTTBR0BaseAddress ();
  T0SZ = ArmGetTCR () & TCR_T0SZ_MASK;
  // Get the Table info from T0SZ
  GetRootTranslationTableInfo (T0SZ, &TableLevel, &EntryCount);
  Status = GetMemoryRegionRec (
             TranslationTable,
             TableLevel,
             (UINTN *)TT_LAST_BLOCK_ADDRESS (TranslationTable, EntryCount),
             BaseAddress,
             RegionLength,
             RegionAttributes
             );
  // If the region continues up to the end of the root table then GetMemoryRegionRec()
  // will return EFI_NOT_FOUND
  if (Status == EFI_NOT_FOUND) {
    return EFI_SUCCESS;
  } else {
    return Status;
  }
}