/** @file
CPU Memory Map Unit Handler Library common functions.
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.
Copyright (c) 2024 Loongson Technology Corporation Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "TlbInvalid.h"
#include "TlbExceptionHandle.h"
#include "Page.h"
/**
Check to see if mmu successfully initializes.
@param VOID.
@retval TRUE Initialization has been completed.
FALSE Initialization did not complete.
**/
STATIC
BOOLEAN
MmuIsInit (
VOID
)
{
if (CsrRead (LOONGARCH_CSR_PGDL) != 0) {
return TRUE;
}
return FALSE;
}
/**
Check to see if mmu is enabled.
@param VOID.
@retval TRUE MMU has been enabled.
FALSE MMU did not enabled.
**/
STATIC
BOOLEAN
MmuIsEnabled (
VOID
)
{
if ((CsrRead (LOONGARCH_CSR_CRMD) & BIT4) != 0) {
return TRUE;
}
return FALSE;
}
/**
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 != INVALID_PAGE) {
return TRUE;
} else {
return FALSE;
}
}
/**
Determine if an entry is huge page.
@param Entry The entry value.
@retval TRUE The entry is a huge page.
@retval FALSE The entry is not a valid huge page.
**/
STATIC
BOOLEAN
IsValidHugePage (
IN UINTN Entry
)
{
if ((Entry & (PAGE_HGLOBAL | PAGE_HUGE)) == (PAGE_HGLOBAL | PAGE_HUGE)) {
return TRUE;
} else {
return FALSE;
}
}
/**
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 | PAGE_GLOBAL | PAGE_VALID;
}
/**
Parse max page table level.
@param[in] PageWalkCfg Page table configure value.
@return 5 MAX page level is 5
4 MAX page level is 4
3 MAX page level is 3
0 Invalid
**/
STATIC
UINTN
ParseMaxPageTableLevel (
IN UINT64 PageWalkCfg
)
{
UINT32 Pwctl0;
UINT32 Pwctl1;
Pwctl0 = PageWalkCfg & MAX_UINT32;
Pwctl1 = (PageWalkCfg >> 32) & MAX_UINT32;
if (((Pwctl1 >> 18) & 0x3F) != 0x0) {
return LEVEL5;
} else if (((Pwctl1 >> 6) & 0x3F) != 0x0) {
return LEVEL4;
} else if (((Pwctl0 >> 25) & 0x3F) != 0x0) {
return LEVEL3;
}
return 0;
}
/**
Parse page table bit width.
Assume that the bit width of the page table that each level is the same to PTwidth.
@param[in] PageWalkCfg Page table configure value.
@return page table bit width
**/
STATIC
UINTN
ParsePageTableBitWidth (
IN UINT64 PageWalkCfg
)
{
//
// PTwidth
//
return ((PageWalkCfg >> 5) & 0x1F);
}
/**
Determine if an entry is a HUGE PTE or 4K PTE.
@param Entry The entry value.
@param Level The current page table level.
@param PageWalkCfg Page table configure value.
@retval TRUE The entry is a block pte.
@retval FALSE The entry is not a block pte.
**/
STATIC
BOOLEAN
IsBlockEntry (
IN UINTN Entry,
IN UINTN Level,
IN UINT64 PageWalkCfg
)
{
if (Level == (ParseMaxPageTableLevel (PageWalkCfg) - 1)) {
return ((Entry & PAGE_VALID) == PAGE_VALID);
}
return IsValidHugePage (Entry);
}
/**
Determine if an entry is a table pte.
@param Entry The entry value.
@param Level The current page table level.
@param PageWalkCfg Page table configure value.
@retval TRUE The entry is a table pte.
@retval FALSE The entry is not a table pte.
**/
STATIC
BOOLEAN
IsTableEntry (
IN UINTN Entry,
IN UINTN Level,
IN UINT64 PageWalkCfg
)
{
if (Level == (ParseMaxPageTableLevel (PageWalkCfg) - 1)) {
//
// The last level is PAGE rather than Table.
//
return FALSE;
}
//
// Is DIR4 or DIR3 or DIR2 a Huge Page ?
//
return (!IsValidHugePage (Entry)) && (IsValidPte (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 && MmuIsInit ()) {
InvalidTlb (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 >> LOONGARCH_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 PageWalkCfg Page table configure value.
@param Level The current level.
**/
STATIC
VOID
FreePageTablesRecursive (
IN UINTN *TranslationTable,
IN UINT64 PageWalkCfg,
IN UINTN Level
)
{
UINTN Index;
UINTN TableEntryNum;
TableEntryNum = (1 << ParsePageTableBitWidth (PageWalkCfg));
if (Level < (ParseMaxPageTableLevel (PageWalkCfg) - 1)) {
for (Index = 0; Index < TableEntryNum; Index++) {
if (IsTableEntry (TranslationTable[Index], Level, PageWalkCfg)) {
FreePageTablesRecursive (
(UINTN *)(GetPpnfromPte ((TranslationTable[Index])) <<
LOONGARCH_MMU_PAGE_SHIFT),
PageWalkCfg,
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 PageWalkCfg Page table configure value.
@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 UINT64 PageWalkCfg,
IN BOOLEAN TableIsLive
)
{
EFI_STATUS Status;
UINTN BlockShift;
UINTN BlockMask;
UINTN BlockEnd;
UINTN *Entry;
UINTN EntryValue;
UINTN *TranslationTable;
UINTN TableEntryNum;
UINTN TableBitWidth;
BOOLEAN NextTableIsLive;
ASSERT (Level < ParseMaxPageTableLevel (PageWalkCfg));
ASSERT (((RegionStart | RegionEnd) & EFI_PAGE_MASK) == 0);
TableBitWidth = ParsePageTableBitWidth (PageWalkCfg);
BlockShift = (ParseMaxPageTableLevel (PageWalkCfg) - Level - 1) * TableBitWidth + LOONGARCH_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
)
);
TableEntryNum = (1 << TableBitWidth);
for ( ; RegionStart < RegionEnd; RegionStart = BlockEnd) {
BlockEnd = MIN (RegionEnd, (RegionStart | BlockMask) + 1);
Entry = &PageTable[(RegionStart >> BlockShift) & (TableEntryNum - 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 2,
// so in that case, we have to recurse unconditionally.
//
if ((Level < 2) ||
(((RegionStart | BlockEnd) & BlockMask) != 0) || IsTableEntry (*Entry, Level, PageWalkCfg))
{
ASSERT (Level < (ParseMaxPageTableLevel (PageWalkCfg) - 1));
if (!IsTableEntry (*Entry, Level, PageWalkCfg)) {
//
// 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, Level, PageWalkCfg)) {
//
// 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,
PageWalkCfg,
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) << LOONGARCH_MMU_PAGE_SHIFT);
NextTableIsLive = TableIsLive;
}
//
// Recurse to the next level
//
Status = UpdateRegionMappingRecursive (
RegionStart,
BlockEnd,
AttributeSetMask,
AttributeClearMask,
TranslationTable,
Level + 1,
PageWalkCfg,
NextTableIsLive
);
if (EFI_ERROR (Status)) {
if (!IsTableEntry (*Entry, Level, PageWalkCfg)) {
//
// 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, PageWalkCfg, Level + 1);
}
return Status;
}
if (!IsTableEntry (*Entry, Level, PageWalkCfg)) {
EntryValue = SetPpnToPte (0, (UINTN)TranslationTable);
ReplaceTableEntry (
Entry,
EntryValue,
RegionStart,
TableIsLive
);
}
} else {
EntryValue = (*Entry & ~AttributeClearMask) | AttributeSetMask;
EntryValue = SetPpnToPte (EntryValue, RegionStart);
EntryValue = SetValidPte (EntryValue);
if (Level < (ParseMaxPageTableLevel (PageWalkCfg) - 1)) {
EntryValue |= (PAGE_HGLOBAL | PAGE_HUGE | PAGE_VALID);
} else {
EntryValue |= PAGE_GLOBAL | PAGE_VALID;
}
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 PageWalkCfg Page table configure value.
@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.
**/
EFI_STATUS
UpdateRegionMapping (
IN UINTN RegionStart,
IN UINTN RegionLength,
IN UINT64 PageWalkCfg,
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,
PageWalkCfg,
TableIsLive
);
}
/**
Convert EFI Attributes to Loongarch Attributes.
@param[in] EfiAttributes Efi Attributes.
@retval Corresponding architecture attributes.
**/
UINT64
EFIAPI
EfiAttributeConverse (
IN UINT64 EfiAttributes
)
{
UINT64 LoongArchAttributes;
LoongArchAttributes = PAGE_VALID | PAGE_DIRTY | PLV_KERNEL | PAGE_GLOBAL;
switch (EfiAttributes & EFI_CACHE_ATTRIBUTE_MASK) {
case EFI_MEMORY_UC:
LoongArchAttributes |= CACHE_SUC;
break;
case EFI_MEMORY_WC:
LoongArchAttributes |= CACHE_WUC;
break;
case EFI_MEMORY_WT:
case EFI_MEMORY_WB:
LoongArchAttributes |= CACHE_CC;
break;
case EFI_MEMORY_WP:
LoongArchAttributes &= ~PAGE_DIRTY;
break;
default:
break;
}
// Write protection attributes
switch (EfiAttributes & EFI_MEMORY_ACCESS_MASK) {
case EFI_MEMORY_RP:
LoongArchAttributes |= PAGE_NO_READ;
break;
case EFI_MEMORY_XP:
LoongArchAttributes |= PAGE_NO_EXEC;
break;
case EFI_MEMORY_RO:
LoongArchAttributes &= ~PAGE_DIRTY;
break;
default:
break;
}
return LoongArchAttributes;
}
/**
TLB refill handler configure.
@param VOID.
@retval EFI_SUCCESS TLB refill handler configure successfully.
EFI_UNSUPPORTED Size not aligned.
**/
EFI_STATUS
TlbRefillHandlerConfigure (
VOID
)
{
UINTN Length;
UINTN TlbReEntry;
UINTN TlbReEntryOffset;
UINTN Remaining;
//
// Set TLB exception handler
//
///
/// TLB Re-entry address at the end of exception vector, a vector is up to 512 bytes,
/// so the starting address is: total exception vector size + total interrupt vector size + base.
/// The total size of TLB handler and exception vector size and interrupt vector size should not
/// be lager than 64KB.
///
Length = (UINTN)HandleTlbRefillEnd - (UINTN)HandleTlbRefillStart;
TlbReEntryOffset = (MAX_LOONGARCH_EXCEPTION + MAX_LOONGARCH_INTERRUPT) * 512;
Remaining = TlbReEntryOffset % SIZE_4KB;
if (Remaining != 0x0) {
TlbReEntryOffset += (SIZE_4KB - Remaining);
}
TlbReEntry = PcdGet64 (PcdLoongArchExceptionVectorBaseAddress) + TlbReEntryOffset;
if ((TlbReEntryOffset + Length) > SIZE_64KB) {
return EFI_UNSUPPORTED;
}
//
// Ensure that TLB refill exception base address alignment is equals to 4KB and is valid.
//
if (TlbReEntry & (SIZE_4KB - 1)) {
return EFI_UNSUPPORTED;
}
CopyMem ((VOID *)TlbReEntry, HandleTlbRefillStart, Length);
InvalidateInstructionCacheRange ((VOID *)(UINTN)HandleTlbRefillStart, Length);
//
// Set the address of TLB refill exception handler
//
SetTlbRebaseAddress ((UINTN)TlbReEntry);
return EFI_SUCCESS;
}
/**
Maps the memory region in the page table to the specified attributes.
@param[in, out] PageTable The pointer to the page table to update, or pointer to NULL
if a new page table is to be created.
@param[in] PageWalkCfg The page walk controller configure.
@param[in] BaseAddress The base address of the memory region to set the Attributes.
@param[in] Length The length of the memory region to set the Attributes.
@param[in] Attributes The bitmask of attributes to set, which refer to UEFI SPEC
7.2.3(EFI_BOOT_SERVICES.GetMemoryMap()).
@param[in] AttributeMask Mask of memory attributes to take into account.
@retval EFI_SUCCESS The Attributes was set successfully or Length is 0.
@retval EFI_INVALID_PARAMETER PageTable is NULL, PageWalkCfg is invalid.
@retval EFI_UNSUPPORTED *PageTable is NULL.
**/
EFI_STATUS
EFIAPI
MemoryRegionMap (
IN OUT UINTN *PageTable OPTIONAL,
IN UINT64 PageWalkCfg,
IN EFI_PHYSICAL_ADDRESS BaseAddress,
IN UINT64 Length,
IN UINT64 Attributes,
IN UINT64 AttributeMask
)
{
EFI_STATUS Status;
UINT64 LoongArchAttributes;
BOOLEAN Initialization;
BOOLEAN CreateNew;
UINTN PageTableBitWidth;
UINTN MaxLevel;
UINTN PgdSize;
if ((PageTable == NULL) || (PageWalkCfg == 0)) {
return EFI_INVALID_PARAMETER;
}
PageTableBitWidth = ParsePageTableBitWidth (PageWalkCfg);
MaxLevel = ParseMaxPageTableLevel (PageWalkCfg);
if ((!PageTableBitWidth && !MaxLevel) || (PageTableBitWidth > 0x1F) || (MaxLevel > LEVEL5)) {
return EFI_INVALID_PARAMETER;
}
Initialization = FALSE;
CreateNew = FALSE;
//
// *PageTable is NULL, create a new and return.
//
if (*PageTable == 0) {
CreateNew = TRUE;
//
// If the MMU has not been configured yet, configure it later.
//
if (!MmuIsInit ()) {
Initialization = TRUE;
}
}
if (Length == 0) {
return EFI_SUCCESS;
}
if (Initialization == TRUE) {
Status = TlbRefillHandlerConfigure ();
ASSERT_EFI_ERROR (Status);
}
if (CreateNew == TRUE) {
//
// Create a new page table.
//
PgdSize = (1 << PageTableBitWidth) * sizeof (UINTN);
*PageTable = (UINTN)AllocatePages (EFI_SIZE_TO_PAGES (PgdSize));
ZeroMem ((VOID *)*PageTable, PgdSize);
if ((VOID *)*PageTable == NULL) {
return EFI_UNSUPPORTED;
}
}
LoongArchAttributes = EfiAttributeConverse (Attributes);
//
// Update the page table attributes.
//
// If the MMU has been configured and *PageTable == CSR_PGDL, the page table in use will update.
//
// If *PageTable != CSR_PGDL, only the page table structure in memory is update, but some TLB
// region may be invalidated during the mapping process. So at this time the caller must ensure
// that the execution environment must be safe. It is recommended to use the DA mode!
//
Status = UpdateRegionMapping (
BaseAddress,
Length,
PageWalkCfg,
LoongArchAttributes,
PTE_ATTRIBUTES_MASK,
(UINTN *)(*PageTable),
(MmuIsEnabled () && !CreateNew)
);
ASSERT_EFI_ERROR (Status);
return Status;
}