Files
Operator-system/main.c

318 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* main.c UEFI boot loader.
*
* This is the first code that runs. efi_main() is called by UEFI
* firmware via the PE32+ entry point. It:
* 1. Reads kernel.elf from the EFI System Partition
* 2. Parses the ELF64 headers and maps PT_LOAD segments into memory
* 3. Populates a BootInfo struct with UEFI service wrappers
* 4. Jumps to the kernel entry point (kmain)
*/
#include <efi.h>
#include <efilib.h>
#include "boot_info.h"
/* ================================================================
* ELF64 constants and types
* ================================================================ */
#define ELF_MAGIC 0x464c457f /* "\x7fELF" as a little-endian UINT32 */
#define PT_LOAD 1 /* loadable program segment */
/* Minimal ELF64 file header (enough to locate program headers). */
typedef struct {
UINT32 e_magic; /* must be ELF_MAGIC */
UINT8 e_class; /* 2 = 64-bit */
UINT8 e_data; /* 1 = little-endian */
UINT8 e_version;
UINT8 e_osabi;
UINT8 e_abiversion;
UINT8 e_pad[7];
UINT16 e_type; /* ET_EXEC = 2 */
UINT16 e_machine; /* EM_X86_64 = 62 */
UINT32 e_version2;
UINT64 e_entry; /* virtual address of entry point */
UINT64 e_phoff; /* file offset to program header table */
UINT64 e_shoff;
UINT32 e_flags;
UINT16 e_ehsize;
UINT16 e_phentsize; /* size of one program header entry */
UINT16 e_phnum; /* number of program header entries */
UINT16 e_shentsize;
UINT16 e_shnum;
UINT16 e_shstrndx;
} Elf64_Ehdr;
/* ELF64 program header (describes one segment). */
typedef struct {
UINT32 p_type; /* segment type (PT_LOAD, etc.) */
UINT32 p_flags; /* segment flags (R/W/X) */
UINT64 p_offset; /* file offset of segment data */
UINT64 p_vaddr; /* virtual address to map at */
UINT64 p_paddr; /* physical address (usually == vaddr) */
UINT64 p_filesz; /* bytes of segment data in file */
UINT64 p_memsz; /* total bytes in memory (>= filesz) */
UINT64 p_align; /* alignment requirement */
} Elf64_Phdr;
/* ================================================================
* EFI file-system helpers
* ================================================================ */
/*
* Open the root directory of the volume from which this loader was
* loaded (i.e. the EFI System Partition).
*/
static EFI_STATUS open_root_volume(EFI_HANDLE ImageHandle, EFI_FILE_PROTOCOL **Root)
{
EFI_STATUS Status;
EFI_LOADED_IMAGE *LoadedImage = NULL;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem = NULL;
Status = uefi_call_wrapper(BS->HandleProtocol, 3, ImageHandle,
&gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage);
if (EFI_ERROR(Status)) {
return Status;
}
Status = uefi_call_wrapper(BS->HandleProtocol, 3, LoadedImage->DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid, (VOID **)&FileSystem);
if (EFI_ERROR(Status)) {
return Status;
}
return uefi_call_wrapper(FileSystem->OpenVolume, 2, FileSystem, Root);
}
/*
* Read an entire file from the EFI System Partition into a pool buffer.
* Caller must free *Buffer with BS->FreePool() when done.
*/
static EFI_STATUS read_file_to_buffer(EFI_HANDLE ImageHandle, CHAR16 *Path,
VOID **Buffer, UINTN *Size)
{
EFI_STATUS Status;
EFI_FILE_PROTOCOL *Root = NULL;
EFI_FILE_PROTOCOL *File = NULL;
EFI_FILE_INFO *Info = NULL;
UINTN InfoSize = 0;
UINTN FileSize = 0;
Status = open_root_volume(ImageHandle, &Root);
if (EFI_ERROR(Status)) {
return Status;
}
Status = uefi_call_wrapper(Root->Open, 5, Root, &File, Path,
EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(Status)) {
return Status;
}
Status = uefi_call_wrapper(File->GetInfo, 4, File, &gEfiFileInfoGuid,
&InfoSize, NULL);
if (Status != EFI_BUFFER_TOO_SMALL) {
uefi_call_wrapper(File->Close, 1, File);
return Status;
}
Status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData,
InfoSize, (VOID **)&Info);
if (EFI_ERROR(Status)) {
uefi_call_wrapper(File->Close, 1, File);
return Status;
}
Status = uefi_call_wrapper(File->GetInfo, 4, File, &gEfiFileInfoGuid,
&InfoSize, Info);
if (EFI_ERROR(Status)) {
uefi_call_wrapper(BS->FreePool, 1, Info);
uefi_call_wrapper(File->Close, 1, File);
return Status;
}
FileSize = (UINTN)Info->FileSize;
Status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData,
FileSize, Buffer);
if (EFI_ERROR(Status)) {
uefi_call_wrapper(BS->FreePool, 1, Info);
uefi_call_wrapper(File->Close, 1, File);
return Status;
}
Status = uefi_call_wrapper(File->Read, 3, File, &FileSize, *Buffer);
if (EFI_ERROR(Status)) {
uefi_call_wrapper(BS->FreePool, 1, Info);
uefi_call_wrapper(File->Close, 1, File);
return Status;
}
uefi_call_wrapper(BS->FreePool, 1, Info);
uefi_call_wrapper(File->Close, 1, File);
*Size = FileSize;
return EFI_SUCCESS;
}
/* ================================================================
* ELF64 kernel loader
* ================================================================ */
/*
* Parse an ELF64 image in memory and load all PT_LOAD segments to
* their specified virtual (== physical) addresses. On success,
* *EntryOut is set to the kernel entry point.
*/
static EFI_STATUS load_elf_kernel(VOID *Image, UINTN Size, UINT64 *EntryOut)
{
Elf64_Ehdr *Ehdr = (Elf64_Ehdr *)Image;
Elf64_Phdr *Phdr = NULL;
UINT16 Index = 0;
if (Size < sizeof(Elf64_Ehdr)) {
return EFI_LOAD_ERROR;
}
if (Ehdr->e_magic != ELF_MAGIC || Ehdr->e_class != 2 || Ehdr->e_data != 1) {
return EFI_LOAD_ERROR;
}
if ((Ehdr->e_phoff + (UINT64)Ehdr->e_phnum * Ehdr->e_phentsize) > Size) {
return EFI_LOAD_ERROR;
}
Phdr = (Elf64_Phdr *)((UINT8 *)Image + Ehdr->e_phoff);
for (Index = 0; Index < Ehdr->e_phnum; Index++) {
Elf64_Phdr *Segment = (Elf64_Phdr *)((UINT8 *)Phdr + (Index * Ehdr->e_phentsize));
UINT64 SegmentStart = 0;
UINT64 SegmentEnd = 0;
UINT64 SegmentPages = 0;
EFI_PHYSICAL_ADDRESS Address = 0;
if (Segment->p_type != PT_LOAD || Segment->p_memsz == 0) {
continue;
}
if ((Segment->p_offset + Segment->p_filesz) > Size) {
return EFI_LOAD_ERROR;
}
SegmentStart = Segment->p_vaddr & ~0xFFFULL;
SegmentEnd = (Segment->p_vaddr + Segment->p_memsz + 0xFFFULL) & ~0xFFFULL;
SegmentPages = (SegmentEnd - SegmentStart) >> 12;
Address = (EFI_PHYSICAL_ADDRESS)SegmentStart;
if (EFI_ERROR(uefi_call_wrapper(BS->AllocatePages, 4, AllocateAddress,
EfiLoaderData, SegmentPages, &Address))) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem((VOID *)(UINTN)Segment->p_vaddr,
(UINT8 *)Image + Segment->p_offset,
(UINTN)Segment->p_filesz);
if (Segment->p_memsz > Segment->p_filesz) {
SetMem((VOID *)(UINTN)(Segment->p_vaddr + Segment->p_filesz),
(UINTN)(Segment->p_memsz - Segment->p_filesz), 0);
}
}
*EntryOut = Ehdr->e_entry;
return EFI_SUCCESS;
}
/* ================================================================
* UEFI service wrappers (passed to the kernel via BootInfo)
* ================================================================ */
static EFI_STATUS loader_clear_screen(void)
{
return uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
}
static EFI_STATUS loader_set_attribute(UINTN Attribute)
{
return uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, Attribute);
}
static EFI_STATUS loader_read_key(EFI_INPUT_KEY *Key)
{
UINTN Index = 0;
uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &Index);
return uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, Key);
}
static EFI_STATUS loader_try_read_key(EFI_INPUT_KEY *Key)
{
return uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, Key);
}
static void loader_shutdown(void)
{
uefi_call_wrapper(RT->ResetSystem, 4, EfiResetShutdown, EFI_SUCCESS, 0, NULL);
}
static EFI_STATUS loader_alloc_pages(UINTN pages, EFI_PHYSICAL_ADDRESS *addr)
{
return uefi_call_wrapper(BS->AllocatePages, 4,
AllocateAnyPages, EfiLoaderData, pages, addr);
}
static EFI_STATUS loader_free_pages(EFI_PHYSICAL_ADDRESS addr, UINTN pages)
{
return uefi_call_wrapper(BS->FreePages, 2, addr, pages);
}
/* ================================================================
* UEFI application entry point
* ================================================================ */
EFI_STATUS
EFIAPI
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
EFI_STATUS Status;
VOID *KernelImage = NULL;
UINTN KernelSize = 0;
UINT64 KernelEntry = 0;
BootInfo Boot;
KernelEntryFn EntryFn = NULL;
/* Initialise the GNU-EFI library */
InitializeLib(ImageHandle, SystemTable);
Print(L"Loading kernel...\n\r");
Status = read_file_to_buffer(ImageHandle, L"\\kernel.elf", &KernelImage, &KernelSize);
if (EFI_ERROR(Status)) {
Print(L"Failed to read kernel.elf: %r\n\r", Status);
return Status;
}
Status = load_elf_kernel(KernelImage, KernelSize, &KernelEntry);
if (EFI_ERROR(Status)) {
Print(L"Failed to load kernel.elf: %r\n\r", Status);
return Status;
}
/* Populate the BootInfo struct with UEFI service wrappers */
Boot.SystemTable = ST;
Boot.print = Print;
Boot.clear_screen = loader_clear_screen;
Boot.set_attribute = loader_set_attribute;
Boot.read_key = loader_read_key;
Boot.try_read_key = loader_try_read_key;
Boot.shutdown = loader_shutdown;
Boot.alloc_pages = loader_alloc_pages;
Boot.free_pages = loader_free_pages;
/* Jump to the kernel this should not return */
EntryFn = (KernelEntryFn)(UINTN)KernelEntry;
EntryFn(&Boot);
Print(L"Kernel returned. Halting.\n\r");
return EFI_SUCCESS;
}