| /** @file | |
| Instance of Memory Below Address Allocation Library based on Windows APIs | |
| and Linux APIs. | |
| Uses Windows APIs VirtualAlloc() and VirtualFree() to allocate and free memory | |
| below a specified virtual address. | |
| Uses Linux APIs mmap() and munmap() to allocate and free memory below a | |
| specified virtual address. | |
| Copyright (c) 2024, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #if defined (_WIN32) || defined (_WIN64) | |
| #include "WinInclude.h" | |
| #elif defined (__linux__) | |
| #include <sys/mman.h> | |
| #include <unistd.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #else | |
| #error Unsupported target | |
| #endif | |
| #include <Library/HostMemoryAllocationBelowAddressLib.h> | |
| #include <Library/DebugLib.h> | |
| /// | |
| /// Signature for PAGE_HEAD_BELOW_ADDRESS structure | |
| /// Used to verify that buffer being freed was allocated by this library. | |
| /// | |
| #define PAGE_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE SIGNATURE_64 ('P', 'A', 'H', 'B', 'e', 'l', 'A', 'd') | |
| /// | |
| /// Structure placed immediately before an aligned allocation to store the | |
| /// information required to free the entire allocated buffer. | |
| /// | |
| typedef struct { | |
| UINT64 Signature; | |
| VOID *AllocatedBuffer; | |
| UINTN TotalPages; | |
| VOID *AlignedBuffer; | |
| UINTN AlignedPages; | |
| } PAGE_HEAD_BELOW_ADDRESS; | |
| /// | |
| /// Signature for POOL_HEAD_BELOW_ADDRESS structure | |
| /// Used to verify that buffer being freed was allocated by this library. | |
| /// | |
| #define POOL_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE SIGNATURE_64 ('P', 'O', 'H', 'B', 'e', 'l', 'A', 'd') | |
| /// | |
| /// Structure placed immediately before an pool allocation to store the | |
| /// information required to free the entire allocated buffer. | |
| /// | |
| typedef struct { | |
| UINT64 Signature; | |
| UINT64 TotalSize; | |
| } POOL_HEAD_BELOW_ADDRESS; | |
| // | |
| // Lowest address that can be allocated by this library | |
| // | |
| #define MINIMUM_ALLOCATION_ADDRESS BASE_64KB | |
| // | |
| // The page size of the host | |
| // | |
| static UINTN mPageSize = 0; | |
| /** | |
| Use system services to get the host page size. | |
| @return Host page size in bytes. | |
| **/ | |
| static | |
| UINTN | |
| HostGetPageSize ( | |
| VOID | |
| ) | |
| { | |
| #if defined (_WIN32) || defined (_WIN64) | |
| SYSTEM_INFO SystemInfo; | |
| GetSystemInfo (&SystemInfo); | |
| return (UINTN)SystemInfo.dwPageSize; | |
| #elif defined (__linux__) | |
| return sysconf (_SC_PAGESIZE); | |
| #else | |
| return 0; | |
| #endif | |
| } | |
| /** | |
| Use system services to allocate a buffer between a minimum and maximum | |
| address aligned to the requested page size. | |
| @param[in] MaximumAddress The address below which the memory allocation must | |
| be performed. | |
| @param[in] Length The size, in bytes, of the memory allocation. | |
| @retval !NULL Pointer to the allocated memory. | |
| @retval NULL The memory allocation failed. | |
| **/ | |
| static | |
| VOID * | |
| HostAllocateBufferInRange ( | |
| UINTN MaximumAddress, | |
| UINTN Length | |
| ) | |
| { | |
| UINTN Address; | |
| VOID *AllocatedAddress; | |
| if (mPageSize == 0) { | |
| mPageSize = HostGetPageSize (); | |
| if (mPageSize == 0) { | |
| return NULL; | |
| } | |
| } | |
| // | |
| // Round maximum address down to the nearest page boundary | |
| // | |
| MaximumAddress &= ~(mPageSize - 1); | |
| for (Address = MaximumAddress; Address >= MINIMUM_ALLOCATION_ADDRESS; Address -= mPageSize) { | |
| #if defined (_WIN32) || defined (_WIN64) | |
| AllocatedAddress = VirtualAlloc ( | |
| (VOID *)Address, | |
| Length, | |
| MEM_RESERVE | MEM_COMMIT, | |
| PAGE_READWRITE | |
| ); | |
| if (AllocatedAddress != NULL) { | |
| return AllocatedAddress; | |
| } | |
| #elif defined (__linux__) | |
| AllocatedAddress = mmap ( | |
| (VOID *)Address, | |
| Length, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, | |
| -1, | |
| 0 | |
| ); | |
| if (AllocatedAddress != MAP_FAILED) { | |
| return AllocatedAddress; | |
| } | |
| #else | |
| return NULL; | |
| #endif | |
| } | |
| return NULL; | |
| } | |
| /** | |
| Use system services to free memory allocated with HostAllocateBufferInRange(). | |
| @param[in] Buffer Pointer to buffer previously allocated with | |
| HostAllocateBufferInRange(). | |
| @param[in] Length Length, in bytes, of buffer previously allocated with | |
| HostAllocateBufferInRange(). | |
| **/ | |
| static | |
| VOID | |
| HostFreeBufferInRange ( | |
| IN VOID *Buffer, | |
| IN UINTN Length | |
| ) | |
| { | |
| #if defined (_WIN32) || defined (_WIN64) | |
| if (!VirtualFree (Buffer, 0, MEM_RELEASE)) { | |
| ASSERT (FALSE); | |
| } | |
| #elif defined (__linux__) | |
| if (munmap (Buffer, Length) == -1) { | |
| ASSERT (FALSE); | |
| } | |
| #endif | |
| } | |
| /** | |
| Allocate memory below a specific address. | |
| @param[in] MaximumAddress The address below which the memory allocation must | |
| be performed. | |
| @param[in] Length The size, in bytes, of the memory allocation. | |
| @retval !NULL Pointer to the allocated memory. | |
| @retval NULL The memory allocation failed. | |
| **/ | |
| VOID * | |
| EFIAPI | |
| HostAllocatePoolBelowAddress ( | |
| IN UINT64 MaximumAddress, | |
| IN UINT64 Length | |
| ) | |
| { | |
| VOID *AllocatedAddress; | |
| POOL_HEAD_BELOW_ADDRESS *PoolHead; | |
| if (Length == 0) { | |
| return NULL; | |
| } | |
| // | |
| // Limit maximum address to the largest supported virtual address | |
| // | |
| MaximumAddress = MIN (MaximumAddress, MAX_UINTN); | |
| // | |
| // Increase requested allocation length by the size of the pool header | |
| // | |
| Length += sizeof (POOL_HEAD_BELOW_ADDRESS); | |
| // | |
| // Make sure allocation length is smaller than maximum address | |
| // | |
| if (Length > MaximumAddress) { | |
| DEBUG ((DEBUG_ERROR, "HostAllocatePoolBelowAddress: Length > MaximumAddress\n")); | |
| return NULL; | |
| } | |
| // | |
| // Reduce maximum address by the requested allocation length | |
| // | |
| MaximumAddress -= Length; | |
| AllocatedAddress = HostAllocateBufferInRange ( | |
| (UINTN)MaximumAddress, | |
| (UINTN)Length | |
| ); | |
| if (AllocatedAddress == NULL) { | |
| DEBUG ((DEBUG_ERROR, "HostAllocatePoolBelowAddress: HostAllocateBufferInRange failed\n")); | |
| return NULL; | |
| } | |
| DEBUG_CLEAR_MEMORY (AllocatedAddress, (UINTN)Length); | |
| PoolHead = (POOL_HEAD_BELOW_ADDRESS *)AllocatedAddress; | |
| PoolHead->Signature = POOL_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE; | |
| PoolHead->TotalSize = Length; | |
| return (VOID *)(PoolHead + 1); | |
| } | |
| /** | |
| Free memory allocated with HostAllocatePoolBelowAddress(). | |
| @param[in] Buffer Pointer to buffer previously allocated with | |
| HostAllocatePoolBelowAddress(). | |
| **/ | |
| VOID | |
| EFIAPI | |
| HostFreePoolBelowAddress ( | |
| IN VOID *Buffer | |
| ) | |
| { | |
| POOL_HEAD_BELOW_ADDRESS *PoolHead; | |
| UINTN Length; | |
| ASSERT (Buffer != NULL); | |
| PoolHead = ((POOL_HEAD_BELOW_ADDRESS *)Buffer) - 1; | |
| ASSERT (PoolHead != NULL); | |
| ASSERT (PoolHead->Signature == POOL_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE); | |
| ASSERT (PoolHead->TotalSize >= sizeof (POOL_HEAD_BELOW_ADDRESS)); | |
| ASSERT (PoolHead->TotalSize <= MAX_UINTN); | |
| Length = (UINTN)PoolHead->TotalSize; | |
| DEBUG_CLEAR_MEMORY (PoolHead, Length); | |
| HostFreeBufferInRange (PoolHead, Length); | |
| } | |
| /** | |
| Allocates one or more 4KB pages below a specified address at a specified | |
| alignment. | |
| Allocates the number of 4KB pages specified by Pages below MaximumAddress with | |
| an alignment specified by Alignment. The allocated buffer is returned. If | |
| Pages is 0, then NULL is returned. If there is not enough memory below the | |
| requested address at the specified alignment remaining to satisfy the request, | |
| then NULL is returned. | |
| If Alignment is not a power of two and Alignment is not zero, then ASSERT(). | |
| If Pages plus EFI_SIZE_TO_PAGES (Alignment) overflows, then ASSERT(). | |
| @param[in] MaximumAddress The address below which the memory allocation must | |
| @param[in] Pages The number of 4 KB pages to allocate. | |
| @param[in] Alignment The requested alignment of the allocation. Must be | |
| a power of two. If Alignment is zero, then byte | |
| alignment is used. | |
| @return A pointer to the allocated buffer or NULL if allocation fails. | |
| **/ | |
| VOID * | |
| EFIAPI | |
| HostAllocateAlignedPagesBelowAddress ( | |
| IN UINT64 MaximumAddress, | |
| IN UINTN Pages, | |
| IN UINT64 Alignment | |
| ) | |
| { | |
| PAGE_HEAD_BELOW_ADDRESS PageHead; | |
| PAGE_HEAD_BELOW_ADDRESS *PageHeadPtr; | |
| UINTN AlignmentMask; | |
| UINTN Length; | |
| if (Pages == 0) { | |
| return NULL; | |
| } | |
| // | |
| // Make sure alignment is a power of two | |
| // | |
| if ((Alignment & (Alignment - 1)) != 0) { | |
| DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: Alignment is not a power of two\n")); | |
| return NULL; | |
| } | |
| // | |
| // Make sure alignment is smaller than the largest supported virtual address | |
| // | |
| if (Alignment > MAX_UINTN) { | |
| DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: Alignment > MAX_UINTN\n")); | |
| return NULL; | |
| } | |
| // | |
| // Make sure alignment is at least 4KB | |
| // | |
| Alignment = MAX (Alignment, SIZE_4KB); | |
| // | |
| // Initialize local page head structure | |
| // | |
| PageHead.Signature = PAGE_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE; | |
| PageHead.AlignedPages = Pages; | |
| PageHead.TotalPages = Pages + 2 * EFI_SIZE_TO_PAGES ((UINTN)Alignment); | |
| // | |
| // Limit maximum address to the largest supported virtual address | |
| // | |
| MaximumAddress = MIN (MaximumAddress, MAX_UINTN); | |
| // | |
| // Make sure total page allocation fits below maximum address | |
| // | |
| if (PageHead.TotalPages >= EFI_SIZE_TO_PAGES (MaximumAddress)) { | |
| DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: TotalPages >= MaximumAddress\n")); | |
| return NULL; | |
| } | |
| // | |
| // Determine the length of the allocation in bytes | |
| // | |
| Length = EFI_PAGES_TO_SIZE (PageHead.TotalPages); | |
| // | |
| // Reduce maximum address by the total allocation length | |
| // | |
| MaximumAddress -= Length; | |
| // | |
| // Allocate buffer large enough to support aligned page request | |
| // | |
| PageHead.AllocatedBuffer = HostAllocateBufferInRange ( | |
| (UINTN)MaximumAddress, | |
| Length | |
| ); | |
| if (PageHead.AllocatedBuffer == NULL) { | |
| DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: HostAllocateBufferInRange failed\n")); | |
| return NULL; | |
| } | |
| DEBUG_CLEAR_MEMORY (PageHead.AllocatedBuffer, Length); | |
| AlignmentMask = ((UINTN)Alignment - 1); | |
| PageHead.AlignedBuffer = (VOID *)(((UINTN)PageHead.AllocatedBuffer + AlignmentMask) & ~AlignmentMask); | |
| if ((UINTN)PageHead.AlignedBuffer - (UINTN)PageHead.AllocatedBuffer < sizeof (PAGE_HEAD_BELOW_ADDRESS)) { | |
| PageHead.AlignedBuffer = (VOID *)((UINTN)PageHead.AlignedBuffer + (UINTN)Alignment); | |
| } | |
| PageHeadPtr = (PAGE_HEAD_BELOW_ADDRESS *)((UINTN)PageHead.AlignedBuffer) - 1; | |
| memcpy (PageHeadPtr, &PageHead, sizeof (PageHead)); | |
| return PageHead.AlignedBuffer; | |
| } | |
| /** | |
| Frees one or more 4KB pages that were previously allocated with | |
| HostAllocateAlignedPagesBelowAddress(). | |
| Frees the number of 4KB pages specified by Pages from the buffer specified by | |
| Buffer. Buffer must have been allocated with HostAllocateAlignedPagesBelowAddress(). | |
| If it is not possible to free allocated pages, then this function will perform | |
| no actions. | |
| If Buffer was not allocated with HostAllocateAlignedPagesBelowAddress(), then | |
| ASSERT(). If Pages is zero, then ASSERT(). | |
| @param[in] Buffer The pointer to the buffer of pages to free. | |
| @param[in] Pages The number of 4 KB pages to free. | |
| **/ | |
| VOID | |
| EFIAPI | |
| HostFreeAlignedPagesBelowAddress ( | |
| IN VOID *Buffer, | |
| IN UINTN Pages | |
| ) | |
| { | |
| PAGE_HEAD_BELOW_ADDRESS *PageHeadPtr; | |
| VOID *AllocatedBuffer; | |
| UINTN Length; | |
| ASSERT (Buffer != NULL); | |
| PageHeadPtr = ((PAGE_HEAD_BELOW_ADDRESS *)Buffer) - 1; | |
| ASSERT (PageHeadPtr != NULL); | |
| ASSERT (PageHeadPtr->Signature == PAGE_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE); | |
| ASSERT (PageHeadPtr->AlignedPages == Pages); | |
| ASSERT (PageHeadPtr->AllocatedBuffer != NULL); | |
| AllocatedBuffer = PageHeadPtr->AllocatedBuffer; | |
| Length = EFI_PAGES_TO_SIZE (PageHeadPtr->TotalPages); | |
| DEBUG_CLEAR_MEMORY (AllocatedBuffer, Length); | |
| HostFreeBufferInRange (AllocatedBuffer, Length); | |
| } |