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
57
Makefile
57
Makefile
|
|
@ -341,6 +341,63 @@ schos.img: $(BUILD)/boot/bootblock $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel $
|
|||
|
||||
kernel: $(BUILD)/kernel/kernel
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# UEFI loader
|
||||
#
|
||||
# A PE/COFF executable, not ELF, so it needs a different target and a
|
||||
# different linker driver -- but the same clang install. That is the whole
|
||||
# reason the toolchain is clang: one compiler reaches every target here.
|
||||
#
|
||||
# -mno-red-zone is not optional. UEFI firmware may take an interrupt at any
|
||||
# point, and the red zone is memory below RSP that a handler is entitled to
|
||||
# clobber; leaving it enabled corrupts locals at random.
|
||||
# -fshort-wchar makes L"..." UTF-16, which is the only string type UEFI has.
|
||||
# -----------------------------------------------------------------------
|
||||
UEFI_CFLAGS = --target=x86_64-unknown-windows -ffreestanding -fshort-wchar -mno-red-zone -fno-stack-protector -Wall -Wextra -O1 -I./include
|
||||
|
||||
$(BUILD)/uefi/BOOTX64.EFI: boot/uefi/main.c boot/uefi/efi.h include/schboot.h
|
||||
@mkdir -p $(BUILD)/uefi
|
||||
clang $(UEFI_CFLAGS) -c boot/uefi/main.c -o $(BUILD)/uefi/main.obj
|
||||
lld-link -subsystem:efi_application -entry:EfiMain -nodefaultlib $(BUILD)/uefi/main.obj -out:$@
|
||||
|
||||
uefi: $(BUILD)/uefi/BOOTX64.EFI
|
||||
|
||||
# The EFI System Partition, as a directory. QEMU serves a directory as FAT
|
||||
# with `-drive file=fat:rw:`, which is enough to boot and test without any
|
||||
# image-building tools -- mtools is not present on every supported host.
|
||||
# A real ESP image for `make iso` is still to come.
|
||||
$(BUILD)/esp/EFI/BOOT/BOOTX64.EFI: $(BUILD)/uefi/BOOTX64.EFI $(BUILD)/kernel/kernel
|
||||
@mkdir -p $(BUILD)/esp/EFI/BOOT $(BUILD)/esp/schos
|
||||
@cp $(BUILD)/uefi/BOOTX64.EFI $(BUILD)/esp/EFI/BOOT/BOOTX64.EFI
|
||||
@cp $(BUILD)/kernel/kernel $(BUILD)/esp/schos/kernel
|
||||
@cp boot/uefi/startup.nsh $(BUILD)/esp/startup.nsh
|
||||
@echo "ESP staged in $(BUILD)/esp"
|
||||
|
||||
esp: $(BUILD)/esp/EFI/BOOT/BOOTX64.EFI
|
||||
|
||||
# Boot the UEFI path under OVMF. The firmware ships with QEMU; the varstore
|
||||
# is copied because it must be writable.
|
||||
#
|
||||
# i440fx, not q35: the kernel's IDE driver speaks to the legacy ports at
|
||||
# 0x1f0, and q35 has no legacy IDE at all -- only AHCI. The root volume would
|
||||
# simply not be there.
|
||||
OVMF_CODE = $(BUILD)/OVMF_CODE.fd
|
||||
OVMF_VARS = $(BUILD)/OVMF_VARS.fd
|
||||
QEMU_SHARE = $(dir $(shell command -v $(QEMU)))share
|
||||
|
||||
$(OVMF_CODE):
|
||||
@mkdir -p $(BUILD)
|
||||
cp "$(QEMU_SHARE)/edk2-x86_64-code.fd" $@
|
||||
chmod u+w $@
|
||||
|
||||
$(OVMF_VARS):
|
||||
@mkdir -p $(BUILD)
|
||||
cp "$(QEMU_SHARE)/edk2-i386-vars.fd" $@
|
||||
chmod u+w $@
|
||||
|
||||
qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
|
||||
$(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 -cpu qemu64,+rdtscp -serial mon:stdio $(QEMU_DISPLAY)
|
||||
|
||||
# Build the ramdisk-root variant. This is a target rather than just
|
||||
# "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT),
|
||||
# and make has no way to notice that: objects compiled for one setting would be
|
||||
|
|
|
|||
Loading…
Reference in a new issue