From f220dcbba86bfc1222180c61bbd31dd6023433db Mon Sep 17 00:00:00 2001 From: Tuan Phan Date: Fri, 14 Jul 2023 12:08:24 -0700 Subject: UefiCpuPkg: RISC-V: Support MMU with SV39/48/57 mode During CpuDxe initialization, MMU will be setup with the highest mode that HW supports. Signed-off-by: Tuan Phan Reviewed-by: Andrei Warkentin Reviewed-by: Sunil V L --- .../Library/BaseRiscVMmuLib/BaseRiscVMmuLib.c | 730 +++++++++++++++++++++ .../Library/BaseRiscVMmuLib/BaseRiscVMmuLib.inf | 27 + UefiCpuPkg/Library/BaseRiscVMmuLib/RiscVMmuCore.S | 31 + 3 files changed, 788 insertions(+) create mode 100644 UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.c create mode 100644 UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.inf create mode 100644 UefiCpuPkg/Library/BaseRiscVMmuLib/RiscVMmuCore.S (limited to 'UefiCpuPkg/Library') diff --git a/UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.c b/UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.c new file mode 100644 index 0000000..9cca5fc --- /dev/null +++ b/UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.c @@ -0,0 +1,730 @@ +/** @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.
+ Copyright (c) 2023, Ventana Micro Systems Inc. All Rights Reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 }; +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; + + 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; +} diff --git a/UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.inf b/UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.inf new file mode 100644 index 0000000..9b28a98 --- /dev/null +++ b/UefiCpuPkg/Library/BaseRiscVMmuLib/BaseRiscVMmuLib.inf @@ -0,0 +1,27 @@ +## @file +# RISC-V MMU library. +# +# Copyright (c) 2023, Ventana Micro Systems Inc. All Rights Reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001b + BASE_NAME = BaseRiscVMmuLib + FILE_GUID = d3bc42ee-c9eb-4339-ba11-06747083d3ae + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = RiscVMmuLib + +[Sources] + BaseRiscVMmuLib.c + RiscVMmuCore.S + +[Packages] + MdePkg/MdePkg.dec + UefiCpuPkg/UefiCpuPkg.dec + +[LibraryClasses] + BaseLib diff --git a/UefiCpuPkg/Library/BaseRiscVMmuLib/RiscVMmuCore.S b/UefiCpuPkg/Library/BaseRiscVMmuLib/RiscVMmuCore.S new file mode 100644 index 0000000..42eec4c --- /dev/null +++ b/UefiCpuPkg/Library/BaseRiscVMmuLib/RiscVMmuCore.S @@ -0,0 +1,31 @@ +/** @file +* +* Copyright (c) 2023, Ventana Micro Systems Inc. All Rights Reserved.
+* +* SPDX-License-Identifier: BSD-2-Clause-Patent +* +**/ + +#include +#include + +.text + .align 3 + +// +// Local tlb flush all. +// +// +ASM_FUNC (RiscVLocalTlbFlushAll) +sfence.vma +ret + +// +// Local tlb flush at a virtual address +// @retval a0 : virtual address. +// +ASM_FUNC ( + RiscVLocalTlbFlush + ) +sfence.vma a0 +ret -- cgit v1.1