UEFI: Getting 'EFI_NOT_FOUND' error when allocating pages.

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
stillconfused
Posts: 18
Joined: Fri Nov 08, 2019 5:35 pm

UEFI: Getting 'EFI_NOT_FOUND' error when allocating pages.

Post by stillconfused »

Hello,
I'm writing a UEFI bootloader for a hobby kernel.
I'm getting an 'EFI_NOT_FOUND' error when using the 'AllocatePages' UEFI function to allocate pages at a specific physical address. I'm able to successfully load and parse the kernel ELF file. I'm able to allocate pages using the 'AllocateAnyPages' allocation type, then copy the kernel image into memory without any problems.
If I use the 'AllocateAddress' allocation type to allocate pages at a specific address, I get an 'EFI_NOT_FOUND' error. The spec states that this indicates "The requested pages could not be found".

The UEFI spec states:
Allocation requests of Type AllocateAnyPages allocate any available range of pages that satisfies the
request. On input, the address pointed to by Memory is ignored.
However if I use 'AllocateAnyPages', and pass in the same `p_paddr` value from the ELF file's program header, the pages are actually allocated at the physical address specified. This confusing behavior occurs in both QEMU (using OVMF) and on real hardware. Due to UEFI identity mapping the address space, I can actually load the kernel into memory using 'AllocateAnyPages', exit boot services, jump to the kernel entry point, and successfully load the kernel.
Does anyone know why this is happening? Am I overlooking something critical in my design?

I'm also confused about where to place the kernel in physical memory. The linker script for my test kernel specifies that the kernel starts at physical address 1MB. I know that UEFI doesn't guarantee any physical address is free, however no test value I've used has yielded a valid value when using the 'AllocateAddress' allocation type. Should I perform all of the virtual memory mapping before jumping to the kernel, and not worry about the kernel's physical address at all?
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by Octocontrabass »

stillconfused wrote:Does anyone know why this is happening? Am I overlooking something critical in my design?
What does your code to call AllocatePages() look like? I suspect you're calling it with the wrong parameters.
stillconfused wrote:Should I perform all of the virtual memory mapping before jumping to the kernel, and not worry about the kernel's physical address at all?
Yes.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by nullplan »

stillconfused wrote:I'm also confused about where to place the kernel in physical memory.
Yeah, so are a lot of people. With UEFI, you cannot assume any physical address to be free when loading the kernel. Not 0, not 1MB, not 16MB, not anything. Therefore, attempting to run a kernel in identity mapped mode is only possible if the kernel is position independent. That requires a lot more loader support than I would be willing to spend, so the alternative is to simply not identity map the kernel. I have a higher-half kernel that is mapped to -2GB, to take advantage of the kernel code model. You can do the same, or you can link the kernel as a completely normal userspace executable, your choice. Then the position of the kernel in physical memory can be abstracted by the virtual mapping.

This does require some work in the kernel itself: You cannot simply start to use physical addresses. If you know a physical address and want to access it, you must map it into virtual space. However, this is also very simple, since in 64-bit mode you have so much virtual space to work with. I have a linear mapping of all physical memory starting at 0xffff800000000000, so mapping a given physical address amounts to checking that the address is in range of the mapping, and then adding that constant.

As I said, the alternative is that you link the kernel as a PIE. Good luck with that. The result should be an executable with ELF type ET_DYN, that contains a relocation table. You can put it anywhere in memory, but must process the relocations.
Carpe diem!
stillconfused
Posts: 18
Joined: Fri Nov 08, 2019 5:35 pm

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by stillconfused »

Octocontrabass wrote:
stillconfused wrote:Does anyone know why this is happening? Am I overlooking something critical in my design?
What does your code to call AllocatePages() look like? I suspect you're calling it with the wrong parameters.
Here's where I make the call to 'AllocatePages()'. The parameters are exactly as written:

Code: Select all

	status = uefi_call_wrapper(gBS->AllocatePages, 4,
		AllocateAnyPages, EfiLoaderData, segment_page_count,
		(EFI_PHYSICAL_ADDRESS*)segment_physical_address);
'segment_physical_address' is the 'p_paddr' attribute from the ELF program header.
I know it would be more correct to use 'EfiLoaderCode' for the memory type. It doesn't seem to make a difference, oddly enough. I'll resolve this eventually.
Keep in mind that the code you're seeing will actually successfully load the kernel. Even on real hardware. So it's unlikely that it's too wrong. I've checked things like the page count, and it seems correct.
Last edited by stillconfused on Fri Aug 05, 2022 1:45 am, edited 1 time in total.
stillconfused
Posts: 18
Joined: Fri Nov 08, 2019 5:35 pm

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by stillconfused »

