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:
Nexoniarz 2026-08-09 17:13:09 +02:00
commit 56139bd0e6
7 changed files with 874 additions and 1 deletions

260
boot/uefi/efi.h Normal file
View file

@ -0,0 +1,260 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifndef SCHOS_EFI_H
#define SCHOS_EFI_H
// The parts of UEFI this loader actually uses, written from the
// specification.
//
// Not gnu-efi and not EDK II. Both are large dependencies for something this
// small, and both raise a licensing question the project does not need to
// answer -- see docs/licensing.md. What is here is the handful of structures
// and function pointers needed to read a file, ask for the framebuffer, take
// the memory map and leave: perhaps two hundred lines against tens of
// thousands.
//
// Everything is called through function pointers in tables the firmware hands
// us, using the Microsoft x64 calling convention. clang's
// --target=x86_64-unknown-windows produces that natively, which is why no
// __attribute__((ms_abi)) appears anywhere below.
//
// Fields this loader never touches are left as `void *` padding rather than
// spelled out. The offsets are what matter and they are preserved exactly;
// naming a field we do not use would only invite it to drift.
typedef unsigned char UINT8;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef unsigned long long UINT64;
typedef long long INTN;
typedef unsigned long long UINTN;
typedef UINT16 CHAR16;
typedef UINT8 BOOLEAN;
typedef void *EFI_HANDLE;
typedef UINTN EFI_STATUS;
typedef UINT64 EFI_PHYSICAL_ADDRESS;
typedef UINT64 EFI_VIRTUAL_ADDRESS;
#define EFI_SUCCESS 0
#define EFI_BUFFER_TOO_SMALL ((EFI_STATUS)(0x8000000000000000ULL | 5))
#define EFI_ERROR(s) (((INTN)(s)) < 0)
typedef struct {
UINT32 Data1;
UINT16 Data2;
UINT16 Data3;
UINT8 Data4[8];
} EFI_GUID;
// --- memory ---------------------------------------------------------------
typedef enum {
AllocateAnyPages,
AllocateMaxAddress,
AllocateAddress
} EFI_ALLOCATE_TYPE;
// Only the types this loader has to reason about are named. The rule it
// applies is simply which of them are free for the kernel to use.
#define EfiReservedMemoryType 0
#define EfiLoaderCode 1
#define EfiLoaderData 2
#define EfiBootServicesCode 3
#define EfiBootServicesData 4
#define EfiRuntimeServicesCode 5
#define EfiRuntimeServicesData 6
#define EfiConventionalMemory 7
#define EfiUnusableMemory 8
#define EfiACPIReclaimMemory 9
#define EfiACPIMemoryNVS 10
#define EfiMemoryMappedIO 11
#define EfiMemoryMappedIOPortSpace 12
#define EfiPalCode 13
#define EfiPersistentMemory 14
typedef struct {
UINT32 Type;
UINT32 Pad;
EFI_PHYSICAL_ADDRESS PhysicalStart;
EFI_VIRTUAL_ADDRESS VirtualStart;
UINT64 NumberOfPages;
UINT64 Attribute;
} EFI_MEMORY_DESCRIPTOR;
// --- simple text output ---------------------------------------------------
typedef struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
void *Reset;
EFI_STATUS (*OutputString)(struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
CHAR16 *String);
void *TestString;
void *QueryMode;
void *SetMode;
void *SetAttribute;
EFI_STATUS (*ClearScreen)(struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This);
void *SetCursorPosition;
void *EnableCursor;
void *Mode;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
// --- boot services --------------------------------------------------------
typedef struct {
char Hdr[24];
void *RaiseTPL;
void *RestoreTPL;
EFI_STATUS (*AllocatePages)(EFI_ALLOCATE_TYPE Type, UINTN MemoryType,
UINTN Pages, EFI_PHYSICAL_ADDRESS *Memory);
EFI_STATUS (*FreePages)(EFI_PHYSICAL_ADDRESS Memory, UINTN Pages);
EFI_STATUS (*GetMemoryMap)(UINTN *MemoryMapSize,
EFI_MEMORY_DESCRIPTOR *MemoryMap,
UINTN *MapKey, UINTN *DescriptorSize,
UINT32 *DescriptorVersion);
EFI_STATUS (*AllocatePool)(UINTN PoolType, UINTN Size, void **Buffer);
EFI_STATUS (*FreePool)(void *Buffer);
void *CreateEvent;
void *SetTimer;
void *WaitForEvent;
void *SignalEvent;
void *CloseEvent;
void *CheckEvent;
void *InstallProtocolInterface;
void *ReinstallProtocolInterface;
void *UninstallProtocolInterface;
EFI_STATUS (*HandleProtocol)(EFI_HANDLE Handle, EFI_GUID *Protocol,
void **Interface);
void *Reserved;
void *RegisterProtocolNotify;
void *LocateHandle;
void *LocateDevicePath;
void *InstallConfigurationTable;
void *LoadImage;
void *StartImage;
void *Exit;
void *UnloadImage;
EFI_STATUS (*ExitBootServices)(EFI_HANDLE ImageHandle, UINTN MapKey);
void *GetNextMonotonicCount;
EFI_STATUS (*Stall)(UINTN Microseconds);
void *SetWatchdogTimer;
void *ConnectController;
void *DisconnectController;
void *OpenProtocol;
void *CloseProtocol;
void *OpenProtocolInformation;
void *ProtocolsPerHandle;
void *LocateHandleBuffer;
EFI_STATUS (*LocateProtocol)(EFI_GUID *Protocol, void *Registration,
void **Interface);
} EFI_BOOT_SERVICES;
typedef struct {
char Hdr[24];
CHAR16 *FirmwareVendor;
UINT32 FirmwareRevision;
EFI_HANDLE ConsoleInHandle;
void *ConIn;
EFI_HANDLE ConsoleOutHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
EFI_HANDLE StandardErrorHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
void *RuntimeServices;
EFI_BOOT_SERVICES *BootServices;
UINTN NumberOfTableEntries;
void *ConfigurationTable;
} EFI_SYSTEM_TABLE;
// --- loaded image ---------------------------------------------------------
#define EFI_LOADED_IMAGE_PROTOCOL_GUID \
{ 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B } }
typedef struct {
UINT32 Revision;
EFI_HANDLE ParentHandle;
void *SystemTable;
EFI_HANDLE DeviceHandle; // the volume this image was loaded from
void *FilePath;
void *Reserved;
UINT32 LoadOptionsSize;
void *LoadOptions;
void *ImageBase;
UINT64 ImageSize;
UINTN ImageCodeType;
UINTN ImageDataType;
void *Unload;
} EFI_LOADED_IMAGE_PROTOCOL;
// --- file system ----------------------------------------------------------
#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \
{ 0x0964E5B22, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B } }
#define EFI_FILE_MODE_READ 0x0000000000000001ULL
typedef struct EFI_FILE_PROTOCOL {
UINT64 Revision;
EFI_STATUS (*Open)(struct EFI_FILE_PROTOCOL *This,
struct EFI_FILE_PROTOCOL **NewHandle,
CHAR16 *FileName, UINT64 OpenMode, UINT64 Attributes);
EFI_STATUS (*Close)(struct EFI_FILE_PROTOCOL *This);
void *Delete;
EFI_STATUS (*Read)(struct EFI_FILE_PROTOCOL *This, UINTN *BufferSize,
void *Buffer);
void *Write;
EFI_STATUS (*GetPosition)(struct EFI_FILE_PROTOCOL *This, UINT64 *Position);
EFI_STATUS (*SetPosition)(struct EFI_FILE_PROTOCOL *This, UINT64 Position);
void *GetInfo;
void *SetInfo;
void *Flush;
} EFI_FILE_PROTOCOL;
typedef struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
UINT64 Revision;
EFI_STATUS (*OpenVolume)(struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
EFI_FILE_PROTOCOL **Root);
} EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
// --- graphics output ------------------------------------------------------
#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
{ 0x9042A9DE, 0x23DC, 0x4A38, { 0x96, 0xFB, 0x7A, 0xDE, 0xD0, 0x80, 0x51, 0x6A } }
#define PixelRedGreenBlueReserved8BitPerColor 0
#define PixelBlueGreenRedReserved8BitPerColor 1
#define PixelBitMask 2
#define PixelBltOnly 3
typedef struct {
UINT32 Version;
UINT32 HorizontalResolution;
UINT32 VerticalResolution;
UINT32 PixelFormat;
UINT32 PixelInformation[4];
UINT32 PixelsPerScanLine;
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
typedef struct {
UINT32 Mode;
UINT32 MaxMode;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
EFI_PHYSICAL_ADDRESS FrameBufferBase;
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL {
void *QueryMode;
void *SetMode;
void *Blt;
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
#endif /* SCHOS_EFI_H */