| /** @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; | |
| } |