nullplan wrote: Yeah, so are a lot of people. With UEFI, you cannot assume any physical address to be free when loading the kernel....
Thank you for your very detailed response. I'll keep this in mind!
I intend to have a higher half kernel in the long run, however for the sake of developing the bootloader first I just developed the minimum viable product, or so I thought.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by nullplan »

stillconfused wrote:I intend to have a higher half kernel in the long run, however for the sake of developing the bootloader first I just developed the minimum viable product, or so I thought.
You still can. Minimum viable kernel is something like:

Code: Select all

kernel.S:
.text
_start:
  cli
  hlt
  jmp _start

link.ld:
SECTIONS {
  . = 0xffffffff80000000 + SIZEOF_HEADERS;
  .text: {
    *(.text)
    *(.text.*)
  }
  .rodata: {
    *(.rodata)
    *(.rodata.*)
  }
  . = ALIGN(4096);
  .data: {
    *(.data)
    *(.data.*)
  }
  .bss: {
    *(.bss)
    *(.bss.*)
    *(COMMON)
  }

Makefile:
kernel.elf: kernel.o link.ld
  x86_64-elf-gcc -ffreestanding -mgeneral-regs-only -O3 -mcmodel=kernel -mno-red-zone -nostdlib -Wl,-z,max-page-size=0x1000 -Wl,-T,link.ld -o kernel.elf kernel.o

kernel.o: kernel.S
  x86_64-elf-gcc -ffreestanding -mgeneral-regs-only -O3 -mcmodel=kernel -mno-red-zone -o kernel.o kernel.S
There. Kernel is essentially a normal ELF executable, except for the high load address and the alignment hole in the middle. It does nothing but halt the system. With UEFI, bootloader needs to set up the initial environment. Kernel can still additionally load itself afterwards, in order to discard the bootloader mappings and take control of the PML4 completely (for example, in my case, it is the kernel that decides to use global pages and map the kernel globally), but the initial mapping has to be done by the bootloader.
Carpe diem!
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by Octocontrabass »

stillconfused wrote:

Code: Select all

uefi_call_wrapper
GNU-EFI is full of ugly hacks like this, but I thought you didn't actually need uefi_call_wrapper anymore. Recent enough versions of GCC should be able to directly emit function calls using the correct ABI.
stillconfused wrote:

Code: Select all

(EFI_PHYSICAL_ADDRESS*)segment_physical_address
That looks like a problem. Why do you have this cast here? I suspect it's not doing what you want it to do. Did you actually want to use the address-of operator?
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by kzinti »

An alternative that I use is simply to have a relocatable trampoline to jump to your kernel.

1) Allocate a page of memory for the trampoline under the kernel's starting address. For example if your kernel is at 0xF0000000, you can use that with UEFi's `maxAddress` allocation type. This will ensure that the memory page allocated is under 0xF0000000 and won't conflict with your kernel's virtual memory range.
2) Copy relocatable trampoline code to the memory allocated in #1.
3) ExitBootServices()
4) Update virtual memory map (CR3) with mappings for kernel. Make sure the anything under the kernel space is identity-mapped.
5) Jump to the relocated trampoline with the address of the kernel's entry point.

For example (note that I do it slightly differently from described above, I exit boot services before allocating the trampoline memory because I don't rely on UEFI at that point, I also identity-map the first 4GB of memory as my kernel's starting virtual address is far above that):

trampoline itself:
https://github.com/kiznit/rainbow-os/bl ... line.S#L35

code to copy and jump to the kernel using the relocated trampoline
https://github.com/kiznit/rainbow-os/bl ... ne.cpp#L40
stillconfused
Posts: 18
Joined: Fri Nov 08, 2019 5:35 pm

Re: UEFI: Getting 'EFI_NOT_FOUND' error when allocating page

Post by stillconfused »

Octocontrabass wrote:Why do you have this cast here? I suspect it's not doing what you want it to do. Did you actually want to use the address-of operator?
Oh wow... This is very embarrassing. You're totally right. Thank you for pointing this out! I was missing the address-of operator! I put the cast there because I compile with strict settings, and I would get a warning otherwise. Ironically in this case it actually suppressed the warning that would have given away the problem.
The call actually works as expected now.

The topic has been very valuable for other reasons too. I'll take the other suggestions on board as well!
Post Reply