Introduce a PCD to control the maximum SATP mode that MMU allowed to use. This PCD helps RISC-V platform set bare or minimum SATP mode during bring up to debug memory map issue. Signed-off-by: Tuan Phan <tphan@ventanamicro.com> Reviewed-by: Dhaval Sharma <dhaval@rivosinc.com> Reviewed-by: Andrei Warkentin <andrei.warkentin@intel.com> Reviewed-by: Sunil V L <sunilvl@ventanamicro.com>
		
			
				
	
	
		
			735 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			735 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /** @file
 | |
|   MMU library for RISC-V.
 | |
| 
 | |
|   Copyright (c) 2011-2020, ARM Limited. All rights reserved.
 | |
|   Copyright (c) 2016, Linaro Limited. All rights reserved.
 | |
|   Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
 | |
|   Copyright (c) 2023, Ventana Micro Systems Inc. All Rights Reserved.<BR>
 | |
| 
 | |
|   SPDX-License-Identifier: BSD-2-Clause-Patent
 | |
| 
 | |
| **/
 | |
| 
 | |
| #include <PiDxe.h>
 | |
| #include <Uefi.h>
 | |
| #include <Library/BaseLib.h>
 | |
| #include <Library/BaseMemoryLib.h>
 | |
| #include <Library/BaseRiscVMmuLib.h>
 | |
| #include <Library/CacheMaintenanceLib.h>
 | |
| #include <Library/DebugLib.h>
 | |
| #include <Library/DxeServicesTableLib.h>
 | |
| #include <Library/UefiBootServicesTableLib.h>
 | |
| #include <Library/MemoryAllocationLib.h>
 | |
| #include <Library/PcdLib.h>
 | |
| #include <Register/RiscV64/RiscVEncoding.h>
 | |
| 
 | |
| #define RISCV_PG_V           BIT0
 | |
| #define RISCV_PG_R           BIT1
 | |
| #define RISCV_PG_W           BIT2
 | |
| #define RISCV_PG_X           BIT3
 | |
| #define RISCV_PG_G           BIT5
 | |
| #define RISCV_PG_A           BIT6
 | |
| #define RISCV_PG_D           BIT7
 | |
| #define PTE_ATTRIBUTES_MASK  0xE
 | |
| 
 | |
| #define PTE_PPN_MASK          0x3FFFFFFFFFFC00ULL
 | |
| #define PTE_PPN_SHIFT         10
 | |
| #define RISCV_MMU_PAGE_SHIFT  12
 | |
| 
 | |
| STATIC UINTN  mModeSupport[] = { SATP_MODE_SV57, SATP_MODE_SV48, SATP_MODE_SV39, SATP_MODE_OFF };
 | |
| STATIC UINTN  mMaxRootTableLevel;
 | |
| STATIC UINTN  mBitPerLevel;
 | |
| STATIC UINTN  mTableEntryCount;
 | |
| 
 | |
| /**
 | |
|   Determine if the MMU enabled or not.
 | |
| 
 | |
|   @retval TRUE  The MMU already enabled.
 | |
|   @retval FALSE The MMU not enabled.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| BOOLEAN
 | |
| RiscVMmuEnabled (
 | |
|   VOID
 | |
|   )
 | |
| {
 | |
|   return ((RiscVGetSupervisorAddressTranslationRegister () &
 | |
|            SATP64_MODE) != (SATP_MODE_OFF << SATP64_MODE_SHIFT));
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Retrieve the root translate table.
 | |
| 
 | |
|   @return The root translate table.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| UINTN
 | |
| RiscVGetRootTranslateTable (
 | |
|   VOID
 | |
|   )
 | |
| {
 | |
|   return (RiscVGetSupervisorAddressTranslationRegister () & SATP64_PPN) <<
 | |
|          RISCV_MMU_PAGE_SHIFT;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Determine if an entry is valid pte.
 | |
| 
 | |
|   @param    Entry   The entry value.
 | |
| 
 | |
|   @retval   TRUE    The entry is a valid pte.
 | |
|   @retval   FALSE   The entry is not a valid pte.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| BOOLEAN
 | |
| IsValidPte (
 | |
|   IN  UINTN  Entry
 | |
|   )
 | |
| {
 | |
|   if (((Entry & RISCV_PG_V) == 0) ||
 | |
|       (((Entry & (RISCV_PG_R | RISCV_PG_W)) == RISCV_PG_W)))
 | |
|   {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Set an entry to be a valid pte.
 | |
| 
 | |
|   @param  Entry   The entry value.
 | |
| 
 | |
|   @return         The entry value.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| UINTN
 | |
| SetValidPte (
 | |
|   IN  UINTN  Entry
 | |
|   )
 | |
| {
 | |
|   /* Set Valid and Global mapping bits */
 | |
