/*++ Copyright (c) 2024, Quinn Stephens. Provided under the BSD 3-Clause license. Module Name: efimm.c Abstract: Provides EFI memory manager routines. --*/ #include "bootlib.h" #include "efi.h" #include "efilib.h" #include "mm.h" #define _1MiB 1048576 #define EFI_PAGE(NtPage) (((NtPage) << PAGE_SHIFT) >> EFI_PAGE_SHIFT) #define NT_PAGE(EfiPage) (((EfiPage) << EFI_PAGE_SHIFT) >> PAGE_SHIFT) MEMORY_TYPE BlMmTranslateEfiMemoryType ( IN EFI_MEMORY_TYPE EfiMemoryType ) /*++ Routine Description: Translates an EFI memory type to an NT memory type. Arguments: EfiMemoryType - the EFI memory type. Return Value: The NT memory type. --*/ { switch (EfiMemoryType) { case EfiConventionalMemory: return MEMORY_TYPE_FREE; case EfiLoaderCode: case EfiLoaderData: return MEMORY_TYPE_BOOT_APPLICATION; case EfiBootServicesCode: case EfiBootServicesData: return MEMORY_TYPE_BOOT_SERVICES; case EfiRuntimeServicesCode: return MEMORY_TYPE_RUNTIME_SERVICES_CODE; case EfiRuntimeServicesData: return MEMORY_TYPE_RUNTIME_SERVICES_DATA; case EfiUnusableMemory: return MEMORY_TYPE_UNUSABLE; case EfiACPIReclaimMemory: return MEMORY_TYPE_ACPI_RECLAIM; case EfiACPIMemoryNVS: return MEMORY_TYPE_ACPI_NVS; case EfiMemoryMappedIO: return MEMORY_TYPE_MMIO; case EfiMemoryMappedIOPortSpace: return MEMORY_TYPE_MMIO_PORT_SPACE; case EfiPalCode: return MEMORY_TYPE_PAL_CODE; case EfiPersistentMemory: return MEMORY_TYPE_PERSISTENT; case EfiReservedMemoryType: default: if ((ULONG)EfiMemoryType < MAXLONG) { return MEMORY_TYPE_RESERVED; } else { return (MEMORY_TYPE)EfiMemoryType; } } } ULONG MmFwpGetOsAttributeType ( IN UINT64 EfiAttributes ) /*++ Routine Description: Translates EFI memory descriptor attributes to NT memory descriptor attributes. Arguments: EfiAttributes - the EFI attributes. Return Value: The NT attributes. --*/ { ULONG NtAttributes; NtAttributes = 0; if (EfiAttributes & EFI_MEMORY_UC) { NtAttributes |= MEMORY_ATTRIBUTE_UC; } if (EfiAttributes & EFI_MEMORY_WC) { NtAttributes |= MEMORY_ATTRIBUTE_WC; } if (EfiAttributes & EFI_MEMORY_WT) { NtAttributes |= MEMORY_ATTRIBUTE_WT; } if (EfiAttributes & EFI_MEMORY_WB) { NtAttributes |= MEMORY_ATTRIBUTE_WB; } if (EfiAttributes & EFI_MEMORY_UCE) { NtAttributes |= MEMORY_ATTRIBUTE_UCE; } if (EfiAttributes & EFI_MEMORY_WP) { NtAttributes |= MEMORY_ATTRIBUTE_WP; } if (EfiAttributes & EFI_MEMORY_RP) { NtAttributes |= MEMORY_ATTRIBUTE_RP; } if (EfiAttributes & EFI_MEMORY_XP) { NtAttributes |= MEMORY_ATTRIBUTE_XP; } if (EfiAttributes & EFI_MEMORY_RUNTIME) { NtAttributes |= MEMORY_ATTRIBUTE_RUNTIME; } return NtAttributes; } NTSTATUS MmFwGetMemoryMap ( IN OUT PMEMORY_DESCRIPTOR_LIST Mdl, IN ULONG Flags ) /*++ Routine Description: Gets the memory map from EFI and converts it into an MDL. Arguments: Mdl - Pointer to the MDL to store new descriptors in. Flags - Unused. Return Value: STATUS_SUCCESS if successful, STATUS_INVALID_PARAMETER if Mdl is invalid. --*/ { NTSTATUS Status; EFI_PHYSICAL_ADDRESS EfiBuffer; EFI_MEMORY_DESCRIPTOR *EfiMap; UINTN EfiMapKey; UINTN EfiMapSize, EfiDescriptorSize; UINT32 EfiDescriptorVersion; UINT64 EfiStartPage, EfiEndPage; UINTN EfiPageCount; ULONGLONG NtStartPage; ULONG NtPageCount; BOOLEAN IsRamdisk; UINT64 EfiRamdiskStartPage, EfiRamdiskEndPage; MEMORY_TYPE MemoryType; PMEMORY_DESCRIPTOR NtDescriptor; (VOID)Flags; EfiBuffer = (EFI_PHYSICAL_ADDRESS)0; if (Mdl == NULL) { return STATUS_INVALID_PARAMETER; } MmMdFreeList(Mdl); // // Get the memory map from firmware. // This is a "fake" call to actually just get // the required buffer size and other info. // EfiMapSize = 0; Status = EfiGetMemoryMap(&EfiMapSize, NULL, &EfiMapKey, &EfiDescriptorSize, &EfiDescriptorVersion); if (Status != STATUS_BUFFER_TOO_SMALL) { DebugPrint(L"MmFwGetMemoryMap(): EfiGetMemoryMap() failed\r\n"); // // Make sure status is not successful, just in case // EfiGetMemoryMap() somehow succeeded. // if (NT_SUCCESS(Status)) { Status = STATUS_UNSUCCESSFUL; } goto exit; } EfiMapSize += 4 * EfiDescriptorSize; NtPageCount = (ALIGN_UP(EfiMapSize, PAGE_SIZE) >> PAGE_SHIFT) + 1; EfiPageCount = EFI_PAGE(NtPageCount); Status = EfiAllocatePages(AllocateAnyPages, EfiLoaderData, EfiPageCount, &EfiBuffer); if (!NT_SUCCESS(Status)) { DebugPrint(L"MmFwGetMemoryMap(): EfiAllocatePages() failed\r\n"); goto exit; } EfiMap = (EFI_MEMORY_DESCRIPTOR *)EfiBuffer; Status = EfiGetMemoryMap(&EfiMapSize, EfiMap, &EfiMapKey, &EfiDescriptorSize, &EfiDescriptorVersion); if (!NT_SUCCESS(Status)) { DebugPrint(L"MmFwGetMemoryMap(): EfiGetMemoryMap() failed\r\n"); goto exit; } if (EfiDescriptorSize < sizeof(EFI_MEMORY_DESCRIPTOR) || EfiMapSize % EfiDescriptorSize) { DebugPrint(L"MmFwGetMemoryMap(): Invalid EFI descriptor/map sizes\r\n"); Status = STATUS_UNSUCCESSFUL; goto exit; } if (BlpBootDevice->Type == BOOT_DEVICE_TYPE_BLOCK && BlpBootDevice->Block.Type == BOOT_BLOCK_DEVICE_TYPE_RAMDISK) { IsRamdisk = TRUE; EfiRamdiskStartPage = BlpBootDevice->Block.Ramdisk.ImageBase.QuadPart >> EFI_PAGE_SHIFT; EfiRamdiskEndPage = EfiRamdiskStartPage + ((BlpBootDevice->Block.Ramdisk.ImageBase.QuadPart + BlpBootDevice->Block.Ramdisk.ImageSize) >> EFI_PAGE_SHIFT); } else { IsRamdisk = FALSE; } for ( ; EfiMapSize > 0; EfiMapSize -= EfiDescriptorSize, EfiMap = (EFI_MEMORY_DESCRIPTOR *)((PUCHAR)EfiMap + EfiDescriptorSize) ) { // // Skip desciptors with no pages. // if (EfiMap->NumberOfPages == 0) { continue; } MemoryType = BlMmTranslateEfiMemoryType(EfiMap->Type); if (MemoryType == MEMORY_TYPE_FREE) { EfiStartPage = ALIGN_UP(EfiMap->PhysicalStart, EFI_PAGE_SIZE) >> EFI_PAGE_SHIFT; } else { EfiStartPage = EfiMap->PhysicalStart >> EFI_PAGE_SHIFT; } EfiEndPage = ALIGN_DOWN(EfiStartPage + EfiMap->NumberOfPages, EFI_PAGE(1)); // // Regions under 1MiB are mapped differently. // if (EfiStartPage < (_1MiB >> EFI_PAGE_SHIFT)) { // // Reserve region at page 0. // if (EfiStartPage == 0) { NtDescriptor = MmMdInitDescriptor( NT_PAGE(EfiStartPage), 0, 1, MmFwpGetOsAttributeType(EfiMap->Attribute), MEMORY_TYPE_RESERVED ); if (NtDescriptor == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE); if (!NT_SUCCESS(Status)) { MmMdFreeDescriptor(NtDescriptor); goto exit; } // // Continue if this descriptor was only one page. // if (EfiEndPage == 1) { continue; } } // // For regions crossing over the 1MiB boundary, // create two descriptors. One for <1MiB and one // for over >=1MiB. // if (EfiEndPage > (_1MiB >> EFI_PAGE_SHIFT)) { NtDescriptor = MmMdInitDescriptor( NT_PAGE(EfiStartPage), 0, (_1MiB >> PAGE_SHIFT) - NT_PAGE(EfiStartPage), MmFwpGetOsAttributeType(EfiMap->Attribute), MemoryType ); if (NtDescriptor == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (NtDescriptor->Type == MEMORY_TYPE_FREE) { NtDescriptor->Attributes |= MEMORY_ATTRIBUTE_BELOW_1MIB; } Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE); if (!NT_SUCCESS(Status)) { MmMdFreeDescriptor(NtDescriptor); goto exit; } // // Continue to creating the >=1MiB mapping. // EfiStartPage = _1MiB >> EFI_PAGE_SHIFT; } } if (IsRamdisk) { if ( EfiStartPage <= EfiRamdiskStartPage && EfiEndPage >= EfiRamdiskEndPage ) { if (NT_PAGE(EfiStartPage) < NT_PAGE(EfiRamdiskStartPage)) { NtDescriptor = MmMdInitDescriptor( NT_PAGE(EfiStartPage), 0, NT_PAGE(EfiRamdiskStartPage) - NT_PAGE(EfiStartPage), MmFwpGetOsAttributeType(EfiMap->Attribute), MemoryType ); if (NtDescriptor == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE); if (!NT_SUCCESS(Status)) { MmMdFreeDescriptor(NtDescriptor); goto exit; } } // // Create a memory descriptor for the ramdisk. // NtDescriptor = MmMdInitDescriptor( NT_PAGE(EfiRamdiskStartPage), 0, NT_PAGE(EfiRamdiskEndPage) - NT_PAGE(EfiRamdiskStartPage), MmFwpGetOsAttributeType(EfiMap->Attribute), MemoryType ); if (NtDescriptor == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE); if (!NT_SUCCESS(Status)) { MmMdFreeDescriptor(NtDescriptor); goto exit; } // // Continue if there is no more memory to map inside the ramdisk. // if (NT_PAGE(EfiEndPage) <= NT_PAGE(EfiRamdiskEndPage)) { continue; } EfiStartPage = EFI_PAGE(NT_PAGE(EfiRamdiskEndPage - 1) + 1); } else if ( NT_PAGE(EfiStartPage) < NT_PAGE(EfiRamdiskStartPage) && NT_PAGE(EfiEndPage) > NT_PAGE(EfiRamdiskStartPage) ) { // // Remove the region inside the start of the ramdisk. // EfiEndPage = EfiRamdiskStartPage; } else if ( NT_PAGE(EfiStartPage) < NT_PAGE(EfiRamdiskEndPage) && NT_PAGE(EfiEndPage) > NT_PAGE(EfiRamdiskEndPage) ) { // // Remove the region inside the end of the ramdisk. // EfiStartPage = EfiRamdiskEndPage; } // // Continue if the region is now empty. // if (NT_PAGE(EfiStartPage) == NT_PAGE(EfiEndPage)) { continue; } } // // Create a memory descriptor for the region. // NtDescriptor = MmMdInitDescriptor( NT_PAGE(EfiStartPage), 0, NT_PAGE(EfiEndPage) - NT_PAGE(EfiStartPage), MmFwpGetOsAttributeType(EfiMap->Attribute), MemoryType ); if (NtDescriptor == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } // // Set attribute if below 1MiB. // if (NtDescriptor->Type == MEMORY_TYPE_FREE && EfiEndPage <= (_1MiB >> EFI_PAGE_SHIFT)) { NtDescriptor->Attributes |= MEMORY_ATTRIBUTE_BELOW_1MIB; } Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE); if (!NT_SUCCESS(Status)) { MmMdFreeDescriptor(NtDescriptor); goto exit; } } // // The following code is to free the buffer and // also mark the freed memory as free in the MDL. // Status = EfiFreePages(EfiBuffer, EfiPageCount); if (!NT_SUCCESS(Status)) { Status = STATUS_SUCCESS; goto exit; } EfiBuffer = 0; EfiStartPage = EfiBuffer >> EFI_PAGE_SHIFT; EfiEndPage = ALIGN_UP(EfiStartPage + EfiPageCount, EFI_PAGE(1)); NtStartPage = NT_PAGE(EfiStartPage); NtPageCount = NT_PAGE(EfiEndPage) - NtStartPage; // // Find the current descriptor. // NtDescriptor = MmMdFindDescriptorFromMdl(Mdl, NtStartPage, MDL_OPERATION_FLAGS_PHYSICAL); if (NtDescriptor == NULL) { Status = STATUS_UNSUCCESSFUL; goto exit; } // // Create a new free descriptor, with the same // attributes as the current one. // NtDescriptor = MmMdInitDescriptor(NtStartPage, 0, NtPageCount, NtDescriptor->Attributes, MEMORY_TYPE_FREE); if (NtDescriptor == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } // // Remove the current descriptor. // Status = MmMdRemoveRegionFromMdl(Mdl, NtStartPage, NtPageCount, MDL_OPERATION_FLAGS_PHYSICAL); if (!NT_SUCCESS(Status)) { MmMdFreeDescriptor(NtDescriptor); goto exit; } // // Add the new descriptor to the MDL. // Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, 0x01); exit: if (EfiBuffer) { EfiFreePages(EfiBuffer, EfiPageCount); } if (!NT_SUCCESS(Status)) { MmMdFreeList(Mdl); } return Status; }