Files
nt-lite/code/boot/uefi/main.c
T

981 lines
35 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 <ntlite/boot_info.h>
#include <ntlite/memory_map.h>
#include <common/ini_parser.h>
#pragma intrinsic(_disable)
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)
#define PAGE_2M (2ULL * 1024ULL * 1024ULL)
#define PAGE_1G (1024ULL * 1024ULL * 1024ULL)
static UINT64* gPml4;
static UINT64* EnsureTable(UINT64* parent, UINTN index) {
UINT64* entry = &parent[index];
if (!(*entry & 1)) {
EFI_PHYSICAL_ADDRESS p;
UINT64* child = AllocPage(&p);
*entry = p | 3;
return child;
}
return (UINT64 *)(*entry & ~0xFFFULL);
}
static void Map4K(UINT64 virt, UINT64 phys, UINT64 flags) {
UINT64* pdpt = EnsureTable(gPml4, PML4_IDX(virt));
UINT64* pdt = EnsureTable(pdpt, PDPT_IDX(virt));
UINT64* pt = EnsureTable(pdt, PDT_IDX(virt));
pt[PT_IDX(virt)] = phys | flags;
}
static void Map2M(UINT64 virt, UINT64 phys, UINT64 flags) {
UINT64* pdpt = EnsureTable(gPml4, PML4_IDX(virt));
UINT64* pdt = EnsureTable(pdpt, PDPT_IDX(virt));
pdt[PDT_IDX(virt)] = phys | flags | 0x80ULL; // PS bit
}
static void Map1G(UINT64 virt, UINT64 phys, UINT64 flags) {
UINT64* pdpt = EnsureTable(gPml4, PML4_IDX(virt));
pdpt[PDPT_IDX(virt)] = phys | flags | 0x80ULL; // PS bit
}
static void MapRangeSmart(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags) {
while (size) {
if ((virt & (PAGE_1G - 1)) == 0 && (phys & (PAGE_1G - 1)) == 0 && size >= PAGE_1G) {
Map1G(virt, phys, flags);
virt += PAGE_1G;
phys += PAGE_1G;
size -= PAGE_1G;
} else if ((virt & (PAGE_2M - 1)) == 0 && (phys & (PAGE_2M - 1)) == 0 && size >= PAGE_2M) {
Map2M(virt, phys, flags);
virt += PAGE_2M;
phys += PAGE_2M;
size -= PAGE_2M;
} else {
Map4K(virt, phys, flags);
virt += PAGE_SIZE;
phys += PAGE_SIZE;
size -= PAGE_SIZE;
}
}
}
static void MapRangeSkipping(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags,
const UINT64* holes, UINTN holeCount) {
UINT64 curVirt = virt;
UINT64 curPhys = phys;
UINT64 end = virt + size;
for (UINTN i = 0; i < holeCount; i++) {
UINT64 hStart = holes[i * 2 + 0];
UINT64 hEnd = holes[i * 2 + 1];
if (hEnd <= curVirt || hStart >= end) continue;
if (curVirt < hStart) {
UINT64 prefixSize = hStart - curVirt;
MapRangeSmart(curVirt, curPhys, prefixSize, flags);
curPhys += prefixSize;
curVirt += prefixSize;
}
if (curVirt < hEnd) {
UINT64 skip = hEnd - curVirt;
curVirt += skip;
curPhys += skip;
}
}
if (curVirt < end) {
MapRangeSmart(curVirt, curPhys, end - curVirt, 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 int ParseBootIniInteger(const char* val) {
int out = 0;
if (!val) return 0;
while (*val == ' ' || *val == '\t') val++;
int base = 10;
if (val[0] == '0' && (val[1] == 'x' || val[1] == 'X')) {
base = 16;
val += 2;
}
while (*val) {
char c = *val++;
int digit;
if (c >= '0' && c <= '9') digit = c - '0';
else if (c >= 'a' && c <= 'f') digit = 10 + c - 'a';
else if (c >= 'A' && c <= 'F') digit = 10 + c - 'A';
else break;
if (digit >= base) break;
out = out * base + digit;
}
return out;
}
static void BootIniCb(const char* section, const char* key,
const char* val, void* ud) {
PARSE_STATE* s = (PARSE_STATE *)ud;
// [kernel] debug = bitmask: 1=console, 2=selftests, 4=file log
if (!AsciiStrCmp(section, "kernel") && !AsciiStrCmp(key, "debug"))
s->debug = ParseBootIniInteger(val);
// [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 EFI_PHYSICAL_ADDRESS AllocateApTrampolinePageBelow1M(void) {
// AP startup via SIPI requires the first instruction page to live below
// 1 MiB. Some real UEFI firmware exposes no ConventionalMemory below
// 640 KiB after ExitBootServices, so reserve a loader-data page here while
// boot services can still satisfy AllocateMaxAddress.
EFI_PHYSICAL_ADDRESS phys = 0x0009F000ULL;
EFI_STATUS st = gBS->AllocatePages(AllocateMaxAddress, EfiLoaderData, 1, &phys);
if (EFI_ERROR(st) || phys == 0 || phys >= 0x000A0000ULL || (phys & 0xFFFULL)) {
return 0;
}
gBS->SetMem((VOID*)phys, PAGE_SIZE, 0);
return phys;
}
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"Signature mismatch\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);
EFI_TIME fwTime;
if (!EFI_ERROR(gRT->GetTime(&fwTime, NULL)) &&
fwTime.Year >= 1970 && fwTime.Month >= 1 && fwTime.Month <= 12 &&
fwTime.Day >= 1 && fwTime.Day <= 31 && fwTime.Hour <= 23 &&
fwTime.Minute <= 59 && fwTime.Second <= 59 &&
fwTime.Nanosecond < 1000000000U) {
bi->BootTime.Year = fwTime.Year;
bi->BootTime.Month = fwTime.Month;
bi->BootTime.Day = fwTime.Day;
bi->BootTime.Hour = fwTime.Hour;
bi->BootTime.Minute = fwTime.Minute;
bi->BootTime.Second = fwTime.Second;
bi->BootTime.Daylight = fwTime.Daylight;
bi->BootTime.Nanosecond = fwTime.Nanosecond;
bi->BootTime.TimeZoneMinutes = fwTime.TimeZone;
bi->BootTime.Flags = BOOT_TIME_FLAG_VALID | BOOT_TIME_FLAG_UEFI;
if (fwTime.TimeZone != EFI_UNSPECIFIED_TIMEZONE)
bi->BootTime.Flags |= BOOT_TIME_FLAG_TZ_KNOWN;
}
// 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"system\\colonel.exe", "kernel.exe", NULL, KERNEL_VIRT_BASE },
{ L"system\\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) {
Map4K(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 into raw buffers
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;
}
// Store file info for the kernel to process later
char name[32];
UnicodeStrToAsciiStrS(ps.drivers[i], name, 32);
BOOT_MODULE* mod = &bi->Modules[bi->ModuleCount++];
AsciiStrCpyS(mod->Name, 32, name);
mod->PhysBase = (uintptr_t)buf; // Raw physical buffer from LoadFile
mod->ImageSize = (size_t)imgSz;
mod->VirtBase = 0; // Kernel will map this later
}
//Print(L"6\n");
// Query GOP FIRST, before the GetMemoryMap loop
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
UINT64 fbBase = 0;
UINT64 fbBytes = 0;
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.
fbBase = gop->Mode->FrameBufferBase;
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;
Map4K(PHYS_MAP_BASE + phys, phys, 0x13); // P|W|PCD (write-combined MMIO)
Map4K(phys, phys, 0x13);
}
}
//Print(L"7\n");
bi->ApTrampolinePhys = (uintptr_t)AllocateApTrampolinePageBelow1M();
// --- 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);
{
UINT64 idHoles[8];
UINT64 winHoles[8];
UINTN idHoleCount = 0;
UINTN winHoleCount = 0;
// Leave virtual page zero genuinely unmapped. Mapping it supervisor/NX
// still hides null pointer bugs from the page-fault path and makes
// diagnostics worse. The physical-window alias for frame zero remains
// available at PHYS_MAP_BASE if low physical memory is ever needed.
idHoles[idHoleCount * 2 + 0] = 0;
idHoles[idHoleCount * 2 + 1] = PAGE_SIZE;
idHoleCount++;
if (fbBytes) {
idHoles[idHoleCount * 2 + 0] = fbBase;
idHoles[idHoleCount * 2 + 1] = fbBase + fbBytes;
idHoleCount++;
winHoles[winHoleCount * 2 + 0] = PHYS_MAP_BASE + fbBase;
winHoles[winHoleCount * 2 + 1] = PHYS_MAP_BASE + fbBase + fbBytes;
winHoleCount++;
}
idHoles[idHoleCount * 2 + 0] = 0xFEC00000ULL;
idHoles[idHoleCount * 2 + 1] = 0xFEE01000ULL;
idHoleCount++;
winHoles[winHoleCount * 2 + 0] = PHYS_MAP_BASE + 0xFEC00000ULL;
winHoles[winHoleCount * 2 + 1] = PHYS_MAP_BASE + 0xFEE01000ULL;
winHoleCount++;
for (UINTN pass = 0; pass < idHoleCount; pass++) {
for (UINTN i = 1; i < idHoleCount; i++) {
if (idHoles[i * 2] < idHoles[(i - 1) * 2]) {
UINT64 s0 = idHoles[(i - 1) * 2 + 0];
UINT64 e0 = idHoles[(i - 1) * 2 + 1];
idHoles[(i - 1) * 2 + 0] = idHoles[i * 2 + 0];
idHoles[(i - 1) * 2 + 1] = idHoles[i * 2 + 1];
idHoles[i * 2 + 0] = s0;
idHoles[i * 2 + 1] = e0;
}
}
}
for (UINTN pass = 0; pass < winHoleCount; pass++) {
for (UINTN i = 1; i < winHoleCount; i++) {
if (winHoles[i * 2] < winHoles[(i - 1) * 2]) {
UINT64 s0 = winHoles[(i - 1) * 2 + 0];
UINT64 e0 = winHoles[(i - 1) * 2 + 1];
winHoles[(i - 1) * 2 + 0] = winHoles[i * 2 + 0];
winHoles[(i - 1) * 2 + 1] = winHoles[i * 2 + 1];
winHoles[i * 2 + 0] = s0;
winHoles[i * 2 + 1] = e0;
}
}
}
for (UINTN i = 0; i < mapSize / descSz; i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)efiMap + i * descSz);
// Map normal firmware-described regions into the physical window.
// PCI/firmware MMIO is mapped explicitly by the owning driver.
if (desc->Type != EfiUnusableMemory &&
desc->Type != EfiMemoryMappedIO &&
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;
MapRangeSkipping(start, start, end - start, 0x03, idHoles, idHoleCount);
MapRangeSkipping(PHYS_MAP_BASE + start, start, end - start, 0x03, winHoles, winHoleCount);
}
}
}
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) {
Map4K(phys, phys, 0x13); // Identity Map, P|W|PCD
Map4K(PHYS_MAP_BASE + phys, phys, 0x13); // Physical Window Map, P|W|PCD
}
//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%016llX\n", bi->RsdpPhysical);
bi->PhysicalMapBase = PHYS_MAP_BASE;
bi->Debug = (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();
}