PeterX wrote:
OK. I nearly don't believe it, but it makes sense, since a framebuffer is just a piece of (video) memory, isn't it? I thought I have to use UEFI runtime services/functions. I just don't know UEFI yet, only small parts of it. And I don't know the framebuffer yet. (One step after the other...)
I have no reason to lie
Almost all of the UEFI services are
boot services. They can be used only before the call to ExitBootServices(). Then, UEFI offers a few
runtime services that the OS can use at any time, plus it's possible to implement UEFI runtime drivers, but your OS doesn't necessarily really need to care about that. The OS might be totally agnostic, as Tilck is.
The framebuffer gets mapped by the firmware into a physical address in a special kind of non-cacheable memory area.
The UEFI bootloader queries (and maybe also sets) the current video mode and passes the video mode info to the kernel via the multiboot protocol. Let me show that in Tilck, where everything is simple:
1. In the exit path of efi_main(), the bootloader calls SetupMultibootInfo():
https://github.com/vvaltchev/tilck/blob ... main.c#L772. SetupMultibootInfo() calls MbiSetFramebufferInfo():
https://github.com/vvaltchev/tilck/blob ... boot.c#L30which does nothing more than saving info about the current video mode in a special MBI struct (defined by multiboot), pointed by gMbi.
3. The last thing that my UEFI bootloader does is calling JumpToKernel():
https://github.com/vvaltchev/tilck/blob ... ain.c#L1044. Which, the 32-bit case, does nothing more than jumping to kernel's entry point:
Code:
void JumpToKernel(void *entry_point)
{
/* Jump to the kernel */
asmVolatile("jmp *%%ecx"
: /* no output */
: "a" (MULTIBOOT_BOOTLOADER_MAGIC),
"b" (gMbi),
"c" (entry_point)
: /* no clobber */);
}
Before doing the actual jump, the multiboot magic is stored in EAX and the gMbi pointer is stored in EBX. In the case of 64-bit machines, because my kernel is only 32-bit, the UEFI bootloader just calls an assembly function to switch from 'long mode' to 'protected mode 32' and then the same jump is performed.
5. In kernel's main, the read_multiboot_info() function is called:
https://github.com/vvaltchev/tilck/blob ... ain.c#L174This function checks, among other things, if there is any framebuffer info. If there is, set_framebuffer_info_from_mbi() is called.
6. set_framebuffer_info_from_mbi() just copies the info in some global variables:
https://github.com/vvaltchev/tilck/blob ... raw.c#L1477. Later, the init_fb_console() function:
https://github.com/vvaltchev/tilck/blob ... ole.c#L533Calls fb_map_in_kernel_space(), which is:
Code:
void fb_map_in_kernel_space(void)
{
fb_vaddr = (ulong) map_framebuffer(get_kernel_pdir(),
fb_paddr,
0,
fb_size,
false);
}
8. Finally, map_framebuffer():
https://github.com/vvaltchev/tilck/blob ... ng.c#L1109Maps the framebuffer in the virtual memory and uses PAT to make the memory area "write-combining", which roughly is needed make writes on that area extremely fast.
That's it.
PeterX wrote:
Just out of curiosity: How is the memory map handled if a kernel works both for UEFI and Legacy BIOS? I guess, by checking which firmware and then different code, right? I don't believe that the old BIOS memory map is identical to the EFI memory map, or is it?
Good question! The memory map is NOT the same and UEFI defines much many memory region types. The multiboot protocol defines just 5 memory region types, which are the same ones returned by the legacy BIOS E820 function. How to handle that? Well, no matter if you boot the legacy way or the UEFI way, the kernel needs the memory map from the bootloader. Actually, that's so important that in UEFI, ExitBootServices() fails if you didn't call GetMemoryMap() immediately before it. So, in Tilck's UEFI bootloader, near the end of efi_main(), I call:
MultibootSaveMemoryMap():
https://github.com/vvaltchev/tilck/blob ... oot.c#L118which calls GetMemoryMap() and then translates the many UEFI memory types back to the classic 5 using the EfiToMultibootMemType() function (in the same file).
This way, in kernel's read_multiboot_info() function (that you already saw from the example above), I get the memory map in the SAME format as in the case of the legacy bootloader. Of course, the legacy bootloader does a ton of stuff to get the same result.
My point here is to have a
boot protocol: a common set of structures shared between the boot loader (no matter how it works) and the kernel, in order to the bootloader to pass information to the kernel. Multiboot is good and widely supported. Multiboot 2 is even more powerful, but QEMU doesn't support it. Linux uses a custom boot protocol, because it needs extra features that multiboot 2 does not cover.
I hope you had some fun following my code.
Vlad