/** @file Copyright (c) 2022 Loongson Technology Corporation Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @par Glossary: - Pgd or Pgd or PGD - Page Global Directory - Pud or Pud or PUD - Page Upper Directory - Pmd or Pmd or PMD - Page Middle Directory - Pte or pte or PTE - Page Table Entry - Val or VAL or val - Value - Dir - Directory **/ #include #include #include #include #include #include "Library/Cpu.h" #include "pte.h" #include "page.h" #include "mmu.h" BOOLEAN mMmuInited = FALSE; /** Check to see if mmu successfully initializes. @param VOID. @retval TRUE Initialization has been completed. FALSE Initialization did not complete. **/ BOOLEAN MmuIsInit (VOID) { if ((mMmuInited == TRUE) || (PcdGet64 (PcdSwapPageDir) != 0)) { return TRUE; } return FALSE; } /** Iterates through the page directory to initialize it. @param Dst A pointer to the directory of the page to initialize. @param Num The number of page directories to initialize. @param Src A pointer to the data used to initialize the page directory. @retval VOID. **/ VOID PageDirInit ( IN VOID *Dst, IN UINTN Num, IN VOID *Src ) { UINTN *Ptr; UINTN *End; UINTN Entry; Entry = (UINTN)Src; Ptr = (UINTN *)Dst; End = Ptr + Num; for ( ;Ptr < End; Ptr++) { *Ptr = Entry; } return ; } /** Gets the virtual address corresponding to the page global directory table entry. @param Address the virtual address for the table entry. @retval PGD A pointer to get the table item. **/ PGD * PgdOffset ( IN UINTN Address ) { return ((PGD *)PcdGet64 (PcdSwapPageDir)) + PGD_INDEX (Address); } /** Gets the virtual address corresponding to the page upper directory table entry. @param Pgd A pointer to a page global directory table entry. @param Address the virtual address for the table entry. @retval PUD A pointer to get the table item. **/ PUD * PudOffset ( IN PGD *Pgd, IN UINTN Address ) { UINTN PgdVal = (UINTN)PGD_VAL (*Pgd); return (PUD *)PgdVal + PUD_INDEX (Address); } /** Gets the virtual address corresponding to the page middle directory table entry. @param Pud A pointer to a page upper directory table entry. @param Address the virtual address for the table entry. @retval PMD A pointer to get the table item. **/ PMD * PmdOffset ( IN PUD *Pud, IN UINTN Address ) { UINTN PudVal = PUD_VAL (*Pud); return (PMD *)PudVal + PMD_INDEX (Address); } /** Gets the virtual address corresponding to the page table entry. @param Pmd A pointer to a page middle directory table entry. @param Address the virtual address for the table entry. @retval PTE A pointer to get the table item. **/ PTE * PteOffset ( IN PMD *Pmd, IN UINTN Address ) { UINTN PmdVal = (UINTN)PMD_VAL (*Pmd); return (PTE *)PmdVal + PTE_INDEX (Address); } /** Sets the value of the page table entry. @param Pte A pointer to a page table entry. @param PteVal The value of the page table entry to set. @retval VOID **/ VOID SetPte ( IN PTE *Pte, IN PTE PteVal ) { *Pte = PteVal; } /** Sets the value of the page global directory. @param Pgd A pointer to a page global directory. @param Pud The value of the page global directory to set. @retval VOID **/ VOID SetPgd ( IN PGD *Pgd, IN PUD *Pud ) { *Pgd = (PGD) {((UINTN)Pud)}; } /** Sets the value of the page upper directory. @param Pud A pointer to a page upper directory. @param Pmd The value of the page upper directory to set. @retval VOID **/ VOID SetPud ( IN PUD *Pud, IN PMD *Pmd ) { *Pud = (PUD) {((UINTN)Pmd)}; } /** Sets the value of the page middle directory. @param Pmd A pointer to a page middle directory. @param Pte The value of the page middle directory to set. @retval VOID **/ VOID SetPmd ( IN PMD *Pmd, IN PTE *Pte ) { *Pmd = (PMD) {((UINTN)Pte)}; } /** Free up memory space occupied by page tables. @param Pte A pointer to the page table. @retval VOID **/ VOID PteFree ( IN PTE *Pte ) { FreePages ((VOID *)Pte, 1); } /** Free up memory space occupied by page middle directory. @param Pmd A pointer to the page middle directory. @retval VOID **/ VOID PmdFree ( IN PMD *Pmd ) { FreePages ((VOID *)Pmd, 1); } /** Free up memory space occupied by page upper directory. @param Pud A pointer to the page upper directory. @retval VOID **/ VOID PudFree ( IN PUD *Pud ) { FreePages ((VOID *)Pud, 1); } /** Requests the memory space required for the page upper directory, initializes it, and places it in the specified page global directory @param Pgd A pointer to the page global directory. @retval EFI_SUCCESS Memory request successful. @retval EFI_OUT_OF_RESOURCES Resource exhaustion cannot be requested to memory. **/ INTN PudAlloc ( IN PGD *Pgd ) { PUD *Pud = (PUD *) AllocatePages (1); if (!Pud) { return EFI_OUT_OF_RESOURCES; } PageDirInit ((VOID *)Pud, ENTRYS_PER_PUD, (VOID *)PcdGet64 (PcdInvalidPmd)); if (pgd_none (*Pgd)) { SetPgd (Pgd, Pud); } else { /* Another has populated it */ PudFree (Pud); } return EFI_SUCCESS; } /** Requests the memory space required for the page middle directory, initializes it, and places it in the specified page upper directory @param Pud A pointer to the page upper directory. @retval EFI_SUCCESS Memory request successful. @retval EFI_OUT_OF_RESOURCES Resource exhaustion cannot be requested to memory. **/ EFI_STATUS PmdAlloc ( IN PUD *Pud ) { PMD *Pmd; Pmd = (PMD *) AllocatePages (1); if (!Pmd) { return EFI_OUT_OF_RESOURCES; } PageDirInit ((VOID *)Pmd, ENTRYS_PER_PMD, (VOID *)PcdGet64 (PcdInvalidPte)); if (pud_none (*Pud)) { SetPud (Pud, Pmd); } else {/* Another has populated it */ PmdFree (Pmd); } return EFI_SUCCESS; } /** Requests the memory space required for the page table, initializes it, and places it in the specified page middle directory @param Pmd A pointer to the page middle directory. @retval EFI_SUCCESS Memory request successful. @retval EFI_OUT_OF_RESOURCES Resource exhaustion cannot be requested to memory. **/ INTN PteAlloc ( IN PMD *Pmd ) { PTE *Pte; Pte = (PTE *) AllocatePages (1); if (!Pte) { return EFI_OUT_OF_RESOURCES; } Pte = ZeroMem (Pte, EFI_PAGE_SIZE); if (pmd_none (*Pmd)) { SetPmd (Pmd, Pte); } else { /* Another has populated it */ PteFree (Pte); } return EFI_SUCCESS; } /** Requests the memory space required for the page upper directory, initializes it, and places it in the specified page global directory, and get the page upper directory entry corresponding to the virtual address @param Pgd A pointer to the page global directory. @retval Gets the page upper directory entry **/ PUD * PudAllocGet ( IN PGD *Pgd, IN UINTN Address ) { return ((pgd_none (*(Pgd)) && PudAlloc (Pgd)) ? NULL : PudOffset (Pgd, Address)); } /** Requests the memory space required for the page middle directory, initializes it, and places it in the specified page upper directory, and get the page middle directory entry corresponding to the virtual address @param Pud A pointer to the page upper directory. @retval Gets the page middle directory entry **/ PMD * PmdAllocGet ( IN PUD *Pud, IN UINTN Address ) { PMD * ret = (pud_none (*Pud) && PmdAlloc (Pud))? NULL: PmdOffset (Pud, Address); DEBUG ((DEBUG_VERBOSE, "%a %d PudVal %p PmdOffset %p PMD_INDEX %p .\n", __func__, __LINE__, Pud->PudVal, PmdOffset (Pud, Address), PMD_INDEX (Address) )); return ret; } /** Requests the memory space required for the page table, initializes it, and places it in the specified page middle directory, and get the page table entry corresponding to the virtual address @param Pmd A pointer to the page upper directory. @retval Gets the page table entry **/ PTE * PteAllocGet ( IN PMD *Pmd, IN UINTN Address ) { return (pmd_none (*Pmd) && PteAlloc (Pmd))? NULL: PteOffset (Pmd, Address); } /** Gets the physical address of the page table entry corresponding to the specified virtual address. @param Address the corresponding virtual address of the page table entry. @retval A pointer to the page table entry. @retval NULL **/ PTE * GetPteAddress ( IN UINTN Address ) { PGD *Pgd; PUD *Pud; PMD *Pmd; Pgd = PgdOffset (Address); if (pgd_none (*Pgd)) { return NULL; } Pud = PudOffset (Pgd, Address); if (pud_none (*Pud)) { return NULL; } Pmd = PmdOffset (Pud, Address); if (pmd_none (*Pmd)) { return NULL; } if (IS_HUGE_PAGE (Pmd->PmdVal)) { return ((PTE *)Pmd); } return PteOffset (Pmd, Address); } /** Establishes a page table entry based on the specified memory region. @param Pmd A pointer to the page middle directory. @param Address The memory space start address. @param End The end address of the memory space. @param Attributes Memory space Attributes. @retval EFI_SUCCESS The page table entry was created successfully. @retval EFI_OUT_OF_RESOURCES Page table entry establishment failed due to resource exhaustion. **/ EFI_STATUS MemoryMapPteRange ( IN PMD *Pmd, IN UINTN Address, IN UINTN End, IN UINTN Attributes ) { PTE *Pte; PTE PteVal; BOOLEAN UpDate; Pte = PteAllocGet (Pmd, Address); if (!Pte) { return EFI_OUT_OF_RESOURCES; } do { UpDate = FALSE; PteVal = MAKE_PTE (Address, Attributes); DEBUG ((DEBUG_VERBOSE, "%a %d Address %p PGD_INDEX %p PUD_INDEX %p PMD_INDEX %p PTE_INDEX %p MAKE_PTE %p\n", __func__, __LINE__, Address, PGD_INDEX (Address), PUD_INDEX (Address), PMD_INDEX (Address), PTE_INDEX (Address), PteVal)); if ((!pte_none (*Pte)) && (PTE_VAL(*Pte) != PTE_VAL(PteVal))) { UpDate = TRUE; } SetPte (Pte, PteVal); if (UpDate) { LoongarchInvalidTlb(Address); } } while (Pte++, Address += EFI_PAGE_SIZE, Address != End); return EFI_SUCCESS; } /** Establishes a page middle directory based on the specified memory region. @param Pud A pointer to the page upper directory. @param Address The memory space start address. @param End The end address of the memory space. @param Attributes Memory space Attributes. @retval EFI_SUCCESS The page middle directory was created successfully. @retval EFI_OUT_OF_RESOURCES Page middle directory establishment failed due to resource exhaustion. **/ EFI_STATUS MemoryMapPmdRange ( IN PUD *Pud, IN UINTN Address, IN UINTN End, IN UINTN Attributes ) { PMD *Pmd; PTE *Pte; UINTN Next; UINTN AddressStart_HugePage; UINTN AddressEnd_HugePage; Pmd = PmdAllocGet (Pud, Address); if (!Pmd) { return EFI_OUT_OF_RESOURCES; } do { Next = PMD_ADDRESS_END (Address, End); if (((Address & (~PMD_MASK)) == 0) && ((Next & (~PMD_MASK)) == 0) && (pmd_none (*Pmd))) { DEBUG ((DEBUG_VERBOSE, "%a %d Address %p PGD_INDEX %p PUD_INDEX %p PMD_INDEX %p MAKE_HUGE_PTE %p\n", __func__, __LINE__, Address, PGD_INDEX (Address), PUD_INDEX (Address), PMD_INDEX (Address), MAKE_HUGE_PTE (Address, Attributes))); SetPmd (Pmd, (PTE *)MAKE_HUGE_PTE (Address, Attributes)); } else { if ((pmd_none (*Pmd)) || ((!pmd_none (*Pmd)) && (!IS_HUGE_PAGE (Pmd->PmdVal)))) { if (MemoryMapPteRange (Pmd, Address, Next, Attributes)) { return EFI_OUT_OF_RESOURCES; } } else { SetPmd (Pmd, (PTE *)PcdGet64 (PcdInvalidPte)); AddressStart_HugePage = Address & PMD_MASK; AddressEnd_HugePage = AddressStart_HugePage + HUGE_PAGE_SIZE; if (MemoryMapPteRange (Pmd, AddressStart_HugePage, AddressEnd_HugePage, Attributes)) { return EFI_OUT_OF_RESOURCES; } Pte = GetPteAddress (AddressStart_HugePage); if (Pte == NULL) { continue ; } if (AddressEnd_HugePage > End) { Next = End; } } } } while (Pmd++, Address = Next, Address != End); return 0; } /** Establishes a page upper directory based on the specified memory region. @param Pgd A pointer to the page global directory. @param Address The memory space start address. @param End The end address of the memory space. @param Attributes Memory space Attributes. @retval EFI_SUCCESS The page upper directory was created successfully. @retval EFI_OUT_OF_RESOURCES Page upper directory establishment failed due to resource exhaustion. **/ EFI_STATUS MemoryMapPudRange ( IN PGD *Pgd, IN UINTN Address, IN UINTN End, IN UINTN Attributes ) { PUD *Pud; UINTN Next; Pud = PudAllocGet (Pgd, Address); if (!Pud) { return EFI_OUT_OF_RESOURCES; } do { Next = PUD_ADDRESS_END (Address, End); if (MemoryMapPmdRange (Pud, Address, Next, Attributes)) { return EFI_OUT_OF_RESOURCES; } } while (Pud++, Address = Next, Address != End); return EFI_SUCCESS; } /** Establishes a page global directory based on the specified memory region. @param Start The memory space start address. @param End The end address of the memory space. @param Attributes Memory space Attributes. @retval EFI_SUCCESS The page global directory was created successfully. @retval EFI_OUT_OF_RESOURCES Page global directory establishment failed due to resource exhaustion. **/ EFI_STATUS MemoryMapPageRange ( IN UINTN Start, IN UINTN End, IN UINTN Attributes ) { PGD *Pgd; UINTN Next; UINTN Address = Start; EFI_STATUS Err; Pgd = PgdOffset (Address); do { Next = PGD_ADDRESS_END (Address, End); Err = MemoryMapPudRange (Pgd, Address, Next, Attributes); if (Err) { return Err; } } while (Pgd++, Address = Next, Address != End); return EFI_SUCCESS; } /** Page tables are established from memory-mapped tables. @param MemoryRegion A pointer to a memory-mapped table entry. @retval EFI_SUCCESS The page table was created successfully. @retval EFI_OUT_OF_RESOURCES Page table establishment failed due to resource exhaustion. **/ EFI_STATUS FillTranslationTable ( IN MEMORY_REGION_DESCRIPTOR *MemoryRegion ) { return MemoryMapPageRange (MemoryRegion->VirtualBase, (MemoryRegion->Length + MemoryRegion->VirtualBase), MemoryRegion->Attributes); } /** write operation is performed Count times from the first element of Buffer. Convert EFI Attributes to Loongarch Attributes. @param[in] EfiAttributes Efi Attributes. @retval LoongArch Attributes. **/ UINTN EfiAttributeToLoongArchAttribute ( IN UINTN EfiAttributes ) { UINTN LoongArchAttributes = PAGE_VALID | PAGE_DIRTY | CACHE_CC | PAGE_USER | PAGE_GLOBAL; switch (EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) { case EFI_MEMORY_UC: LoongArchAttributes |= CACHE_SUC; break; case EFI_MEMORY_WC: case EFI_MEMORY_WT: case EFI_MEMORY_WB: LoongArchAttributes |= CACHE_CC; break; default : LoongArchAttributes |= CACHE_CC; break; } // Write protection attributes if ((EfiAttributes & EFI_MEMORY_RO) != 0) { LoongArchAttributes &= ~PAGE_DIRTY; } //eXecute protection attribute if ((EfiAttributes & EFI_MEMORY_XP) != 0) { LoongArchAttributes |= PAGE_NO_EXEC; } return LoongArchAttributes; } /** Finds the length and memory properties of the memory region corresponding to the specified base address. @param[in] BaseAddress To find the base address of the memory region. @param[in] EndAddress To find the end address of the memory region. @param[out] RegionLength The length of the memory region found. @param[out] RegionAttributes Properties of the memory region found. @retval EFI_SUCCESS The corresponding memory area was successfully found EFI_NOT_FOUND No memory area found **/ EFI_STATUS GetLoongArchMemoryRegion ( IN UINTN BaseAddress, IN UINTN EndAddress, OUT UINTN *RegionLength, OUT UINTN *RegionAttributes ) { PTE *Pte; UINTN Attributes; UINTN AttributesTmp; UINTN MaxAddress; MaxAddress = LShiftU64 (1ULL, MAX_VA_BITS) - 1; Pte = GetPteAddress (BaseAddress); if (!MmuIsInit ()) { return EFI_SUCCESS; } if (Pte == NULL) { return EFI_NOT_FOUND; } Attributes = GET_PAGE_ATTRIBUTES (*Pte); if (IS_HUGE_PAGE (Pte->PteVal)) { *RegionAttributes = Attributes & (~(PAGE_HUGE)); *RegionLength += HUGE_PAGE_SIZE; } else { *RegionLength += EFI_PAGE_SIZE; *RegionAttributes = Attributes; } while (BaseAddress <= MaxAddress) { Pte = GetPteAddress (BaseAddress); if (Pte == NULL) { return EFI_SUCCESS; } AttributesTmp = GET_PAGE_ATTRIBUTES (*Pte); if (IS_HUGE_PAGE (Pte->PteVal)) { if (AttributesTmp == Attributes) { *RegionLength += HUGE_PAGE_SIZE; } BaseAddress += HUGE_PAGE_SIZE; } else { if (AttributesTmp == Attributes) { *RegionLength += EFI_PAGE_SIZE; } BaseAddress += EFI_PAGE_SIZE; } if (BaseAddress > EndAddress) { break; } } return EFI_SUCCESS; } /** Sets the Attributes of the specified memory region @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 Attributes to be set. @retval EFI_SUCCESS The Attributes was set successfully **/ EFI_STATUS LoongArchSetMemoryAttributes ( IN EFI_PHYSICAL_ADDRESS BaseAddress, IN UINTN Length, IN UINTN Attributes ) { if (!MmuIsInit ()) { return EFI_SUCCESS; } Attributes = EfiAttributeToLoongArchAttribute (Attributes); DEBUG ((DEBUG_VERBOSE, "%a %d %p %p %p.\n", __func__, __LINE__, BaseAddress , Length, Attributes)); MemoryMapPageRange (BaseAddress, BaseAddress + Length, Attributes); return EFI_SUCCESS; } /** Sets the non-executable Attributes for the specified memory region @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. @retval EFI_SUCCESS The Attributes was set successfully **/ EFI_STATUS LoongArchSetMemoryRegionNoExec ( IN EFI_PHYSICAL_ADDRESS BaseAddress, IN UINTN Length ) { if (MmuIsInit ()) { Length = EFI_PAGES_TO_SIZE (EFI_SIZE_TO_PAGES (Length)); LoongArchSetMemoryAttributes (BaseAddress, Length, EFI_MEMORY_XP); } return EFI_SUCCESS; } /** Check to see if mmu successfully initializes and saves the result. @param VOID. @retval EFI_SUCCESS Initialization succeeded. **/ EFI_STATUS MmuInitialize (VOID) { if (PcdGet64 (PcdSwapPageDir) != 0) { mMmuInited = TRUE; } return EFI_SUCCESS; }