boot/uefi: a working UEFI loader, with one known limitation
Loads the kernel from the ESP, places its ELF segments, records the framebuffer, translates the UEFI memory map into E820 entries, exits boot services, builds the page tables the kernel expects and jumps. Verified under OVMF: the kernel starts, brings up PCI, VGA, e1000 and the RTC, and reaches "cpu0: starting". UEFI structures are written from the specification rather than importing gnu-efi or EDK II -- a large dependency for two hundred lines of use, and a licensing question the project does not need to answer. clang emits PE/COFF directly, so this needs no new toolchain. The kernel gained a second entry point. The multiboot entry is 32-bit code that climbs into long mode itself, and firmware that already put us there would have to drop back down only to climb out again -- a mode switch whose failure mode is a triple fault with no console. So entry.S publishes its 64-bit entry the same way it publishes the multiboot header: a magic to scan for, SCH64BOO, with include/schboot.h owning the constants so the two cannot disagree. That header goes *after* the multiboot header, never before: header_addr says the multiboot header lives at mboot_load_addr, and the BIOS loader computes the kernel's file offset from it. The kernel loads its own GDT at that entry rather than the loader doing it. Arriving in long mode means arriving on the firmware's descriptors, and the first thing depending on the kernel's own selectors faulted -- "unexpected trap 13 ... cs=38", a long way from the cause. KNOWN LIMITATION, diagnosed and not yet fixed: e820_init takes the usable run *containing the kernel*, which under OVMF ends at 8 MB -- the firmware places an ACPI NVS region at 0x800000, so the run is genuinely fragmented. The map is truthful and untruncated (21 entries); the assumption of one contiguous run is BIOS-shaped. The fix is for the kernel to free each usable region rather than one range, which touches kalloc and deserves its own change rather than the tail of this one. BIOS boot is unaffected: 27 checks, no failures.
This commit is contained in:
parent
9b6106eab2
commit
56139bd0e6
7 changed files with 874 additions and 1 deletions
|
|
@ -27,7 +27,31 @@
|
|||
#define mboot_magic 0x1badb002
|
||||
#define mboot_flags 0x00010000
|
||||
#include "mmu.h"
|
||||
#include "schboot.h"
|
||||
|
||||
/* A second entry point, for firmware that has already put us in long mode.
|
||||
*
|
||||
* The multiboot header below is entered in 32-bit protected mode, and
|
||||
* mboot_entry builds the page tables and makes the long-mode transition
|
||||
* itself. UEFI firmware hands a loader long mode with paging already on, so
|
||||
* that path would mean dropping back to protected mode only to climb out
|
||||
* again -- a mode switch whose failure mode is a triple fault with no console.
|
||||
*
|
||||
* Instead the 64-bit entry is published where a loader can find it, by the
|
||||
* same trick the BIOS loader already uses to find the multiboot header: scan
|
||||
* the first 16 KB for a magic. boot/uefi/ looks for SCH64_MAGIC and jumps to
|
||||
* the physical address recorded here.
|
||||
*
|
||||
* The contract at that address, which boot/uefi/main.c must satisfy:
|
||||
* - long mode, interrupts off
|
||||
* - CR3 pointing at page tables that identity-map the low 4 GB *and* map
|
||||
* the same 4 GB at 0xffff800000000000 -- entry64low's first act is a
|
||||
* movabs to a high address, so a loader that only identity-maps triple
|
||||
* faults on the very first instruction
|
||||
* - RBX = 0, marking the boot processor rather than an AP
|
||||
* Those are exactly the conditions mboot_entry has established by the time it
|
||||
* reaches entry64low, which is why this points there and not somewhere new.
|
||||
*/
|
||||
.code64
|
||||
.global mboot_header
|
||||
.global mboot_entry
|
||||
|
|
@ -42,6 +66,18 @@ mboot_header:
|
|||
.long mboot_bss_end
|
||||
.long mboot_entry_addr
|
||||
|
||||
/* After the multiboot header, never before it: the header's own header_addr
|
||||
field says it lives at mboot_load_addr, and the BIOS loader computes the
|
||||
kernel's file offset from (header_addr - load_addr). Sixteen bytes inserted
|
||||
ahead of it makes that arithmetic wrong and loads the kernel shifted, which
|
||||
fails as a triple fault rather than as anything that names the cause. */
|
||||
.align 8
|
||||
.global sch64_header
|
||||
sch64_header:
|
||||
.long SCH64_MAGIC0
|
||||
.long SCH64_MAGIC1
|
||||
.quad (entry64uefi - mboot_header + mboot_load_addr)
|
||||
|
||||
.code32
|
||||
mboot_entry:
|
||||
mov %eax, %ebp
|
||||
|
|
@ -110,6 +146,29 @@ gdt64_end:
|
|||
|
||||
.align 16
|
||||
.code64
|
||||
|
||||
/* Entered by boot/uefi/ with the firmware's GDT still loaded.
|
||||
*
|
||||
* mboot_entry reaches entry64low by way of entry32mp, which does the lgdt on
|
||||
* the way past. A loader that arrives already in long mode skips all of that,
|
||||
* so it arrives running on the firmware's descriptors -- OVMF's CS is 0x38 --
|
||||
* and the first thing the kernel does that depends on its own selectors takes
|
||||
* a #GP against a GDT the firmware no longer owns. It surfaced as
|
||||
* "unexpected trap 13 ... cs=38", a long way from the cause.
|
||||
*
|
||||
* So load the kernel's GDT here and reload CS through it before going on.
|
||||
* Doing it in the kernel rather than in the loader is deliberate: the GDT is
|
||||
* the kernel's, and a loader that had to know its layout would have to be
|
||||
* changed every time it moved.
|
||||
*/
|
||||
.global entry64uefi
|
||||
entry64uefi:
|
||||
lgdt (gdtr64 - mboot_header + mboot_load_addr)
|
||||
movq $(entry64low - mboot_header + mboot_load_addr), %rax
|
||||
pushq $8 /* the 64-bit code selector in gdt64 */
|
||||
pushq %rax
|
||||
lretq /* far return: reloads CS */
|
||||
|
||||
entry64low:
|
||||
movabs $entry64high, %rax
|
||||
jmp *%rax
|
||||
|
|
|
|||
Loading…
Reference in a new issue