audk/UefiCpuPkg/Library/CpuMmuLib/LoongArch64/CpuMmu.c

785 lines
20 KiB
C

/** @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.<BR>
Copyright (c) 2023, Ventana Micro Systems Inc. All Rights Reserved.<BR>
Copyright (c) 2024 Loongson Technology Corporation Limited. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/CacheMaintenanceLib.h>
#include <Library/CpuMmuLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/DebugSupport.h>
#include <Register/LoongArch64/Csr.h>
#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;
}