You do not need paging to load your kernel file(s) into memory. you do need paging enabled in the end of the loader work. if your kernel file(s) reside(s) on a FAT volume, you use EFI_SIMPLE_FILE_SYSTEM_PROTOCOL and EFI_FILE_PROTOCOL to read contents of the file(s) into memory. otherwise, you use Block/Disk I/O protocols to get data at the block level and by parsing filesystem in your loader, - get from there the file contents.
Either way, when loading your ELF file, you 1) work in not the same address space, that the kernel will be running in, and thus you don't need to request any specific address to put your ELF segments into, since, as you can see, it easily results in denial. you just allocate AnyPages and put your segments there, remembering their addresses/sizes. then, while in the loader and before ExitBootSevices(), you build up the page tables and map those ranges, where your segments have been loaded to whatever virtual addresses, those segments were linked and then, you do ExitBootServices() and switch to the address space, which the mapping you've just created for.
Whether you need to enable paging yourself, depends on the architecture, and overall - what you actually need to do does. For example on x64, paging is already enabled in UEFI, on x86, on the other hand, it's not and on ARM64, you could well be running in the Hypervisor Exception Level (EL2) address space - this means, that switching mechanics depends heavily on the architecture, but always you do it in the loader after ExitBootServices(). of course, for this switch to be successful, not only mapping for the ELF segments of your kernel file(s) should be established, the whole set of entities you mean to pass to the kernel and what the kernel expects to be there, should be accessible - stack, the freshly created page tables themselves, sort of loader parameter block passed to the kernel, other things of your design, they all have to have the mapping correctly set. the trick is whatever mode/state/level, your loader runs in, numerically, addresses are always 1:1 mapped to the system address space (physical address space), in the address space, it's running in originally, so you take these numbers and put them in the CR3/TTBRx, page tables entries etc. switching the address space to your kernel AS is done in some kind of trampoline code in the loader, at the latest phase, that code does additional CPU magic, required by an architecture. writing that thing is a real quest for the developer's understanding of the architecture, they program. creating and filling in the page tables on the other hand, can be done before, during the loader operation, for example, when you load your ELF file, you add mapping, whenever some block of data has been processed, like the ELF segment for example - it's up to the logic of your loader.
_________________ ANT - NT-like OS for x64 and arm64. efify - UEFI for a couple of boards (mips and arm). suspended due to lost of all the target park boards (russians destroyed our town).
|