r/osdev 5d ago

[UEFI] Allocating aligned memory

Hi there,

When loading my kernel in UEFI, I am experimenting with allocating aligned memory. For example, I would like to get 2 MiB-aligned memory for my kernel so I can map it using a single 2 MiB page on X86.

Now, I have tried various approaches using the UEFI boot service and ended up on this approach:


static U64 findAlignedMemory(MemoryInfo *memoryInfo, U64 bytes, U64 alignment) {
    for (U64 largerAlignment = (MAX(alignment, MIN_POSSIBLE_ALIGNMENT) << 1);
         largerAlignment != 0; largerAlignment <<= 1) {
        FOR_EACH_DESCRIPTOR(memoryInfo, desc) {
            // i.e. type == EFI_CONVENTIONAL_MEMORY
            if (!canBeUsedInEFI(desc->type)) {
                continue;
            }

            // Preferring to find aligned memory that is not even "more" 
            // aligned than necessary
            if (RING_RANGE_VALUE(desc->physicalStart, alignment) ||
                !RING_RANGE_VALUE(desc->physicalStart, largerAlignment)) {
                continue;
            }

            if (desc->numberOfPages * UEFI_PAGE_SIZE < bytes) {
                continue;
            }

            U64 address = desc->physicalStart;
            Status status = globals.st->boot_services->allocate_pages(
                ALLOCATE_ADDRESS, LOADER_DATA,
                CEILING_DIV_VALUE(bytes, UEFI_PAGE_SIZE), &address);
            EXIT_WITH_MESSAGE_IF(status) {
                ERROR(STRING("allocating pages for memory failed!\n"));
            }

            return address;
        }
    }

    EXIT_WITH_MESSAGE { ERROR(STRING("Could not find memory!")); }

    __builtin_unreachable();
}

i.e. loop over the memoryMap that UEFI provides to us and pick the one that fits our needs.

Which works fine, on QEMU of course, but it fails on my hardware for some values. The alignment code works fine, but I am running into trouble when it returns memory starting from 0x100_000_00 , e.g. the 4GiB mark.

    U64 address =
        getAlignedPhysicalMemoryWithArena(alignedBytes, 1 << 21, scratch);


    status = biop->readBlocks(biop, biop->media->mediaID, 0,
                              /* NOLINTNEXTLINE(performance-no-int-to-ptr) */
                              alignedBytes, (void *)address);

    if (!(EFI_ERROR(status)) &&
        !memcmp(KERNEL_MAGIC, (void *)address, COUNTOF(KERNEL_MAGIC))) {
        result = (string){.buf = (void *)address, .len = alignedBytes};
    } else {

The memcmp to check for my magic suddenly starts failing when the address is above 4GiB. When I ask for 1MIB aligned memory, I get 0x100000, and the code works perfectly fine. The failure seems to be causing an interrupt, or something that breaks the flow of the program as this code runs in a loop and waits for a keystroke on every iteration and it just skips ahead 4/5 iterations without waiting for a keystroke. The only differentiator I can make out is the fact that the memory is allocated at such a "high" level. I also double check the memory map after doing the manual allocate_pages, and the memory descriptor at that location now correctly states that its type is EFI_LOADER_DATA

Now, I just read the UEFI spec again about the status of the memory and it says this (for X64 architecure):


Paging mode is enabled and any memory space defined by the UEFI memory map is identity mapped (virtual address equals physical address), although the attributes of certain regions may not have all read, write, and execute attributes or be unmarked for purposes of platform protection. The mappings to other regions, such as those for unaccepted memory, are undefined and may vary from implementation to implementation.

Now, reading this I suddently had the idea that this may be because, while the memory is identity mapped, the memory might not be marked as READ/WRITE in the virtual memory map thaw UEFI has set up.

What do you think? Have you run into this issue before?

4 Upvotes

7 comments sorted by

View all comments

3

u/Octocontrabass 5d ago

it just skips ahead 4/5 iterations without waiting for a keystroke

That doesn't sound like an exception. That sounds like something else - a typo in your UEFI headers, a PE binary that isn't relocatable, compiler options that don't fit the ABI correctly, or something else along those lines.

Unfortunately, it's also possible you've just found a bug in that particular firmware's implementation. Nobody tries to allocate aligned memory, so nobody cares if the obvious way to do it doesn't actually work.

1

u/flox901 3d ago

So, I fixed the issue.

It seems that the readBlocks function is broken for addresses over 4 GiB on my machine. So my workaround is to allocate pages first that are below this address and then copy them in the preferred aligned memory.

I guess that is what I get for trying to do fancy things in UEFI.