/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #if defined (_WIN32) || defined (_WIN64) #include "WinInclude.h" #elif defined (__linux__) #include #include #include #include #else #error Unsupported target #endif #include #include /// /// 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); }