/** @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
#define RISCV_CPU_FEATURE_PBMT_BITMASK BIT2
#define PTE_PBMT_NC BIT61
#define PTE_PBMT_IO BIT62
#define PTE_PBMT_MASK (PTE_PBMT_NC | PTE_PBMT_IO)
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
UINT64
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 UINT64 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
UINT64
SetValidPte (
IN UINT64 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 UINT64 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 UINT64 Entry
)
{
return IsValidPte (Entry) &&
!IsBlockEntry (Entry);
}
/**
Set an entry to be a table pte.
@param Entry The entry value.
@return The entry value.
**/
STATIC
UINT64
SetTableEntry (
IN UINT64 Entry
)
{
Entry = SetValidPte (Entry);
Entry &= ~(UINT64)(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 UINT64 *Entry,
IN UINT64 Value,
IN UINT64 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
UINT64
GetPpnfromPte (
IN UINT64 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
UINT64
SetPpnToPte (
UINT64 Entry,
UINT64 Address
)
{
UINT64 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 UINT64 *TranslationTable,
IN UINTN Level
)
{
UINTN Index;
if (Level < mMaxRootTableLevel - 1) {
for (Index = 0; Index < mTableEntryCount; Index++) {
if (IsTableEntry (TranslationTable[Index])) {
FreePageTablesRecursive (
(UINT64 *)(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 UINT64 RegionStart,
IN UINT64 RegionEnd,
IN UINT64 AttributeSetMask,
IN UINT64 AttributeClearMask,
IN UINT64 *PageTable,
IN UINTN Level,
IN BOOLEAN TableIsLive
)
{
EFI_STATUS Status;
UINT64 BlockShift;
UINT64 BlockMask;
UINT64 BlockEnd;
UINT64 *Entry;
UINT64 EntryValue;
UINT64 *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): %LX - %LX 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 = (UINT64 *)(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, (UINT64)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 UINT64 RegionStart,
IN UINT64 RegionLength,
IN UINT64 AttributeSetMask,
IN UINT64 AttributeClearMask,
IN UINT64 *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.
@param RiscVAttributes The pointer of RISC-V page attribute.
@retval EFI_INVALID_PARAMETER The RiscVAttributes is NULL or cache type mask not valid.
@retval EFI_SUCCESS The operation succesfully.
**/
STATIC
EFI_STATUS
GcdAttributeToPageAttribute (
IN UINT64 GcdAttributes,
OUT UINT64 *RiscVAttributes
)
{
UINT64 CacheTypeMask;
BOOLEAN PmbtExtEnabled;
if (RiscVAttributes == NULL) {
return EFI_INVALID_PARAMETER;
}
*RiscVAttributes = RISCV_PG_R | RISCV_PG_W | RISCV_PG_X;
PmbtExtEnabled = FALSE;
if ((PcdGet64 (PcdRiscVFeatureOverride) & RISCV_CPU_FEATURE_PBMT_BITMASK) != 0) {
PmbtExtEnabled = TRUE;
}
// Determine protection attributes
if ((GcdAttributes & EFI_MEMORY_RO) != 0) {
*RiscVAttributes &= ~(UINT64)(RISCV_PG_W);
}
// Process eXecute Never attribute
if ((GcdAttributes & EFI_MEMORY_XP) != 0) {
*RiscVAttributes &= ~(UINT64)RISCV_PG_X;
}
CacheTypeMask = GcdAttributes & EFI_CACHE_ATTRIBUTE_MASK;
if ((CacheTypeMask != 0) &&
(((CacheTypeMask - 1) & CacheTypeMask) != 0))
{
DEBUG ((
DEBUG_ERROR,
"%a: More than one bit set in cache type mask (0x%LX)\n",
__func__,
CacheTypeMask
));
return EFI_INVALID_PARAMETER;
}
switch (CacheTypeMask) {
case EFI_MEMORY_UC:
if (PmbtExtEnabled) {
*RiscVAttributes |= PTE_PBMT_IO;
}
break;
case EFI_MEMORY_WC:
if (PmbtExtEnabled) {
*RiscVAttributes |= PTE_PBMT_NC;
} else {
DEBUG ((
DEBUG_VERBOSE,
"%a: EFI_MEMORY_WC set but Pmbt extension not available\n",
__func__
));
}
break;
default:
// Default PMA mode
break;
}
return EFI_SUCCESS;
}
/**
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 UINT64 Length,
IN UINT64 Attributes
)
{
UINT64 PageAttributesSet;
UINT64 PageAttributesClear;
EFI_STATUS Status;
Status = GcdAttributeToPageAttribute (Attributes, &PageAttributesSet);
if (EFI_ERROR (Status)) {
return Status;
}
if (!RiscVMmuEnabled ()) {
return EFI_SUCCESS;
}
PageAttributesClear = PTE_ATTRIBUTES_MASK;
if ((PcdGet64 (PcdRiscVFeatureOverride) & RISCV_CPU_FEATURE_PBMT_BITMASK) != 0) {
PageAttributesClear |= PTE_PBMT_MASK;
}
DEBUG ((
DEBUG_VERBOSE,
"%a: %LX: set attributes 0x%LX, clear attributes 0x%LX\n",
__func__,
BaseAddress,
PageAttributesSet,
PageAttributesClear
));
return UpdateRegionMapping (
BaseAddress,
Length,
PageAttributesSet,
PageAttributesClear,
(UINT64 *)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;
UINT64 SatpReg;
UINT64 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 (UINT64));
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 = (UINT64)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;
UINTN 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;
}