/** @file
MM Memory page management functions.
Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.
Copyright (c) 2016 - 2018, ARM Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "StandaloneMmCore.h"
#define NEXT_MEMORY_DESCRIPTOR(MemoryDescriptor, Size) \
((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(MemoryDescriptor) + (Size)))
#define TRUNCATE_TO_PAGES(a) ((a) >> EFI_PAGE_SHIFT)
LIST_ENTRY mMmMemoryMap = INITIALIZE_LIST_HEAD_VARIABLE (mMmMemoryMap);
UINTN mMapKey;
/**
Internal Function. Allocate n pages from given free page node.
@param Pages The free page node.
@param NumberOfPages Number of pages to be allocated.
@param MaxAddress Request to allocate memory below this address.
@return Memory address of allocated pages.
**/
UINTN
InternalAllocPagesOnOneNode (
IN OUT FREE_PAGE_LIST *Pages,
IN UINTN NumberOfPages,
IN UINTN MaxAddress
)
{
UINTN Top;
UINTN Bottom;
FREE_PAGE_LIST *Node;
Top = TRUNCATE_TO_PAGES (MaxAddress + 1 - (UINTN)Pages);
if (Top > Pages->NumberOfPages) {
Top = Pages->NumberOfPages;
}
Bottom = Top - NumberOfPages;
if (Top < Pages->NumberOfPages) {
Node = (FREE_PAGE_LIST*)((UINTN)Pages + EFI_PAGES_TO_SIZE (Top));
Node->NumberOfPages = Pages->NumberOfPages - Top;
InsertHeadList (&Pages->Link, &Node->Link);
}
if (Bottom > 0) {
Pages->NumberOfPages = Bottom;
} else {
RemoveEntryList (&Pages->Link);
}
return (UINTN)Pages + EFI_PAGES_TO_SIZE (Bottom);
}
/**
Internal Function. Allocate n pages from free page list below MaxAddress.
@param FreePageList The free page node.
@param NumberOfPages Number of pages to be allocated.
@param MaxAddress Request to allocate memory below this address.
@return Memory address of allocated pages.
**/
UINTN
InternalAllocMaxAddress (
IN OUT LIST_ENTRY *FreePageList,
IN UINTN NumberOfPages,
IN UINTN MaxAddress
)
{
LIST_ENTRY *Node;
FREE_PAGE_LIST *Pages;
for (Node = FreePageList->BackLink; Node != FreePageList; Node = Node->BackLink) {
Pages = BASE_CR (Node, FREE_PAGE_LIST, Link);
if (Pages->NumberOfPages >= NumberOfPages &&
(UINTN)Pages + EFI_PAGES_TO_SIZE (NumberOfPages) - 1 <= MaxAddress) {
return InternalAllocPagesOnOneNode (Pages, NumberOfPages, MaxAddress);
}
}
return (UINTN)(-1);
}
/**
Internal Function. Allocate n pages from free page list at given address.
@param FreePageList The free page node.
@param NumberOfPages Number of pages to be allocated.
@param MaxAddress Request to allocate memory below this address.
@return Memory address of allocated pages.
**/
UINTN
InternalAllocAddress (
IN OUT LIST_ENTRY *FreePageList,
IN UINTN NumberOfPages,
IN UINTN Address
)
{
UINTN EndAddress;
LIST_ENTRY *Node;
FREE_PAGE_LIST *Pages;
if ((Address & EFI_PAGE_MASK) != 0) {
return ~Address;
}
EndAddress = Address + EFI_PAGES_TO_SIZE (NumberOfPages);
for (Node = FreePageList->BackLink; Node!= FreePageList; Node = Node->BackLink) {
Pages = BASE_CR (Node, FREE_PAGE_LIST, Link);
if ((UINTN)Pages <= Address) {
if ((UINTN)Pages + EFI_PAGES_TO_SIZE (Pages->NumberOfPages) < EndAddress) {
break;
}
return InternalAllocPagesOnOneNode (Pages, NumberOfPages, EndAddress);
}
}
return ~Address;
}
/**
Allocates pages from the memory map.
@param Type The type of allocation to perform.
@param MemoryType The type of memory to turn the allocated pages
into.
@param NumberOfPages The number of pages to allocate.
@param Memory A pointer to receive the base allocated memory
address.
@retval EFI_INVALID_PARAMETER Parameters violate checking rules defined in spec.
@retval EFI_NOT_FOUND Could not allocate pages match the requirement.
@retval EFI_OUT_OF_RESOURCES No enough pages to allocate.
@retval EFI_SUCCESS Pages successfully allocated.
**/
EFI_STATUS
EFIAPI
MmInternalAllocatePages (
IN EFI_ALLOCATE_TYPE Type,
IN EFI_MEMORY_TYPE MemoryType,
IN UINTN NumberOfPages,
OUT EFI_PHYSICAL_ADDRESS *Memory
)
{
UINTN RequestedAddress;
if (MemoryType != EfiRuntimeServicesCode &&
MemoryType != EfiRuntimeServicesData) {
return EFI_INVALID_PARAMETER;
}
if (NumberOfPages > TRUNCATE_TO_PAGES ((UINTN)-1) + 1) {
return EFI_OUT_OF_RESOURCES;
}
//
// We don't track memory type in MM
//
RequestedAddress = (UINTN)*Memory;
switch (Type) {
case AllocateAnyPages:
RequestedAddress = (UINTN)(-1);
case AllocateMaxAddress:
*Memory = InternalAllocMaxAddress (
&mMmMemoryMap,
NumberOfPages,
RequestedAddress
);
if (*Memory == (UINTN)-1) {
return EFI_OUT_OF_RESOURCES;
}
break;
case AllocateAddress:
*Memory = InternalAllocAddress (
&mMmMemoryMap,
NumberOfPages,
RequestedAddress
);
if (*Memory != RequestedAddress) {
return EFI_NOT_FOUND;
}
break;
default:
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
/**
Allocates pages from the memory map.
@param Type The type of allocation to perform.
@param MemoryType The type of memory to turn the allocated pages
into.
@param NumberOfPages The number of pages to allocate.
@param Memory A pointer to receive the base allocated memory
address.
@retval EFI_INVALID_PARAMETER Parameters violate checking rules defined in spec.
@retval EFI_NOT_FOUND Could not allocate pages match the requirement.
@retval EFI_OUT_OF_RESOURCES No enough pages to allocate.
@retval EFI_SUCCESS Pages successfully allocated.
**/
EFI_STATUS
EFIAPI
MmAllocatePages (
IN EFI_ALLOCATE_TYPE Type,
IN EFI_MEMORY_TYPE MemoryType,
IN UINTN NumberOfPages,
OUT EFI_PHYSICAL_ADDRESS *Memory
)
{
EFI_STATUS Status;
Status = MmInternalAllocatePages (Type, MemoryType, NumberOfPages, Memory);
return Status;
}
/**
Internal Function. Merge two adjacent nodes.
@param First The first of two nodes to merge.
@return Pointer to node after merge (if success) or pointer to next node (if fail).
**/
FREE_PAGE_LIST *
InternalMergeNodes (
IN FREE_PAGE_LIST *First
)
{
FREE_PAGE_LIST *Next;
Next = BASE_CR (First->Link.ForwardLink, FREE_PAGE_LIST, Link);
ASSERT (
TRUNCATE_TO_PAGES ((UINTN)Next - (UINTN)First) >= First->NumberOfPages);
if (TRUNCATE_TO_PAGES ((UINTN)Next - (UINTN)First) == First->NumberOfPages) {
First->NumberOfPages += Next->NumberOfPages;
RemoveEntryList (&Next->Link);
Next = First;
}
return Next;
}
/**
Frees previous allocated pages.
@param Memory Base address of memory being freed.
@param NumberOfPages The number of pages to free.
@retval EFI_NOT_FOUND Could not find the entry that covers the range.
@retval EFI_INVALID_PARAMETER Address not aligned.
@return EFI_SUCCESS Pages successfully freed.
**/
EFI_STATUS
EFIAPI
MmInternalFreePages (
IN EFI_PHYSICAL_ADDRESS Memory,
IN UINTN NumberOfPages
)
{
LIST_ENTRY *Node;
FREE_PAGE_LIST *Pages;
if ((Memory & EFI_PAGE_MASK) != 0) {
return EFI_INVALID_PARAMETER;
}
Pages = NULL;
Node = mMmMemoryMap.ForwardLink;
while (Node != &mMmMemoryMap) {
Pages = BASE_CR (Node, FREE_PAGE_LIST, Link);
if (Memory < (UINTN)Pages) {
break;
}
Node = Node->ForwardLink;
}
if (Node != &mMmMemoryMap &&
Memory + EFI_PAGES_TO_SIZE (NumberOfPages) > (UINTN)Pages) {
return EFI_INVALID_PARAMETER;
}
if (Node->BackLink != &mMmMemoryMap) {
Pages = BASE_CR (Node->BackLink, FREE_PAGE_LIST, Link);
if ((UINTN)Pages + EFI_PAGES_TO_SIZE (Pages->NumberOfPages) > Memory) {
return EFI_INVALID_PARAMETER;
}
}
Pages = (FREE_PAGE_LIST*)(UINTN)Memory;
Pages->NumberOfPages = NumberOfPages;
InsertTailList (Node, &Pages->Link);
if (Pages->Link.BackLink != &mMmMemoryMap) {
Pages = InternalMergeNodes (
BASE_CR (Pages->Link.BackLink, FREE_PAGE_LIST, Link)
);
}
if (Node != &mMmMemoryMap) {
InternalMergeNodes (Pages);
}
return EFI_SUCCESS;
}
/**
Frees previous allocated pages.
@param Memory Base address of memory being freed.
@param NumberOfPages The number of pages to free.
@retval EFI_NOT_FOUND Could not find the entry that covers the range.
@retval EFI_INVALID_PARAMETER Address not aligned.
@return EFI_SUCCESS Pages successfully freed.
**/
EFI_STATUS
EFIAPI
MmFreePages (
IN EFI_PHYSICAL_ADDRESS Memory,
IN UINTN NumberOfPages
)
{
EFI_STATUS Status;
Status = MmInternalFreePages (Memory, NumberOfPages);
return Status;
}
/**
Add free MMRAM region for use by memory service.
@param MemBase Base address of memory region.
@param MemLength Length of the memory region.
@param Type Memory type.
@param Attributes Memory region state.
**/
VOID
MmAddMemoryRegion (
IN EFI_PHYSICAL_ADDRESS MemBase,
IN UINT64 MemLength,
IN EFI_MEMORY_TYPE Type,
IN UINT64 Attributes
)
{
UINTN AlignedMemBase;
//
// Do not add memory regions that is already allocated, needs testing, or needs ECC initialization
//
if ((Attributes & (EFI_ALLOCATED | EFI_NEEDS_TESTING | EFI_NEEDS_ECC_INITIALIZATION)) != 0) {
return;
}
//
// Align range on an EFI_PAGE_SIZE boundary
//
AlignedMemBase = (UINTN)(MemBase + EFI_PAGE_MASK) & ~EFI_PAGE_MASK;
MemLength -= AlignedMemBase - MemBase;
MmFreePages (AlignedMemBase, TRUNCATE_TO_PAGES ((UINTN)MemLength));
}