809 lines
29 KiB
C
809 lines
29 KiB
C
// boot/main.c - UEFI bootloader
|
|
//
|
|
// Responsibilities (see design §5.2):
|
|
// 1. Parse boot.ini
|
|
// 2. Read drivers/drivers.ini
|
|
// 3. Load PE images (kernel.exe, hal.dll, syscall.dll, win32.dll, *.sys)
|
|
// 4. Validate PE checksums
|
|
// 5. Map images into the upper half of a new PML4
|
|
// 6. Map all RAM as direct physical window at 0xFFFF880000000000
|
|
// 7. Convert EFI memory map -> MEMORY_MAP
|
|
// 8. ExitBootServices
|
|
// 9. Set up minimal GDT/IDT, enable NXE, set CR3
|
|
// 10. Fill BOOT_INFO and jump to KernelMain
|
|
//
|
|
|
|
#include <efi.h>
|
|
#include <efilib.h>
|
|
#include <stdbool.h>
|
|
#include <intrin.h>
|
|
#include <boot_info.h>
|
|
#include <memory_map.h>
|
|
#include <ini_parser.h>
|
|
|
|
int _fltused = 0;
|
|
|
|
#define KERNEL_VIRT_BASE 0xFFFF800000000000ULL
|
|
#define PHYS_MAP_BASE 0xFFFF880000000000ULL
|
|
#define PAGE_SIZE 4096ULL
|
|
|
|
static int AsciiStrToUnicodeStrS(const char* Source, uint16_t* Dest, size_t DestMax) {
|
|
if (!Source || !Dest || !DestMax) return -1;
|
|
|
|
for (size_t i = 0; i < DestMax; i++) {
|
|
if ((Dest[i] = (unsigned char)Source[i]) == '\0') return 0;
|
|
}
|
|
|
|
Dest[DestMax - 1] = '\0';
|
|
return -1; // Buffer too small
|
|
}
|
|
|
|
int AsciiStrCpyS(char* Dest, size_t DestMax, const char* Source) {
|
|
if (!Dest || !Source || !DestMax) return -1;
|
|
|
|
for (size_t i = 0; i < DestMax; i++) {
|
|
if ((Dest[i] = Source[i]) == '\0') return 0;
|
|
}
|
|
|
|
Dest[DestMax - 1] = '\0'; // Ensure termination on truncation
|
|
return -1;
|
|
}
|
|
|
|
int UnicodeStrToAsciiStrS(const uint16_t* Source, char* Dest, size_t DestMax) {
|
|
if (!Source || !Dest || !DestMax) return -1;
|
|
|
|
for (size_t i = 0; i < DestMax; i++) {
|
|
// Narrowing: only taking the lower 8 bits
|
|
Dest[i] = (char)(Source[i] & 0xFF);
|
|
|
|
if (Source[i] == 0) return 0;
|
|
}
|
|
|
|
Dest[DestMax - 1] = '\0';
|
|
return -1;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PE32+ checksum validation
|
|
// ---------------------------------------------------------------------------
|
|
static UINT32 ComputePeChecksum(const UINT8* buf, UINTN size) {
|
|
UINT64 checksum = 0;
|
|
const UINT16* words = (const UINT16 *)buf;
|
|
UINTN wordCount = size / 2;
|
|
|
|
// Locate and zero the CheckSum field in the optional header.
|
|
UINT32 peOff = *(UINT32 *)(buf + 0x3C);
|
|
UINTN cksumOff = peOff + 4 + 20 + 64; // COFF hdr + optional hdr offset to CheckSum
|
|
UINT16 savedLo = words[cksumOff / 2];
|
|
UINT16 savedHi = words[cksumOff / 2 + 1];
|
|
((UINT16 *)buf)[cksumOff / 2] = 0;
|
|
((UINT16 *)buf)[cksumOff / 2 + 1] = 0;
|
|
|
|
for (UINTN i = 0; i < wordCount; i++) {
|
|
checksum += words[i];
|
|
if (checksum > 0xFFFF) checksum = (checksum & 0xFFFF) + (checksum >> 16);
|
|
}
|
|
if (size & 1) { checksum += buf[size - 1]; }
|
|
checksum = (checksum & 0xFFFF) + (checksum >> 16);
|
|
checksum += (UINT64)size;
|
|
|
|
// Restore.
|
|
((UINT16 *)buf)[cksumOff / 2] = savedLo;
|
|
((UINT16 *)buf)[cksumOff / 2 + 1] = savedHi;
|
|
return (UINT32)checksum;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Page table helpers
|
|
// ---------------------------------------------------------------------------
|
|
static UINT64* AllocPage(EFI_PHYSICAL_ADDRESS* physOut) {
|
|
VOID* mem = NULL;
|
|
gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1, (EFI_PHYSICAL_ADDRESS *)&mem);
|
|
gBS->SetMem(mem, PAGE_SIZE, 0);
|
|
if (physOut) *physOut = (EFI_PHYSICAL_ADDRESS)mem; // identity-mapped in UEFI
|
|
return (UINT64 *)mem;
|
|
}
|
|
|
|
#define PML4_IDX(va) (((va) >> 39) & 0x1FF)
|
|
#define PDPT_IDX(va) (((va) >> 30) & 0x1FF)
|
|
#define PDT_IDX(va) (((va) >> 21) & 0x1FF)
|
|
#define PT_IDX(va) (((va) >> 12) & 0x1FF)
|
|
|
|
static UINT64* gPml4;
|
|
|
|
static void MapPage(UINT64 virt, UINT64 phys, UINT64 flags) {
|
|
UINT64* pml4e = &gPml4[PML4_IDX(virt)];
|
|
if (!(*pml4e & 1)) { EFI_PHYSICAL_ADDRESS p; AllocPage(&p); *pml4e = p | 3; }
|
|
UINT64* pdpt = (UINT64 *)(*pml4e & ~0xFFFULL);
|
|
UINT64* pdpte = &pdpt[PDPT_IDX(virt)];
|
|
if (!(*pdpte & 1)) { EFI_PHYSICAL_ADDRESS p; AllocPage(&p); *pdpte = p | 3; }
|
|
UINT64* pdt = (UINT64 *)(*pdpte & ~0xFFFULL);
|
|
UINT64* pdte = &pdt[PDT_IDX(virt)];
|
|
if (!(*pdte & 1)) { EFI_PHYSICAL_ADDRESS p; AllocPage(&p); *pdte = p | 3; }
|
|
UINT64* pt = (UINT64 *)(*pdte & ~0xFFFULL);
|
|
pt[PT_IDX(virt)] = phys | flags;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// File loading
|
|
// ---------------------------------------------------------------------------
|
|
static EFI_FILE_PROTOCOL* gRoot;
|
|
|
|
static UINT8* LoadFile(CHAR16* path, UINTN* sizeOut) {
|
|
EFI_STATUS Status;
|
|
EFI_FILE_PROTOCOL* fh;
|
|
|
|
// Open the file
|
|
Status = gRoot->Open(gRoot, &fh, path, EFI_FILE_MODE_READ, 0);
|
|
if (EFI_ERROR(Status)) return NULL;
|
|
|
|
// Get the correct buffer size for EFI_FILE_INFO
|
|
EFI_FILE_INFO* info;
|
|
UINTN infoSz = 0;
|
|
|
|
// This first call will fail but will return the required size in infoSz
|
|
fh->GetInfo(fh, &gEfiFileInfoGuid, &infoSz, NULL);
|
|
|
|
// Allocate the buffer for the info struct
|
|
info = AllocatePool(infoSz);
|
|
if (info == NULL) {
|
|
fh->Close(fh);
|
|
return NULL;
|
|
}
|
|
|
|
// Now actually get the info
|
|
Status = fh->GetInfo(fh, &gEfiFileInfoGuid, &infoSz, info);
|
|
if (EFI_ERROR(Status)) {
|
|
FreePool(info);
|
|
fh->Close(fh);
|
|
return NULL;
|
|
}
|
|
|
|
// Allocate buffer for file content
|
|
UINTN sz = (UINTN)info->FileSize;
|
|
FreePool(info); // Don't need this anymore
|
|
|
|
UINT8* buf = AllocatePool(sz);
|
|
if (buf == NULL) {
|
|
fh->Close(fh);
|
|
return NULL;
|
|
}
|
|
|
|
// Read the file into the buffer
|
|
Status = fh->Read(fh, &sz, buf);
|
|
fh->Close(fh);
|
|
|
|
if (EFI_ERROR(Status)) {
|
|
FreePool(buf);
|
|
return NULL;
|
|
}
|
|
|
|
if (sizeOut) *sizeOut = sz;
|
|
return buf;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// INI parsing state
|
|
// ---------------------------------------------------------------------------
|
|
typedef struct {
|
|
BOOT_INFO* bi;
|
|
CHAR16 drivers[32][64];
|
|
int driverCount;
|
|
int debug;
|
|
char commandLine[64];
|
|
} PARSE_STATE;
|
|
|
|
static void BootIniCb(const char* section, const char* key,
|
|
const char* val, void* ud) {
|
|
PARSE_STATE* s = (PARSE_STATE *)ud;
|
|
// [kernel] debug = 1
|
|
if (!AsciiStrCmp(section, "kernel") && !AsciiStrCmp(key, "debug"))
|
|
s->debug = (int)(val[0] - '0');
|
|
// [init] commandline = hello.exe
|
|
else if (!AsciiStrCmp(section, "init") && !AsciiStrCmp(key, "commandline"))
|
|
AsciiStrCpyS(s->commandLine, 64, val);
|
|
}
|
|
|
|
static void DriversIniCb(const char* section, const char* key,
|
|
const char* val, void* ud) {
|
|
(void)val;
|
|
PARSE_STATE* s = (PARSE_STATE *)ud;
|
|
if (!AsciiStrCmp(section, "boot") && s->driverCount < 32) {
|
|
AsciiStrToUnicodeStrS(key, s->drivers[s->driverCount], 64);
|
|
s->driverCount++;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// EFI Memory Map -> MEMORY_MAP conversion
|
|
// ---------------------------------------------------------------------------
|
|
static void ConvertMemoryMap(MEMORY_MAP* out) {
|
|
UINTN mapSize = 0, descSz = 0, mapKey; UINT32 descVer;
|
|
gBS->GetMemoryMap(&mapSize, NULL, &mapKey, &descSz, &descVer);
|
|
mapSize += 8 * descSz;
|
|
EFI_MEMORY_DESCRIPTOR* efiMap = AllocatePool(mapSize);
|
|
gBS->GetMemoryMap(&mapSize, efiMap, &mapKey, &descSz, &descVer);
|
|
|
|
out->Magic = MEMORY_MAP_MAGIC;
|
|
out->RegionCount = 0;
|
|
|
|
EFI_MEMORY_DESCRIPTOR* desc = efiMap;
|
|
for (UINTN i = 0; i < mapSize / descSz && out->RegionCount < MEMORY_MAX_REGIONS; i++) {
|
|
MEMORY_TYPE type;
|
|
switch (desc->Type) {
|
|
case EfiConventionalMemory: type = MemoryUsable; break;
|
|
case EfiMemoryMappedIO:
|
|
case EfiMemoryMappedIOPortSpace: type = MemoryMappedIO; break;
|
|
case EfiLoaderCode:
|
|
case EfiLoaderData: type = MemoryBootloader; break;
|
|
case EfiRuntimeServicesCode:
|
|
case EfiRuntimeServicesData:
|
|
default: type = MemoryReserved; break;
|
|
}
|
|
MEMORY_REGION* r = &out->Regions[out->RegionCount++];
|
|
r->Base = desc->PhysicalStart;
|
|
r->Length = desc->NumberOfPages * PAGE_SIZE;
|
|
r->Type = type;
|
|
r->_pad = 0;
|
|
desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)desc + descSz);
|
|
}
|
|
FreePool(efiMap);
|
|
}
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct {
|
|
UINT32 Characteristics;
|
|
UINT32 TimeDateStamp;
|
|
UINT16 MajorVersion;
|
|
UINT16 MinorVersion;
|
|
UINT32 Name;
|
|
UINT32 Base;
|
|
UINT32 NumberOfFunctions;
|
|
UINT32 NumberOfNames;
|
|
UINT32 AddressOfFunctions; // RVA to array of RVAs
|
|
UINT32 AddressOfNames; // RVA to array of RVAs
|
|
UINT32 AddressOfNameOrdinals; // RVA to array of UINT16
|
|
} IMAGE_EXPORT_DIRECTORY;
|
|
|
|
typedef struct {
|
|
union {
|
|
UINT32 Characteristics;
|
|
UINT32 OriginalFirstThunk; // RVA to Import Lookup Table
|
|
} DUMMYUNIONNAME;
|
|
UINT32 TimeDateStamp;
|
|
UINT32 ForwarderChain;
|
|
UINT32 Name; // RVA to DLL Name
|
|
UINT32 FirstThunk; // RVA to IAT (Import Address Table)
|
|
} IMAGE_IMPORT_DESCRIPTOR;
|
|
|
|
typedef struct {
|
|
union {
|
|
UINT64 ForwarderString;
|
|
UINT64 Function;
|
|
UINT64 Ordinal;
|
|
UINT64 AddressOfData; // RVA to IMAGE_IMPORT_BY_NAME
|
|
} u1;
|
|
} IMAGE_THUNK_DATA64;
|
|
|
|
typedef struct {
|
|
UINT16 Hint;
|
|
CHAR8 Name[1];
|
|
} IMAGE_IMPORT_BY_NAME;
|
|
#pragma pack(pop)
|
|
|
|
#define IMAGE_ORDINAL_FLAG64 0x8000000000000000ULL
|
|
|
|
// --- Essential PE Structures (if not in your headers) ---
|
|
#pragma pack(push, 1)
|
|
typedef struct {
|
|
UINT32 VirtualAddress;
|
|
UINT32 Size;
|
|
} IMAGE_DATA_DIRECTORY;
|
|
|
|
typedef struct {
|
|
UINT32 VirtualAddress;
|
|
UINT32 SizeOfBlock;
|
|
} IMAGE_BASE_RELOCATION;
|
|
|
|
typedef struct {
|
|
UINT8 Name[8];
|
|
UINT32 VirtualSize;
|
|
UINT32 VirtualAddress;
|
|
UINT32 SizeOfRawData;
|
|
UINT32 PointerToRawData;
|
|
UINT32 PointerToRelocations;
|
|
UINT32 PointerToLinenumbers;
|
|
UINT16 NumberOfRelocations;
|
|
UINT16 NumberOfLinenumbers;
|
|
UINT32 Characteristics;
|
|
} IMAGE_SECTION_HEADER;
|
|
#pragma pack(pop)
|
|
|
|
// --- The PE Loader ---
|
|
EFI_STATUS LoadPeImage(UINT8* rawFile, UINT64 virtDest, EFI_PHYSICAL_ADDRESS* outPhys) {
|
|
UINT32 peOff = *(UINT32*)(rawFile + 0x3C);
|
|
UINT8* ntHeaders = rawFile + peOff;
|
|
|
|
// Validate Signature
|
|
if (*(UINT32*)ntHeaders != 0x00004550) { Print(L"no\n"); return EFI_INVALID_PARAMETER; } // "PE\0\0"
|
|
|
|
UINT16 sizeOfOptHeader = *(UINT16*)(ntHeaders + 20);
|
|
UINT16 numSections = *(UINT16*)(ntHeaders + 6);
|
|
|
|
// Offsets for PE32+ (64-bit)
|
|
UINT64 imageBase = *(UINT64*)(ntHeaders + 24 + 24);
|
|
UINT32 sizeOfImage = *(UINT32*)(ntHeaders + 24 + 56);
|
|
UINT32 sizeOfHeaders = *(UINT32*)(ntHeaders + 24 + 60);
|
|
|
|
IMAGE_DATA_DIRECTORY* dataDirs = (IMAGE_DATA_DIRECTORY*)(ntHeaders + 24 + 112);
|
|
IMAGE_DATA_DIRECTORY* relocDir = &dataDirs[5];
|
|
//IMAGE_DATA_DIRECTORY* importDir = &dataDirs[1];
|
|
|
|
// Allocate Physical Memory for the mapped image
|
|
UINTN pages = (sizeOfImage + PAGE_SIZE - 1) / PAGE_SIZE;
|
|
EFI_PHYSICAL_ADDRESS physBase;
|
|
gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, &physBase);
|
|
gBS->SetMem((VOID*)physBase, pages * PAGE_SIZE, 0);
|
|
|
|
// Copy Headers
|
|
gBS->CopyMem((VOID*)physBase, rawFile, sizeOfHeaders);
|
|
|
|
// Map Sections to their RVAs
|
|
IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*)(ntHeaders + 24 + sizeOfOptHeader);
|
|
for (int i = 0; i < numSections; i++) {
|
|
if (sections[i].SizeOfRawData > 0) {
|
|
UINT8* dest = (UINT8*)physBase + sections[i].VirtualAddress;
|
|
UINT8* src = rawFile + sections[i].PointerToRawData;
|
|
gBS->CopyMem(dest, src, sections[i].SizeOfRawData);
|
|
}
|
|
}
|
|
|
|
// Apply Base Relocations (.reloc)
|
|
// We calculate the delta between where it wants to be (ImageBase)
|
|
// and where it will actually be (virtDest).
|
|
UINT64 delta = virtDest - imageBase;
|
|
|
|
if (delta != 0 && relocDir->Size > 0) {
|
|
IMAGE_BASE_RELOCATION* reloc = (IMAGE_BASE_RELOCATION*)((UINT8*)physBase + relocDir->VirtualAddress);
|
|
UINTN endOfRelocs = (UINTN)reloc + relocDir->Size;
|
|
|
|
while ((UINTN)reloc < endOfRelocs && reloc->SizeOfBlock > 0) {
|
|
UINTN count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(UINT16);
|
|
UINT16* entries = (UINT16*)(reloc + 1);
|
|
|
|
for (UINTN i = 0; i < count; i++) {
|
|
UINT16 type = entries[i] >> 12;
|
|
UINT16 offset = entries[i] & 0x0FFF;
|
|
|
|
if (type == 10) { // IMAGE_REL_BASED_DIR64
|
|
UINT64* patchAddr = (UINT64*)((UINT8*)physBase + reloc->VirtualAddress + offset);
|
|
*patchAddr += delta;
|
|
}
|
|
}
|
|
reloc = (IMAGE_BASE_RELOCATION*)((UINT8*)reloc + reloc->SizeOfBlock);
|
|
}
|
|
}
|
|
|
|
*outPhys = physBase;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static UINT32 GetExportRva(UINT8* providerPhys, const char* funcName) {
|
|
UINT32 peOff = *(UINT32*)(providerPhys + 0x3C);
|
|
UINT8* ntHeaders = providerPhys + peOff;
|
|
|
|
// Export Dir is index 0 in the DataDirectory array
|
|
UINT32 exportDirRva = *(UINT32*)(ntHeaders + 24 + 112);
|
|
if (!exportDirRva) return 0; // No exports!
|
|
|
|
IMAGE_EXPORT_DIRECTORY* exports = (IMAGE_EXPORT_DIRECTORY*)(providerPhys + exportDirRva);
|
|
|
|
UINT32* addrOfFunctions = (UINT32*)(providerPhys + exports->AddressOfFunctions);
|
|
UINT32* addrOfNames = (UINT32*)(providerPhys + exports->AddressOfNames);
|
|
UINT16* addrOfOrdinals = (UINT16*)(providerPhys + exports->AddressOfNameOrdinals);
|
|
|
|
// Binary search is faster, but linear is fine for a bootloader
|
|
for (UINT32 i = 0; i < exports->NumberOfNames; i++) {
|
|
char* name = (char*)(providerPhys + addrOfNames[i]);
|
|
if (AsciiStrCmp(name, funcName) == 0) {
|
|
UINT16 ordinal = addrOfOrdinals[i];
|
|
return addrOfFunctions[ordinal];
|
|
}
|
|
}
|
|
return 0; // Function not found
|
|
}
|
|
|
|
EFI_STATUS ResolveImports(UINT8* consumerPhys, UINT8* providerPhys, UINT64 providerVirt) {
|
|
UINT32 peOff = *(UINT32*)(consumerPhys + 0x3C);
|
|
UINT8* ntHeaders = consumerPhys + peOff;
|
|
|
|
// Import Dir is index 1 in the DataDirectory array
|
|
UINT32 importDirRva = *(UINT32*)(ntHeaders + 24 + 112 + 8);
|
|
if (!importDirRva) return EFI_SUCCESS; // No imports to resolve
|
|
|
|
IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)(consumerPhys + importDirRva);
|
|
|
|
// Iterate through all imported DLLs
|
|
while (importDesc->Name != 0) {
|
|
char* dllName = (char*)(consumerPhys + importDesc->Name);
|
|
|
|
// In a full OS, you'd loop through all loaded modules here.
|
|
// For now, we assume we are resolving hal.dll.
|
|
// Convert to lowercase or do a case-insensitive check if needed!
|
|
if (AsciiStrCmp(dllName, "hal.dll") == 0) {
|
|
|
|
IMAGE_THUNK_DATA64* origFirstThunk = (IMAGE_THUNK_DATA64*)(consumerPhys + importDesc->DUMMYUNIONNAME.OriginalFirstThunk);
|
|
IMAGE_THUNK_DATA64* firstThunk = (IMAGE_THUNK_DATA64*)(consumerPhys + importDesc->FirstThunk);
|
|
|
|
if (!importDesc->DUMMYUNIONNAME.OriginalFirstThunk) {
|
|
origFirstThunk = firstThunk; // Some compilers omit OriginalFirstThunk
|
|
}
|
|
|
|
int i = 0;
|
|
while (origFirstThunk[i].u1.Function != 0) {
|
|
if (origFirstThunk[i].u1.Ordinal & IMAGE_ORDINAL_FLAG64) {
|
|
// Import by Ordinal (Usually you don't do this in modern kernels, but good to handle)
|
|
Print(L"Error: Ordinal imports not supported in basic loader.\n");
|
|
return EFI_UNSUPPORTED;
|
|
} else {
|
|
// Import by Name
|
|
IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)(consumerPhys + origFirstThunk[i].u1.AddressOfData);
|
|
char* funcName = (char*)importByName->Name;
|
|
|
|
UINT32 funcRva = GetExportRva(providerPhys, funcName);
|
|
if (funcRva == 0) {
|
|
Print(L"FATAL: Unresolved import: %a\n", funcName);
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
// PATCH THE IAT!
|
|
// We write the final Virtual Address (Provider Base + Target RVA)
|
|
// into the physical memory of the consumer's IAT.
|
|
firstThunk[i].u1.Function = providerVirt + funcRva;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
importDesc++;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UefiMain
|
|
// ---------------------------------------------------------------------------
|
|
EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) {
|
|
InitializeLib(ImageHandle, SystemTable);
|
|
|
|
// Open root filesystem on the device we actually booted from
|
|
EFI_LOADED_IMAGE_PROTOCOL* LoadedImage;
|
|
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;
|
|
|
|
// Get the LoadedImage protocol for this application
|
|
EFI_STATUS Status = gBS->HandleProtocol(
|
|
ImageHandle,
|
|
&gEfiLoadedImageProtocolGuid,
|
|
(VOID **)&LoadedImage
|
|
);
|
|
|
|
if (EFI_ERROR(Status)) {
|
|
Print(L"Failed to get LoadedImage protocol\n");
|
|
return Status;
|
|
}
|
|
|
|
// Get the SimpleFileSystem protocol for the device we were loaded from
|
|
Status = gBS->HandleProtocol(
|
|
LoadedImage->DeviceHandle,
|
|
&gEfiSimpleFileSystemProtocolGuid,
|
|
(VOID **)&fs
|
|
);
|
|
|
|
if (EFI_ERROR(Status)) {
|
|
Print(L"Failed to get SimpleFileSystem protocol on boot device\n");
|
|
return Status;
|
|
}
|
|
|
|
// Open the volume
|
|
Status = fs->OpenVolume(fs, &gRoot);
|
|
if (EFI_ERROR(Status)) {
|
|
Print(L"Failed to open root volume\n");
|
|
return Status;
|
|
}
|
|
|
|
Print(L"1\n");
|
|
|
|
// --- Parse boot.ini ---
|
|
UINTN sz;
|
|
UINT8* bootIniRaw = LoadFile(L"boot.ini", &sz);
|
|
PARSE_STATE ps = {0};
|
|
if (bootIniRaw) {
|
|
char* txt = (char *)bootIniRaw;
|
|
txt[sz] = '\0';
|
|
IniParse(txt, BootIniCb, &ps);
|
|
FreePool(bootIniRaw);
|
|
}
|
|
|
|
Print(L"2\n");
|
|
|
|
// --- Parse drivers/drivers.ini ---
|
|
UINT8* drvIniRaw = LoadFile(L"drivers\\drivers.ini", &sz);
|
|
if (drvIniRaw) {
|
|
char* txt = (char *)drvIniRaw;
|
|
txt[sz] = '\0';
|
|
IniParse(txt, DriversIniCb, &ps);
|
|
FreePool(drvIniRaw);
|
|
}
|
|
|
|
Print(L"3\n");
|
|
|
|
BOOT_INFO* bi = AllocateZeroPool(sizeof(BOOT_INFO));
|
|
bi->Magic = BOOT_INFO_MAGIC;
|
|
AsciiStrCpyS(bi->CommandLine, 64, ps.commandLine);
|
|
|
|
// Build PML4.
|
|
gPml4 = AllocPage(NULL);
|
|
|
|
// Current mapping cursor for upper half.
|
|
UINT64 virtCursor = KERNEL_VIRT_BASE;
|
|
|
|
Print(L"4\n");
|
|
|
|
// --- Load and validate PE images ---
|
|
typedef struct {
|
|
CHAR16* path;
|
|
char name[32];
|
|
UINT8* physBase; // Temporary storage for import resolution
|
|
UINT64 virtBase;
|
|
} MODULE_LOAD_INFO;
|
|
|
|
MODULE_LOAD_INFO core[] = {
|
|
{ L"colonel.exe", "kernel.exe", NULL, KERNEL_VIRT_BASE },
|
|
{ L"hal.dll", "hal.dll", NULL, 0 /* Calculated dynamically */ },
|
|
};
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
Print(L"Loading %s...\n", core[i].path);
|
|
UINTN rawSize;
|
|
UINT8* rawBuf = LoadFile(core[i].path, &rawSize);
|
|
if (!rawBuf) {
|
|
Print(L"MISSING: %s\n", core[i].path);
|
|
while(true);
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
// Map sections and apply base relocations
|
|
EFI_PHYSICAL_ADDRESS physBase;
|
|
EFI_STATUS status = LoadPeImage(rawBuf, virtCursor, &physBase);
|
|
if (EFI_ERROR(status)) {
|
|
Print(L"PE Load Error: %r\n", status);
|
|
while(true);
|
|
return status;
|
|
}
|
|
|
|
// Store locations for the Import Resolver
|
|
core[i].physBase = (UINT8*)physBase;
|
|
core[i].virtBase = virtCursor;
|
|
|
|
// Map the pages into the new PML4
|
|
// We need the SizeOfImage from the mapped headers
|
|
UINT32 peOff = *(UINT32*)(rawBuf + 0x3C);
|
|
UINT32 imgLen = *(UINT32*)(rawBuf + peOff + 4 + 20 + 56); // SizeOfImage
|
|
imgLen = (imgLen + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
|
|
for (UINT64 off = 0; off < imgLen; off += PAGE_SIZE) {
|
|
MapPage(virtCursor + off, physBase + off, 0x03 /* P|W */);
|
|
}
|
|
|
|
// Fill BOOT_INFO
|
|
AsciiStrCpyS(bi->Modules[bi->ModuleCount].Name, 32, core[i].name);
|
|
bi->Modules[bi->ModuleCount].VirtBase = virtCursor;
|
|
bi->Modules[bi->ModuleCount].ImageSize = imgLen;
|
|
bi->ModuleCount++;
|
|
|
|
// Increment cursor (1MB alignment for safety)
|
|
virtCursor = (virtCursor + imgLen + 0xFFFFF) & ~0xFFFFF;
|
|
|
|
// We can free the raw file buffer now; sections are in physBase
|
|
FreePool(rawBuf);
|
|
}
|
|
|
|
if (EFI_ERROR(ResolveImports(core[0].physBase, core[1].physBase, core[1].virtBase))) {
|
|
Print(L"Failed to resolve imports.\n");
|
|
while(true);
|
|
}
|
|
|
|
Print(L"5\n");
|
|
|
|
// Load driver .sys files.
|
|
for (int i = 0; i < ps.driverCount; i++) {
|
|
CHAR16 path[128];
|
|
UnicodeSPrint(path, sizeof(path), L"drivers\\%s", ps.drivers[i]);
|
|
UINTN imgSz;
|
|
UINT8* buf = LoadFile(path, &imgSz);
|
|
if (!buf) { Print(L"Driver not found: %s\n", ps.drivers[i]); continue; }
|
|
|
|
UINT32 stored = *(UINT32 *)(buf + *(UINT32 *)(buf + 0x3C) + 4 + 20 + 64);
|
|
UINT32 computed = ComputePeChecksum(buf, imgSz);
|
|
if (stored && computed != stored) {
|
|
Print(L"Checksum mismatch: %s\n", ps.drivers[i]);
|
|
FreePool(buf); continue;
|
|
}
|
|
|
|
UINT32 peOff = *(UINT32 *)(buf + 0x3C);
|
|
UINT32 imgLen = *(UINT32 *)(buf + peOff + 4 + 20 + 56);
|
|
imgLen = (imgLen + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
|
|
EFI_PHYSICAL_ADDRESS phys;
|
|
EFI_STATUS status = LoadPeImage(buf, virtCursor, &phys);
|
|
if (EFI_ERROR(status)) {
|
|
Print(L"Driver PE load failed: %s\n", ps.drivers[i]);
|
|
FreePool(buf);
|
|
continue;
|
|
}
|
|
|
|
UINT64 virt = virtCursor;
|
|
for (UINT64 off = 0; off < imgLen; off += PAGE_SIZE)
|
|
MapPage(virt + off, phys + off, 0x03);
|
|
|
|
char name[32];
|
|
UnicodeStrToAsciiStrS(ps.drivers[i], name, 32);
|
|
AsciiStrCpyS(bi->Modules[bi->ModuleCount].Name, 32, name);
|
|
bi->Modules[bi->ModuleCount].VirtBase = virt;
|
|
bi->Modules[bi->ModuleCount].ImageSize = imgLen;
|
|
bi->ModuleCount++;
|
|
virtCursor = (virtCursor + imgLen + 0xFFFFF) & ~0xFFFFF;
|
|
FreePool(buf);
|
|
}
|
|
|
|
Print(L"6\n");
|
|
|
|
// Query GOP FIRST, before the GetMemoryMap loop
|
|
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
|
|
if (!EFI_ERROR(gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&gop))) {
|
|
bi->FramebufferBase = gop->Mode->FrameBufferBase;
|
|
bi->FramebufferWidth = gop->Mode->Info->HorizontalResolution;
|
|
bi->FramebufferHeight = gop->Mode->Info->VerticalResolution;
|
|
bi->FramebufferStride = gop->Mode->Info->PixelsPerScanLine;
|
|
|
|
// FrameBufferSize is a lie - EFI reports it as Width*Height*4.
|
|
// The ACTUAL consumed bytes are Stride*Height*4.
|
|
// Add one extra page as a safety margin.
|
|
UINT64 fbBytes = (UINT64)gop->Mode->Info->PixelsPerScanLine
|
|
* (UINT64)gop->Mode->Info->VerticalResolution
|
|
* 4 + PAGE_SIZE;
|
|
fbBytes = (fbBytes + PAGE_SIZE - 1) & ~(UINT64)(PAGE_SIZE - 1);
|
|
|
|
for (UINT64 i = 0; i < fbBytes; i += PAGE_SIZE) {
|
|
UINT64 phys = gop->Mode->FrameBufferBase + i;
|
|
MapPage(PHYS_MAP_BASE + phys, phys, 0x13); // P|W|PCD (write-combined MMIO)
|
|
MapPage(phys, phys, 0x13);
|
|
}
|
|
}
|
|
|
|
Print(L"7\n");
|
|
|
|
// --- Map physical RAM as direct window AND identity map ---
|
|
UINTN mapSize = 0, descSz = 0, mapKey; UINT32 descVer;
|
|
gBS->GetMemoryMap(&mapSize, NULL, &mapKey, &descSz, &descVer);
|
|
mapSize += 8 * descSz;
|
|
EFI_MEMORY_DESCRIPTOR* efiMap = AllocatePool(mapSize);
|
|
gBS->GetMemoryMap(&mapSize, efiMap, &mapKey, &descSz, &descVer);
|
|
{
|
|
for (UINTN i = 0; i < mapSize / descSz; i++) {
|
|
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)efiMap + i * descSz);
|
|
|
|
// Map everything except unusable memory to the Physical Window
|
|
if (desc->Type != EfiUnusableMemory && desc->Type != EfiMemoryMappedIOPortSpace) {
|
|
UINT64 start = desc->PhysicalStart;
|
|
UINT64 end = start + (desc->NumberOfPages * PAGE_SIZE);
|
|
|
|
// Add 2 pages (8KB) of padding to the end of the region.
|
|
// This catches ACPI tables that cross page boundaries into unmapped
|
|
// memory because of buggy UEFI size reporting.
|
|
end += 2 * PAGE_SIZE;
|
|
|
|
for (UINT64 phys = start; phys < end; phys += PAGE_SIZE) {
|
|
MapPage(phys, phys, 0x03); // Identity
|
|
MapPage(PHYS_MAP_BASE + phys, phys, 0x03); // High-half
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FreePool(efiMap);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Explicitly Map Standard Architectural MMIO
|
|
// -------------------------------------------------------------------------
|
|
// Motherboards frequently omit the APIC/IOAPIC/HPET from the UEFI memory map.
|
|
// We explicitly map the region from 0xFEC00000 up through 0xFEE00FFF
|
|
// to guarantee the HAL can access them via the physical window.
|
|
for (UINT64 phys = 0xFEC00000; phys < 0xFEE01000; phys += PAGE_SIZE) {
|
|
// In a production OS you'd use Cache-Disable flags (0x13 or 0x1B),
|
|
// but 0x03 is fine for hobby OS initialization before PAT is set up.
|
|
MapPage(phys, phys, 0x03); // Identity Map
|
|
MapPage(PHYS_MAP_BASE + phys, phys, 0x03); // Physical Window Map
|
|
}
|
|
|
|
Print(L"8\n");
|
|
|
|
// --- Convert EFI memory map -> MEMORY_MAP ---
|
|
ConvertMemoryMap(&bi->MemoryMap);
|
|
|
|
// --- Locate ACPI RSDP ---
|
|
// Define the standard UEFI ACPI GUIDs
|
|
EFI_GUID acpi2Guid = { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81} };
|
|
EFI_GUID acpi1Guid = { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d} };
|
|
|
|
bi->RsdpPhysical = 0;
|
|
void* acpi1Table = NULL;
|
|
|
|
// Search the UEFI Configuration Table
|
|
for (UINTN i = 0; i < SystemTable->NumberOfTableEntries; i++) {
|
|
EFI_GUID* vendorGuid = &SystemTable->ConfigurationTable[i].VendorGuid;
|
|
|
|
// Check for ACPI 2.0 (Preferred)
|
|
if (vendorGuid->Data1 == acpi2Guid.Data1 &&
|
|
vendorGuid->Data2 == acpi2Guid.Data2 &&
|
|
vendorGuid->Data3 == acpi2Guid.Data3 &&
|
|
vendorGuid->Data4[0] == acpi2Guid.Data4[0] &&
|
|
vendorGuid->Data4[7] == acpi2Guid.Data4[7])
|
|
{
|
|
bi->RsdpPhysical = (UINT64)SystemTable->ConfigurationTable[i].VendorTable;
|
|
break; // Found the best one, stop searching
|
|
}
|
|
|
|
// Check for ACPI 1.0 (Fallback)
|
|
if (vendorGuid->Data1 == acpi1Guid.Data1 &&
|
|
vendorGuid->Data2 == acpi1Guid.Data2 &&
|
|
vendorGuid->Data3 == acpi1Guid.Data3 &&
|
|
vendorGuid->Data4[0] == acpi1Guid.Data4[0] &&
|
|
vendorGuid->Data4[7] == acpi1Guid.Data4[7])
|
|
{
|
|
acpi1Table = SystemTable->ConfigurationTable[i].VendorTable;
|
|
}
|
|
}
|
|
|
|
// Fallback to ACPI 1.0 if ACPI 2.0 wasn't found
|
|
if (bi->RsdpPhysical == 0 && acpi1Table != NULL) {
|
|
bi->RsdpPhysical = (UINT64)acpi1Table;
|
|
}
|
|
|
|
// Print it out to verify the bootloader actually found it before launching the kernel
|
|
Print(L"Found RSDP at: 0x%llx\n", bi->RsdpPhysical);
|
|
|
|
bi->PhysicalMapBase = PHYS_MAP_BASE;
|
|
bi->DebugEnabled = (UINT32)ps.debug;
|
|
|
|
Print(L"9\n");
|
|
|
|
// --- ExitBootServices ---
|
|
gBS->GetMemoryMap(&mapSize, NULL, &mapKey, &descSz, &descVer);
|
|
gBS->ExitBootServices(ImageHandle, mapKey);
|
|
|
|
// --- Load minimal GDT / IDT, enable NXE, set CR3 ---
|
|
// (These are inline since we have no runtime services after ExitBootServices.)
|
|
// GDT is set up by the kernel's KeInitializeGdt; we just need a valid one to
|
|
// reach KernelMain. The bootloader installed a flat 64-bit GDT earlier via
|
|
// EDK2, which is fine for the jump.
|
|
|
|
// Enable NXE bit in EFER.
|
|
UINT64 efer = __readmsr(0xC0000080);
|
|
__writemsr(0xC0000080, efer | (1ULL << 11));
|
|
|
|
// Switch to our new PML4.
|
|
__writecr3((UINT64)gPml4);
|
|
|
|
// --- Jump to KernelMain ---
|
|
typedef void (*KERNEL_ENTRY)(BOOT_INFO *);
|
|
KERNEL_ENTRY KernelMain = (KERNEL_ENTRY)bi->Modules[0].VirtBase;
|
|
|
|
// The PE entry point is at VirtBase + AddressOfEntryPoint.
|
|
UINT32 epOff = *(UINT32 *)(bi->Modules[0].VirtBase + *(UINT32 *)(bi->Modules[0].VirtBase + 0x3C) + 4 + 20 + 16);
|
|
KernelMain = (KERNEL_ENTRY)(bi->Modules[0].VirtBase + epOff);
|
|
KernelMain(bi);
|
|
|
|
// Never returns.
|
|
_disable(); while (true) __halt();
|
|
}
|