分配EfiMemoryMappedIO内存时AllocatePages返回EFI_NOT_FOUND的问题排查
问题描述
尝试分配EfiMemoryMappedIO类型内存页供Windows驱动访问,定义了以下启动内存区域:
#define EFI_BOOT_MEMORY_REGIONS_LOW 0x90000, 0x88000, 0x80000 #define EFI_BOOT_MEMORY_REGIONS_HIGH \ 0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, 0x700000, 0x800000, \ 0x900000, 0xA00000, 0xB00000, 0xC00000, 0xD00000, 0xE00000, 0xF00000, 0x1000000 #define EFI_BOOT_MEMORY_REGIONS EFI_BOOT_MEMORY_REGIONS_LOW, EFI_BOOT_MEMORY_REGIONS_HIGH
遍历这些地址调用AllocatePages尝试分配,代码实现如下:
static EFI_STATUS PrepareMemory( IN UINTN Address, IN UINTN Len, OUT VOID** ppMemory) { EFI_PHYSICAL_ADDRESS PhysAddr; UINTN Pages = 0; VOID* pAllocatedMemory = NULL; EFI_STATUS Status = EFI_SUCCESS; PhysAddr = Address & ~0x0fff; // set alignment to page size (for x86, x86-64 page size if 0x1000) Pages = ((Len & ~0x0fff) + 0x1000) >> 12; Status = BS->AllocatePages(AllocateAddress, EfiMemoryMappedIO, Pages, &PhysAddr); if (EFI_ERROR(Status)) { LOG_ERROR_FN(L"!!! ERROR: Failed to prepare memory 0x%x (status = %d)", (UINTN)PhysAddr, Status); DbgEfiPrint((L"!!! ERROR: Failed to prepare memory 0x%x (status = %d)\n", (UINTN)PhysAddr, Status)); DbgEfiPressAnyKey(); return Status; } pAllocatedMemory = (void*)(UINTN)PhysAddr; SetMem(pAllocatedMemory, Pages << 12, 0); *ppMemory = pAllocatedMemory; return Status; } EFI_STATUS copy_into_boot_memory_region() { LOG_INFO_FN(L"enter"); UINTN BootMemoryRegions[] = { EFI_BOOT_MEMORY_REGIONS }; UINTN BootMemoryRegionIndex = 0; UINT8* pMemoryRegion = NULL; EFI_STATUS Status = EFI_NOT_FOUND; for (BootMemoryRegionIndex = 0; BootMemoryRegionIndex < sizeof(BootMemoryRegions) / sizeof(BootMemoryRegions[1]); ++BootMemoryRegionIndex) { Status = PrepareMemory(BootMemoryRegions[BootMemoryRegionIndex], 1024, (void**)&pMemoryRegion); if (!EFI_ERROR(Status)) { LOG_ERROR_FN(L"Copy into boot memory region: 0x%x", (UINTN)pMemoryRegion); DbgEfiPrint((L"Copy into boot memory region: 0x%x\n", (UINTN)pMemoryRegion)); DbgEfiPressAnyKey(); gBS->CopyMem(pMemoryRegion, (UINT8*) ADDRESS, 1024); break; } else { LOG_ERROR_FN(L"PrepareMemory() for boot memory region [%lu] failed with status = 0x%llX!", BootMemoryRegions[BootMemoryRegionIndex], Status); } } if (EFI_ERROR(Status)) { LOG_ERROR_FN(L"Failed to copy into boot memory region, status = 0x%llX!", Status); } LOG_INFO_FN(L"leave"); return Status; }
该逻辑取自VeraCryptLib,但目前所有区域调用AllocatePages均返回EFI_NOT_FOUND错误,需解答:
- 问题可能原因是什么?
- 是否可以添加更多地址扩大区域列表?若可以,哪些地址可用于分配
EfiMemoryMappedIO页? - 是否有其他方式通过内存将数据从UEFI传递到OS?
问题解答
一、EFI_NOT_FOUND错误的可能原因
- 目标地址已被占用:指定的内存区域可能已被UEFI固件、其他启动加载项或硬件预留使用,无法再以
EfiMemoryMappedIO类型分配。 - 内存类型不匹配:
AllocatePages的AllocateAddress模式要求目标地址所在区域原本属于可分配的EfiMemoryMappedIO类型,若该区域实际是EfiBootServicesCode、EfiReserved等其他类型,分配会失败。 - 页范围跨类型:即使做了页对齐,若目标地址对应的物理页范围跨越不同内存类型的区域,也会导致分配失败。
- 固件权限限制:部分UEFI固件会限制特定内存区域的分配权限,即使地址看似空闲,也不允许用户代码修改其类型或分配。
二、是否可扩大区域列表及可选地址
可以扩大区域列表,需选择符合以下条件的地址:
- 查询UEFI内存映射:调用
gBS->GetMemoryMap获取系统内存映射,筛选类型为EfiMemoryMappedIO且状态为EfiFree的区域,这些是合法的可分配目标。 - 传统空闲区域参考:
- 低端内存:0x60000到0x9FFFF之间(避开BIOS数据区0x400-0x4FF、VGA显存0xA0000-0xBFFFF)
- 高端内存:1MB到16MB之间未被硬件占用的区域,部分系统在0x100000-0x1000000区间存在空闲MMIO区域
- 规避硬件冲突:避开显卡、PCI设备等硬件的MMIO范围,这类区域通常在内存映射中标记为
EfiReserved或已占用的EfiMemoryMappedIO。
三、UEFI到OS的其他内存数据传递方式
EfiReserved类型内存:分配EfiReserved类型内存页,UEFI会保留该区域不被OS回收,Windows驱动可通过物理地址映射访问。- UEFI配置表:将数据结构注册到UEFI配置表中,OS启动后可通过查询配置表获取数据指针,这是标准的UEFI数据传递方式。
- ACPI表注入:自定义ACPI表并注入系统,Windows会解析ACPI表,驱动可从中读取预留的内存地址或数据。
- 启动参数传递:修改UEFI启动项参数,将数据地址或内容作为参数传递给OS,OS启动后驱动可从启动参数中提取数据。
内容的提问来源于stack exchange,提问作者Wolf Team FC
相关产品推荐
相关产品推荐