|   return Entry | RISCV_PG_G | RISCV_PG_V;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Determine if an entry is a block pte.
 | |
| 
 | |
|   @param    Entry   The entry value.
 | |
| 
 | |
|   @retval   TRUE    The entry is a block pte.
 | |
|   @retval   FALSE   The entry is not a block pte.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| BOOLEAN
 | |
| IsBlockEntry (
 | |
|   IN  UINTN  Entry
 | |
|   )
 | |
| {
 | |
|   return IsValidPte (Entry) &&
 | |
|          (Entry & (RISCV_PG_X | RISCV_PG_R));
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Determine if an entry is a table pte.
 | |
| 
 | |
|   @param    Entry   The entry value.
 | |
| 
 | |
|   @retval   TRUE    The entry is a table pte.
 | |
|   @retval   FALSE   The entry is not a table pte.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| BOOLEAN
 | |
| IsTableEntry (
 | |
|   IN  UINTN  Entry
 | |
|   )
 | |
| {
 | |
|   return IsValidPte (Entry) &&
 | |
|          !IsBlockEntry (Entry);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Set an entry to be a table pte.
 | |
| 
 | |
|   @param  Entry   The entry value.
 | |
| 
 | |
|   @return         The entry value.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| UINTN
 | |
| SetTableEntry (
 | |
|   IN  UINTN  Entry
 | |
|   )
 | |
| {
 | |
|   Entry  = SetValidPte (Entry);
 | |
|   Entry &= ~(RISCV_PG_X | RISCV_PG_W | RISCV_PG_R);
 | |
| 
 | |
|   return Entry;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Replace an existing entry with new value.
 | |
| 
 | |
|   @param  Entry               The entry pointer.
 | |
|   @param  Value               The new entry value.
 | |
|   @param  RegionStart         The start of region that new value affects.
 | |
|   @param  IsLiveBlockMapping  TRUE if this is live update, FALSE otherwise.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| VOID
 | |
| ReplaceTableEntry (
 | |
|   IN  UINTN    *Entry,
 | |
|   IN  UINTN    Value,
 | |
|   IN  UINTN    RegionStart,
 | |
|   IN  BOOLEAN  IsLiveBlockMapping
 | |
|   )
 | |
| {
 | |
|   *Entry = Value;
 | |
| 
 | |
|   if (IsLiveBlockMapping && RiscVMmuEnabled ()) {
 | |
|     RiscVLocalTlbFlush (RegionStart);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Get an ppn value from an entry.
 | |
| 
 | |
|   @param  Entry   The entry value.
 | |
| 
 | |
|   @return         The ppn value.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| UINTN
 | |
| GetPpnfromPte (
 | |
|   IN UINTN  Entry
 | |
|   )
 | |
| {
 | |
|   return ((Entry & PTE_PPN_MASK) >> PTE_PPN_SHIFT);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Set an ppn value to a entry.
 | |
| 
 | |
|   @param  Entry   The entry value.
 | |
|   @param  Address The address.
 | |
| 
 | |
|   @return The new entry value.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| UINTN
 | |
| SetPpnToPte (
 | |
|   UINTN  Entry,
 | |
|   UINTN  Address
 | |
|   )
 | |
| {
 | |
|   UINTN  Ppn;
 | |
| 
 | |
|   Ppn = ((Address >> RISCV_MMU_PAGE_SHIFT) << PTE_PPN_SHIFT);
 | |
|   ASSERT (~(Ppn & ~PTE_PPN_MASK));
 | |
|   Entry &= ~PTE_PPN_MASK;
 | |
|   return Entry | Ppn;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Free resources of translation table recursively.
 | |
| 
 | |
|   @param  TranslationTable  The pointer of table.
 | |
|   @param  Level             The current level.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| VOID
 | |
| FreePageTablesRecursive (
 | |
|   IN  UINTN  *TranslationTable,
 | |
|   IN  UINTN  Level
 | |
|   )
 | |
| {
 | |
|   UINTN  Index;
 | |
| 
 | |
|   if (Level < mMaxRootTableLevel - 1) {
 | |
|     for (Index = 0; Index < mTableEntryCount; Index++) {
 | |
|       if (IsTableEntry (TranslationTable[Index])) {
 | |
|         FreePageTablesRecursive (
 | |
|           (UINTN *)(GetPpnfromPte ((TranslationTable[Index])) <<
 | |
|                     RISCV_MMU_PAGE_SHIFT),
 | |
|           Level + 1
 | |
|           );
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   FreePages (TranslationTable, 1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Update region mapping recursively.
 | |
| 
 | |
|   @param  RegionStart           The start address of the region.
 | |
|   @param  RegionEnd             The end address of the region.
 | |
|   @param  AttributeSetMask      The attribute mask to be set.
 | |
|   @param  AttributeClearMask    The attribute mask to be clear.
 | |
|   @param  PageTable             The pointer of current page table.
 | |
|   @param  Level                 The current level.
 | |
|   @param  TableIsLive           TRUE if this is live update, FALSE otherwise.
 | |
| 
 | |
|   @retval EFI_OUT_OF_RESOURCES  Not enough resource.
 | |
|   @retval EFI_SUCCESS           The operation succesfully.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| EFI_STATUS
 | |
| UpdateRegionMappingRecursive (
 | |
|   IN  UINTN    RegionStart,
 | |
|   IN  UINTN    RegionEnd,
 | |
|   IN  UINTN    AttributeSetMask,
 | |
|   IN  UINTN    AttributeClearMask,
 | |
|   IN  UINTN    *PageTable,
 | |
|   IN  UINTN    Level,
 | |
|   IN  BOOLEAN  TableIsLive
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS  Status;
 | |
|   UINTN       BlockShift;
 | |
|   UINTN       BlockMask;
 | |
|   UINTN       BlockEnd;
 | |
|   UINTN       *Entry;
 | |
|   UINTN       EntryValue;
 | |
|   UINTN       *TranslationTable;
 | |
|   BOOLEAN     NextTableIsLive;
 | |
| 
 | |
|   ASSERT (Level < mMaxRootTableLevel);
 | |
|   ASSERT (((RegionStart | RegionEnd) & EFI_PAGE_MASK) == 0);
 | |
| 
 | |
|   BlockShift = (mMaxRootTableLevel - Level - 1) * mBitPerLevel + RISCV_MMU_PAGE_SHIFT;
 | |
|   BlockMask  = MAX_ADDRESS >> (64 - BlockShift);
 | |
| 
 | |
|   DEBUG (
 | |
|     (
 | |
|      DEBUG_VERBOSE,
 | |
|      "%a(%d): %llx - %llx set %lx clr %lx\n",
 | |
|      __func__,
 | |
|      Level,
 | |
|      RegionStart,
 | |
|      RegionEnd,
 | |
|      AttributeSetMask,
 | |
|      AttributeClearMask
 | |
|     )
 | |
|     );
 | |
| 
 | |
|   for ( ; RegionStart < RegionEnd; RegionStart = BlockEnd) {
 | |
|     BlockEnd = MIN (RegionEnd, (RegionStart | BlockMask) + 1);
 | |
|     Entry    = &PageTable[(RegionStart >> BlockShift) & (mTableEntryCount - 1)];
 | |
| 
 | |
|     //
 | |
|     // If RegionStart or BlockEnd is not aligned to the block size at this
 | |
|     // level, we will have to create a table mapping in order to map less
 | |
|     // than a block, and recurse to create the block or page entries at
 | |
|     // the next level. No block mappings are allowed at all at level 0,
 | |
|     // so in that case, we have to recurse unconditionally.
 | |
|     //
 | |
|     if ((Level == 0) ||
 | |
|         (((RegionStart | BlockEnd) & BlockMask) != 0) || IsTableEntry (*Entry))
 | |
|     {
 | |
|       ASSERT (Level < mMaxRootTableLevel - 1);
 | |
|       if (!IsTableEntry (*Entry)) {
 | |
|         //
 | |
|         // No table entry exists yet, so we need to allocate a page table
 | |
|         // for the next level.
 | |
|         //
 | |
|         TranslationTable = AllocatePages (1);
 | |
|         if (TranslationTable == NULL) {
 | |
|           return EFI_OUT_OF_RESOURCES;
 | |
|         }
 | |
| 
 | |
|         ZeroMem (TranslationTable, EFI_PAGE_SIZE);
 | |
| 
 | |
|         if (IsBlockEntry (*Entry)) {
 | |
|           //
 | |
|           // We are splitting an existing block entry, so we have to populate
 | |
|           // the new table with the attributes of the block entry it replaces.
 | |
|           //
 | |
|           Status = UpdateRegionMappingRecursive (
 | |
|                      RegionStart & ~BlockMask,
 | |
|                      (RegionStart | BlockMask) + 1,
 | |
|                      *Entry & PTE_ATTRIBUTES_MASK,
 | |
|                      PTE_ATTRIBUTES_MASK,
 | |
|                      TranslationTable,
 | |
|                      Level + 1,
 | |
|                      FALSE
 | |
|                      );
 | |
|           if (EFI_ERROR (Status)) {
 | |
|             //
 | |
|             // The range we passed to UpdateRegionMappingRecursive () is block
 | |
|             // aligned, so it is guaranteed that no further pages were allocated
 | |
|             // by it, and so we only have to free the page we allocated here.
 | |
|             //
 | |
|             FreePages (TranslationTable, 1);
 | |
|             return Status;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         NextTableIsLive = FALSE;
 | |
|       } else {
 | |
|         TranslationTable = (UINTN *)(GetPpnfromPte (*Entry) << RISCV_MMU_PAGE_SHIFT);
 | |
|         NextTableIsLive  = TableIsLive;
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // Recurse to the next level
 | |
|       //
 | |
|       Status = UpdateRegionMappingRecursive (
 | |
|                  RegionStart,
 | |
|                  BlockEnd,
 | |
|                  AttributeSetMask,
 | |
|                  AttributeClearMask,
 | |
|                  TranslationTable,
 | |
|                  Level + 1,
 | |
|                  NextTableIsLive
 | |
|                  );
 | |
|       if (EFI_ERROR (Status)) {
 | |
|         if (!IsTableEntry (*Entry)) {
 | |
|           //
 | |
|           // We are creating a new table entry, so on failure, we can free all
 | |
|           // allocations we made recursively, given that the whole subhierarchy
 | |
|           // has not been wired into the live page tables yet. (This is not
 | |
|           // possible for existing table entries, since we cannot revert the
 | |
|           // modifications we made to the subhierarchy it represents.)
 | |
|           //
 | |
|           FreePageTablesRecursive (TranslationTable, Level + 1);
 | |
|         }
 | |
| 
 | |
|         return Status;
 | |
|       }
 | |
| 
 | |
|       if (!IsTableEntry (*Entry)) {
 | |
|         EntryValue = SetPpnToPte (0, (UINTN)TranslationTable);
 | |
|         EntryValue = SetTableEntry (EntryValue);
 | |
|         ReplaceTableEntry (
 | |
|           Entry,
 | |
|           EntryValue,
 | |
|           RegionStart,
 | |
|           TableIsLive
 | |
|           );
 | |
|       }
 | |
|     } else {
 | |
|       EntryValue = (*Entry & ~AttributeClearMask) | AttributeSetMask;
 | |
|       //
 | |
|       // We don't have page fault exception handler when a virtual page is accessed and
 | |
|       // the A bit is clear, or is written and the D bit is clear.
 | |
|       // So just set A for read and D for write permission.
 | |
|       //
 | |
|       if ((AttributeSetMask & RISCV_PG_R) != 0) {
 | |
|         EntryValue |= RISCV_PG_A;
 | |
|       }
 | |
| 
 | |
|       if ((AttributeSetMask & RISCV_PG_W) != 0) {
 | |
|         EntryValue |= RISCV_PG_D;
 | |
|       }
 | |
| 
 | |
|       EntryValue = SetPpnToPte (EntryValue, RegionStart);
 | |
|       EntryValue = SetValidPte (EntryValue);
 | |
|       ReplaceTableEntry (Entry, EntryValue, RegionStart, TableIsLive);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return EFI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Update region mapping at root table.
 | |
| 
 | |
|   @param  RegionStart           The start address of the region.
 | |
|   @param  RegionLength          The length of the region.
 | |
|   @param  AttributeSetMask      The attribute mask to be set.
 | |
|   @param  AttributeClearMask    The attribute mask to be clear.
 | |
|   @param  RootTable             The pointer of root table.
 | |
|   @param  TableIsLive           TRUE if this is live update, FALSE otherwise.
 | |
| 
 | |
|   @retval EFI_INVALID_PARAMETER The RegionStart or RegionLength was not valid.
 | |
|   @retval EFI_OUT_OF_RESOURCES  Not enough resource.
 | |
|   @retval EFI_SUCCESS           The operation succesfully.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| EFI_STATUS
 | |
| UpdateRegionMapping (
 | |
|   IN  UINTN    RegionStart,
 | |
|   IN  UINTN    RegionLength,
 | |
|   IN  UINTN    AttributeSetMask,
 | |
|   IN  UINTN    AttributeClearMask,
 | |
|   IN  UINTN    *RootTable,
 | |
|   IN  BOOLEAN  TableIsLive
 | |
|   )
 | |
| {
 | |
|   if (((RegionStart | RegionLength) & EFI_PAGE_MASK) != 0) {
 | |
|     return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   return UpdateRegionMappingRecursive (
 | |
|            RegionStart,
 | |
|            RegionStart + RegionLength,
 | |
|            AttributeSetMask,
 | |
|            AttributeClearMask,
 | |
|            RootTable,
 | |
|            0,
 | |
|            TableIsLive
 | |
|            );
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Convert GCD attribute to RISC-V page attribute.
 | |
| 
 | |
|   @param  GcdAttributes The GCD attribute.
 | |
| 
 | |
|   @return               The RISC-V page attribute.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| UINTN
 | |
| GcdAttributeToPageAttribute (
 | |
|   IN UINTN  GcdAttributes
 | |
|   )
 | |
| {
 | |
|   UINTN  RiscVAttributes;
 | |
| 
 | |
|   RiscVAttributes = RISCV_PG_R | RISCV_PG_W | RISCV_PG_X;
 | |
| 
 | |
|   // Determine protection attributes
 | |
|   if ((GcdAttributes & EFI_MEMORY_RO) != 0) {
 | |
|     RiscVAttributes &= ~(RISCV_PG_W);
 | |
|   }
 | |
| 
 | |
|   // Process eXecute Never attribute
 | |
|   if ((GcdAttributes & EFI_MEMORY_XP) != 0) {
 | |
|     RiscVAttributes &= ~RISCV_PG_X;
 | |
|   }
 | |
| 
 | |
|   return RiscVAttributes;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   The API to set a GCD attribute on an memory region.
 | |
| 
 | |
|   @param  BaseAddress             The base address of the region.
 | |
|   @param  Length                  The length of the region.
 | |
|   @param  Attributes              The GCD attributes.
 | |
| 
 | |
|   @retval EFI_INVALID_PARAMETER   The BaseAddress or Length was not valid.
 | |
|   @retval EFI_OUT_OF_RESOURCES    Not enough resource.
 | |
|   @retval EFI_SUCCESS             The operation succesfully.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| EFIAPI
 | |
| RiscVSetMemoryAttributes (
 | |
|   IN EFI_PHYSICAL_ADDRESS  BaseAddress,
 | |
|   IN UINTN                 Length,
 | |
|   IN UINTN                 Attributes
 | |
|   )
 | |
| {
 | |
|   UINTN  PageAttributesSet;
 | |
| 
 | |
|   PageAttributesSet = GcdAttributeToPageAttribute (Attributes);
 | |
| 
 | |
|   if (!RiscVMmuEnabled ()) {
 | |
|     return EFI_SUCCESS;
 | |
|   }
 | |
| 
 | |
|   DEBUG (
 | |
|     (
 | |
|      DEBUG_VERBOSE,
 | |
|      "%a: Set %llX page attribute 0x%X\n",
 | |
|      __func__,
 | |
|      BaseAddress,
 | |
|      PageAttributesSet
 | |
|     )
 | |
|     );
 | |
| 
 | |
|   return UpdateRegionMapping (
 | |
|            BaseAddress,
 | |
|            Length,
 | |
|            PageAttributesSet,
 | |
|            PTE_ATTRIBUTES_MASK,
 | |
|            (UINTN *)RiscVGetRootTranslateTable (),
 | |
|            TRUE
 | |
|            );
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Set SATP mode.
 | |
| 
 | |
|   @param  SatpMode  The SATP mode to be set.
 | |
| 
 | |
|   @retval EFI_INVALID_PARAMETER   The SATP mode was not valid.
 | |
|   @retval EFI_OUT_OF_RESOURCES    Not enough resource.
 | |
|   @retval EFI_DEVICE_ERROR        The SATP mode not supported by HW.
 | |
|   @retval EFI_SUCCESS             The operation succesfully.
 | |
| 
 | |
| **/
 | |
| STATIC
 | |
| EFI_STATUS
 | |
| RiscVMmuSetSatpMode  (
 | |
|   UINTN  SatpMode
 | |
|   )
 | |
| {
 | |
|   VOID                             *TranslationTable;
 | |
|   UINTN                            SatpReg;
 | |
|   UINTN                            Ppn;
 | |
|   EFI_GCD_MEMORY_SPACE_DESCRIPTOR  *MemoryMap;
 | |
|   UINTN                            NumberOfDescriptors;
 | |
|   UINTN                            Index;
 | |
|   EFI_STATUS                       Status;
 | |
| 
 | |
|   if (SatpMode > PcdGet32 (PcdCpuRiscVMmuMaxSatpMode)) {
 | |
|     return EFI_DEVICE_ERROR;
 | |
|   }
 | |
| 
 | |
|   switch (SatpMode) {
 | |
|     case SATP_MODE_OFF:
 | |
|       return EFI_SUCCESS;
 | |
|     case SATP_MODE_SV39:
 | |
|       mMaxRootTableLevel = 3;
 | |
|       mBitPerLevel       = 9;
 | |
|       mTableEntryCount   = 512;
 | |
|       break;
 | |
|     case SATP_MODE_SV48:
 | |
|       mMaxRootTableLevel = 4;
 | |
|       mBitPerLevel       = 9;
 | |
|       mTableEntryCount   = 512;
 | |
|       break;
 | |
|     case SATP_MODE_SV57:
 | |
|       mMaxRootTableLevel = 5;
 | |
|       mBitPerLevel       = 9;
 | |
|       mTableEntryCount   = 512;
 | |
|       break;
 | |
|     default:
 | |
|       return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   // Allocate pages for translation table
 | |
|   TranslationTable = AllocatePages (1);
 | |
|   if (TranslationTable == NULL) {
 | |
|     return EFI_OUT_OF_RESOURCES;
 | |
|   }
 | |
| 
 | |
|   ZeroMem (TranslationTable, mTableEntryCount * sizeof (UINTN));
 | |
| 
 | |
|   NumberOfDescriptors = 0;
 | |
|   MemoryMap           = NULL;
 | |
|   Status              = gDS->GetMemorySpaceMap (
 | |
|                                &NumberOfDescriptors,
 | |
|                                &MemoryMap
 | |
|                                );
 | |
|   ASSERT_EFI_ERROR (Status);
 | |
| 
 | |
|   for (Index = 0; Index < NumberOfDescriptors; Index++) {
 | |
|     if (MemoryMap[Index].GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) {
 | |
|       // Default Read/Write attribute for memory mapped IO
 | |
|       UpdateRegionMapping (
 | |
|         MemoryMap[Index].BaseAddress,
 | |
|         MemoryMap[Index].Length,
 | |
|         RISCV_PG_R | RISCV_PG_W,
 | |
|         PTE_ATTRIBUTES_MASK,
 | |
|         TranslationTable,
 | |
|         FALSE
 | |
|         );
 | |
|     } else if (MemoryMap[Index].GcdMemoryType == EfiGcdMemoryTypeSystemMemory) {
 | |
|       // Default Read/Write/Execute attribute for system memory
 | |
|       UpdateRegionMapping (
 | |
|         MemoryMap[Index].BaseAddress,
 | |
|         MemoryMap[Index].Length,
 | |
|         RISCV_PG_R | RISCV_PG_W | RISCV_PG_X,
 | |
|         PTE_ATTRIBUTES_MASK,
 | |
|         TranslationTable,
 | |
|         FALSE
 | |
|         );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   FreePool ((VOID *)MemoryMap);
 | |
| 
 | |
|   if (GetInterruptState ()) {
 | |
|     DisableInterrupts ();
 | |
|   }
 | |
| 
 | |
|   Ppn = (UINTN)TranslationTable >> RISCV_MMU_PAGE_SHIFT;
 | |
|   ASSERT (!(Ppn & ~(SATP64_PPN)));
 | |
| 
 | |
|   SatpReg  = Ppn;
 | |
|   SatpReg |= (SatpMode <<
 | |
|               SATP64_MODE_SHIFT) & SATP64_MODE;
 | |
|   RiscVSetSupervisorAddressTranslationRegister (SatpReg);
 | |
|   /* Check if HW support the setup satp mode */
 | |
|   if (SatpReg != RiscVGetSupervisorAddressTranslationRegister ()) {
 | |
|     DEBUG (
 | |
|       (
 | |
|        DEBUG_VERBOSE,
 | |
|        "%a: HW does not support SATP mode:%d\n",
 | |
|        __func__,
 | |
|        SatpMode
 | |
|       )
 | |
|       );
 | |
|     FreePageTablesRecursive (TranslationTable, 0);
 | |
|     return EFI_DEVICE_ERROR;
 | |
|   }
 | |
| 
 | |
|   RiscVLocalTlbFlushAll ();
 | |
| 
 | |
|   if (GetInterruptState ()) {
 | |
|     EnableInterrupts ();
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   The API to configure and enable RISC-V MMU with the highest mode supported.
 | |
| 
 | |
|   @retval EFI_OUT_OF_RESOURCES    Not enough resource.
 | |
|   @retval EFI_SUCCESS             The operation succesfully.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| EFIAPI
 | |
| RiscVConfigureMmu (
 | |
|   VOID
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS  Status;
 | |
|   INTN        Idx;
 | |
| 
 | |
|   Status = EFI_SUCCESS;
 | |
| 
 | |
|   /* Try to setup MMU with highest mode as possible */
 | |
|   for (Idx = 0; Idx < ARRAY_SIZE (mModeSupport); Idx++) {
 | |
|     Status = RiscVMmuSetSatpMode (mModeSupport[Idx]);
 | |
|     if (Status == EFI_DEVICE_ERROR) {
 | |
|       continue;
 | |
|     } else if (EFI_ERROR (Status)) {
 | |
|       return Status;
 | |
|     }
 | |
| 
 | |
|     DEBUG (
 | |
|       (
 | |
|        DEBUG_INFO,
 | |
|        "%a: SATP mode %d successfully configured\n",
 | |
|        __func__,
 | |
|        mModeSupport[Idx]
 | |
|       )
 | |
|       );
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| }
 |