From def649b9953023641abd0866619fbd9c6ab61702 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Thu, 14 May 2026 14:57:00 +0200 Subject: [PATCH] memory savings --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes code/boot/main.c | 1684 +++++++++++++++++++++------------------- code/kernel/kmain.c | 950 ++++++++++++----------- code/kernel/mm_phys.c | 352 +++++---- code/kernel/mm_virt.c | 943 +++++++++++----------- code/video/video.c | 2 +- readme.md | 1 - 7 files changed, 2049 insertions(+), 1883 deletions(-) diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 7de2fece8e77552e9d5f9da0e8f331637215a718..9d59754dafb940e96e5860f409b4f3817ec9c1f6 100644 GIT binary patch delta 121 zcmZo@P;3CA7RD`1MN=pH1vzcsGL`8P*XDP*yI2{YZ=M*`Ex`B!LcN4guOQTG2=xX+ xy@gQkfRv}eK$pv}}jPHTmi(K2=7BKbQY7<~-6JTu=6JTo-U|%M{ GVE_RC4-w!1 diff --git a/code/boot/main.c b/code/boot/main.c index c6a528b..9012b33 100644 --- a/code/boot/main.c +++ b/code/boot/main.c @@ -1,790 +1,894 @@ -// 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 -#include -#include -#include -#include -#include -#include - -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"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) { - 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 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; - 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(); -} +// 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 +#include +#include +#include +#include +#include +#include + +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 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"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"); + + // --- 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[4]; + UINT64 winHoles[4]; + UINTN holeCount = 0; + if (fbBytes) { + idHoles[holeCount * 2 + 0] = fbBase; + idHoles[holeCount * 2 + 1] = fbBase + fbBytes; + winHoles[holeCount * 2 + 0] = PHYS_MAP_BASE + fbBase; + winHoles[holeCount * 2 + 1] = PHYS_MAP_BASE + fbBase + fbBytes; + holeCount++; + } + idHoles[holeCount * 2 + 0] = 0xFEC00000ULL; + idHoles[holeCount * 2 + 1] = 0xFEE01000ULL; + winHoles[holeCount * 2 + 0] = PHYS_MAP_BASE + 0xFEC00000ULL; + winHoles[holeCount * 2 + 1] = PHYS_MAP_BASE + 0xFEE01000ULL; + holeCount++; + + if (holeCount == 2) { + if (idHoles[2] < idHoles[0]) { + UINT64 t0 = idHoles[0], t1 = idHoles[1]; + idHoles[0] = idHoles[2]; + idHoles[1] = idHoles[3]; + idHoles[2] = t0; + idHoles[3] = t1; + } + if (winHoles[2] < winHoles[0]) { + UINT64 t0 = winHoles[0], t1 = winHoles[1]; + winHoles[0] = winHoles[2]; + winHoles[1] = winHoles[3]; + winHoles[2] = t0; + winHoles[3] = t1; + } + } + + 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; + + MapRangeSkipping(start, start, end - start, 0x03, idHoles, holeCount); + MapRangeSkipping(PHYS_MAP_BASE + start, start, end - start, 0x03, winHoles, holeCount); + } + } + } + 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. + Map4K(phys, phys, 0x03); // Identity Map + Map4K(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(); +} diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index 5b10f5f..11838cb 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -1,459 +1,491 @@ -// kmain.c - kernel entry point -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef struct { - BOOT_INFO* bootInfo; - const HAL_API* hal; -} SYSTEM_INIT_ARGS; - -typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API *); - -// HAL pointer cached at boot so kernel threads can reach it without -// threading a parameter through every call chain. -static const HAL_API* gKmainHal; - -// --------------------------------------------------------------------------- -// Phase Helper: Resolve Imports for core modules -// --------------------------------------------------------------------------- -static void ResolveSystemImports(BOOT_INFO* bootInfo) { - // Only resolve imports for kernel.exe and hal.dll (typically indices 0 and 1). - // The bootloader maps these, but drivers are now passed as raw physical files. - uint32_t coreModuleCount = (bootInfo->ModuleCount > 2) ? 2 : bootInfo->ModuleCount; - - for (uint32_t i = 0; i < coreModuleCount; i++) { - if (bootInfo->Modules[i].VirtBase != 0) { - LdrResolveImports(bootInfo->Modules[i].VirtBase); - } - } -} - -// --------------------------------------------------------------------------- -// Phase Helper: Initialize dynamically loaded .sys drivers -// --------------------------------------------------------------------------- -static void DriversIniCb(const char* section, const char* key, - const char* val, void* ud) { - (void)val; - if (!strcmp(section, "runtime")) { - char*** pArray = (char***)ud; - size_t len = strlen(key) + 1; - char* keyCopy = MmAllocatePool(len); - if (keyCopy) { - memcpy(keyCopy, key, len); - arr_push(*pArray, keyCopy); - } - } -} - -static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { - DbgPrint("InitializeDrivers: Initializing drivers...\n"); - - // boot drivers (provided by the bootloader) - for (uint32_t i = 0; i < bootInfo->ModuleCount; i++) { - BOOT_MODULE* mod = &bootInfo->Modules[i]; - - // Skip modules that are already mapped (Kernel/HAL) - // or have no physical data (invalid) - if (mod->VirtBase != 0 || mod->PhysBase == 0) continue; - - DbgPrint("InitializeDrivers: Loading boot driver %s\n", mod->Name); - - // Calculate virtual address of the raw file in the physical window - void* rawPeBuffer = (void*)(bootInfo->PhysicalMapBase + mod->PhysBase); - - // Map the PE image into kernel space - uintptr_t mappedBase = 0; - KSTATUS st = LdrLoadImage(rawPeBuffer, mod->ImageSize, &mappedBase, false); - if (!KSUCCESS(st)) { - DbgPrint("ERROR: Failed to map driver %s (0x%X)\n", mod->Name, st); - continue; - } - - // Update the struct so other modules can find it - mod->VirtBase = mappedBase; - - // Resolve imports (can now see Kernel, HAL, and previous drivers) - if (!KSUCCESS(LdrResolveImports(mappedBase))) { - DbgPrint("ERROR: Failed to resolve imports for %s\n", mod->Name); - continue; - } - - // Register with the global module list so subsequent drivers can import from this one - LdrRegisterModule(mod->Name, mod->VirtBase); - - // Execute Entry Point - DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)LdrGetEntryPoint(mappedBase); - if (entry) { - KSTATUS ds = entry(hal); - if (!KSUCCESS(ds)) { - DbgPrint("WARNING: Driver %s returned failure status 0x%X\n", mod->Name, ds); - } - } - } - - // runtime drivers - KSTATUS s; - FILE_OBJECT* f; - - s = IoOpenFile("drivers\\drivers.ini", &f); - if (s != STATUS_SUCCESS) { - KePanic("InitializeDrivers: Failed to open drivers.ini (status %d)\n", s); - } - - uint64_t size = IoGetFileSize(f) + 1; - char* mem = MmAllocatePool(size); - size_t got; - if (!KSUCCESS(IoReadFile(f, mem, size, &got))) { - IoCloseFile(f); - KePanic("InitializeDrivers: Failed to read drivers.ini\n"); - } - mem[got] = '\0'; - - IoCloseFile(f); - - char** runtimeKeys = NULL; - IniParse(mem, DriversIniCb, &runtimeKeys); - - for (uint32_t i = 0; i < arr_len(runtimeKeys); i++) { - DbgPrint("Found runtime driver: %s\n", runtimeKeys[i]); - - // Load the driver - size_t len = strlen(runtimeKeys[i]); - char* path = MmAllocatePool(len + 10); - if (!path) { - DbgPrint("KernelMain: Failed to allocate memory for driver path. How?\n"); - continue; - } - - // memcpy maneuvers - memcpy(path, "drivers\\", 8); - memcpy(path + 8, runtimeKeys[i], len + 1); - - s = IoOpenFile(path, &f); - if (s != STATUS_SUCCESS) { - DbgPrint("InitializeDrivers: Failed to open runtime driver: %s (%s)\n", runtimeKeys[i], path); - continue; // Skip this driver but keep going with others - } - - MmFreePool(path); - - uint64_t drvSize = IoGetFileSize(f); - void* drvMem = MmAllocatePool(drvSize); - if (!KSUCCESS(IoReadFile(f, drvMem, drvSize, &got))) { - DbgPrint("InitializeDrivers: Failed to read runtime driver: %s\n", runtimeKeys[i]); - IoCloseFile(f); - continue; - } - IoCloseFile(f); - - uintptr_t mappedBase = 0; - if (!KSUCCESS(LdrLoadImage(drvMem, drvSize, &mappedBase, false))) { // Load into kernel space - DbgPrint("InitializeDrivers: Failed to load runtime driver: %s\n", runtimeKeys[i]); - MmFreePool(drvMem); - continue; - } - MmFreePool(drvMem); - - if (!KSUCCESS(LdrResolveImports(mappedBase))) { - DbgPrint("InitializeDrivers: Failed to resolve imports for runtime driver: %s\n", runtimeKeys[i]); - continue; - } - - DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)(LdrGetEntryPoint(mappedBase)); - - KSTATUS ds = entry(hal); - if (!KSUCCESS(ds)) { - DbgPrint("InitializeDrivers: Driver %s failed to init!\n", runtimeKeys[i]); - } - } - - // Free the individual strings allocated via strdup in the callback - if (runtimeKeys) { - for (uint32_t i = 0; i < arr_len(runtimeKeys); i++) { - if (runtimeKeys[i]) { - MmFreePool(runtimeKeys[i]); - } - } - // Free the dynamic array structure itself - arr_free(runtimeKeys); - } - - // Free the file buffer - MmFreePool(mem); -} - -// --------------------------------------------------------------------------- -// Phase Helper: Load and launch a process from the filesystem. -// Returns the new PROCESS* with one reference owned by the caller. -// Returns NULL on failure. -// --------------------------------------------------------------------------- -static PROCESS* LaunchUserProcess(const char* filePath, const char* args) { - KSTATUS s; - FILE_OBJECT* f; - - // Read the executable file into a raw buffer. - s = IoOpenFile(filePath, &f); - DbgPrint("LaunchUserProcess: IoOpenFile: %d\n", s); - if (s != STATUS_SUCCESS) { - DbgPrint("Failed to load file: \"%s\"!\n", filePath); - return 0; - } - - uint64_t size = IoGetFileSize(f); - DbgPrint("LaunchUserProcess: IoGetFileSize: %llu\n", size); - - void* mem = MmAllocateUserMemory(size); - DbgPrint("LaunchUserProcess: MmAllocateUserMemory: 0x%llX\n", mem); - if (!mem) { - IoCloseFile(f); - return 0; - } - - size_t got = 0; - s = IoReadFile(f, mem, size, &got); - DbgPrint("LaunchUserProcess: IoReadFile: %d\n", s); - IoCloseFile(f); - if (!KSUCCESS(s)) { - MmFreeUserMemory(mem, size); - return 0; - } - - // Create the process object. - PROCESS* proc; - THREAD* thread; - - s = KeCreateProcess(filePath, &proc); - if (!KSUCCESS(s)) { - DbgPrint("Failed to create process. System halted.\n"); - MmFreeUserMemory(mem, size); - return 0; - } - DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath); - - if (args && args[0]) - strncpy(proc->Cmdline, args, sizeof(proc->Cmdline) - 1); - else - strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1); - proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0'; - - // proc->Cmdline is kernel pool (ffffc000...) - not readable at CPL=3. - // Copy into user memory before handing the pointer to the initial thread. - size_t cmdlineLen = strlen(proc->Cmdline) + 1; - char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen); - if (!userCmdline) { - DbgPrint("LaunchUserProcess: Failed to allocate user cmdline buffer.\n"); - MmFreeUserMemory(mem, size); - ObDereferenceObject(&proc->Header); - return 0; - } - memcpy(userCmdline, proc->Cmdline, cmdlineLen); - proc->UserCmdlineBase = (uintptr_t)userCmdline; - proc->UserCmdlineSize = cmdlineLen; - - // Parse the raw PE file and map it into executable memory. - uintptr_t imageBase = 0; - s = LdrLoadImage(mem, size, &imageBase, true); - MmFreeUserMemory(mem, size); - if (!KSUCCESS(s)) { - DbgPrint("LaunchUserProcess: LdrLoadImage failed with status: %d\n", s); - ObDereferenceObject(&proc->Header); - return 0; - } - - s = LdrResolveImportsForProcess(proc, imageBase); - if (!KSUCCESS(s)) { - DbgPrint("LaunchUserProcess: Failed to resolve EXE imports for %s (status %d)\n", filePath, s); - MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); - ObDereferenceObject(&proc->Header); - return 0; - } - - uintptr_t entryPoint = LdrGetEntryPoint(imageBase); - DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint); - - // Create the main thread, pointing it to the PE Entry Point. - s = KeCreateThread(proc, (uintptr_t)entryPoint, (uintptr_t)userCmdline, PriorityNormal, &thread); - if (!KSUCCESS(s)) { - DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s); - MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); - ObDereferenceObject(&proc->Header); - return 0; - } - proc->ImageBase = imageBase; - proc->ImageSize = LdrGetImageSize(imageBase); - DbgPrint("LaunchUserProcess: Thread created and queued!\n"); - - // Caller owns the reference returned by KeCreateProcess. - return proc; -} -static void PrintProcessList() { - LIST_ENTRY* listHead = KeGetProcessList(); - LIST_ENTRY* current; - - DbgPrint("\nPID | Name | Threads | ExitCode\n"); - DbgPrint("---------------------------------------------\n"); - - for (current = listHead->Fwd; current != listHead; current = current->Fwd) { - // Translate the list entry back to the PROCESS structure - PROCESS* proc = CONTAINING_RECORD(current, PROCESS, GlobalLink); - - DbgPrint("%-5u | %-16s | %-7u | %d\n", - proc->ProcessId, - proc->Name, - proc->ThreadCount, - proc->ExitCode); - } -} - -void SystemInitThread(void* arg) { - SYSTEM_INIT_ARGS* args = (SYSTEM_INIT_ARGS*)arg; - - DbgPrint("SystemInitThread: Thread started. Igniting the system...\n"); - - InitializeDrivers(args->bootInfo, args->hal); - - ResolveSystemImports(args->bootInfo); - - args->hal->Screen.Clear(0x000000); - - // Print memory status now that the system is fully alive - DbgPrint("Memory Status:\n"); - DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f)); - DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f)); - DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f)); - - // Launch User-Mode init.exe and restart it until it exits cleanly. - while (true) { - PROCESS* shellProc = LaunchUserProcess("system/init.exe", args->bootInfo->CommandLine); - if (!shellProc) { - DbgPrint("SystemInitThread: Failed to start system/init.exe; retrying.\n"); - KeYield(); - continue; - } - - KeWaitForEvent(shellProc->ExitEvent); - int exitCode = shellProc->ExitCode; - DbgPrint("SystemInitThread: init.exe exited with code %d.\n", exitCode); - ObDereferenceObject(&shellProc->Header); - - if (exitCode == 0) break; - DbgPrint("SystemInitThread: Restarting init.exe.\n"); - } - - DbgPrint("SystemInitThread: init.exe completed successfully; shutting down.\n"); - gKmainHal->Power.Shutdown(); - MmFreePool(args); -} - -// --------------------------------------------------------------------------- -// Main Kernel Entry -// --------------------------------------------------------------------------- -extern void KernelMain(BOOT_INFO* bootInfo) { - // - // Physical & Virtual Memory initialization - // - if (!MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase)) { - _disable(); while (true) __halt(); // there's nothing we can do - } - - PHYSICAL_ADDRESS pml4 = (PHYSICAL_ADDRESS)__readcr3(); - MmInitializeVirtual(pml4, 4 * 1024 * 1024 /* 4 MB initial pool */); - - // - // CPU Architecture setup (GDT/IDT/Faults) - // - KeInitializeGdt(); - KeInitializeIdt(); - - // - // Register boot modules (Allows LdrGetExport to work for drivers and the HAL) - // Note: Kept to a single call here to avoid the duplicate in your original code. - // - LdrRegisterModulesFromBootInfo(bootInfo); - - // - // HAL Initialization - // - HAL_INIT_PARAMS halParams = { - .PhysicalWindowBase = bootInfo->PhysicalMapBase, - .FramebufferBase = bootInfo->FramebufferBase, - .FramebufferWidth = bootInfo->FramebufferWidth, - .FramebufferHeight = bootInfo->FramebufferHeight, - .FramebufferStride = bootInfo->FramebufferStride, - .RsdpPhysical = bootInfo->RsdpPhysical, - .DebugEnabled = bootInfo->DebugEnabled - }; - - KSTATUS s = HalInitialize(&halParams); - if (!KSUCCESS(s)) { - _disable(); while (true) __halt(); // there's nothing we can do. - } - - const HAL_API* hal = HalGetApi(); - gKmainHal = hal; - - KeInitDebug(hal->Screen.PrintChar, bootInfo->DebugEnabled); // Enable debug - - KeIrqInitialize(hal); - - KeIrqRegister(hal->Timer.IrqNumber, hal->Timer.Isr); - KeIrqRegister(hal->Keyboard.IrqNumber, hal->Keyboard.Isr); - KeIrqRegister(hal->Mouse.IrqNumber, hal->Mouse.Isr); - - HalRegisterScheduler(KeSchedulerTick); - - KeSyscallSetHal(hal); - - // - // Dynamic Linking & Driver Execution - // - ResolveSystemImports(bootInfo); - //InitializeDrivers(bootInfo, hal); - - // - // Scheduler Prep & Status Update - // - KeInitializeScheduler(); - - // Pack the arguments for the init thread - SYSTEM_INIT_ARGS* initArgs = MmAllocatePool(sizeof(SYSTEM_INIT_ARGS)); - initArgs->bootInfo = bootInfo; - initArgs->hal = hal; - - // Spawn the Initialization Thread - THREAD* initThread = (THREAD*)0; - KSTATUS st = KeCreateKernelThread((uintptr_t)SystemInitThread, - (uintptr_t)initArgs, - PriorityNormal, &initThread); - - if (!KSUCCESS(st)) { - KePanic("KernelMain: Failed to create SystemInitThread (status 0x%x)\n", st); - } - - DbgPrint("KernelMain: Handing over to scheduler...\n"); - - // Ignite the system! - _enable(); - - // The Idle Loop: The CPU rests here whenever all other threads are waiting on events. - while (true) { - __halt(); - } -} +// kmain.c - kernel entry point +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + BOOT_INFO* bootInfo; + const HAL_API* hal; +} SYSTEM_INIT_ARGS; + +typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API *); + +// HAL pointer cached at boot so kernel threads can reach it without +// threading a parameter through every call chain. +static const HAL_API* gKmainHal; + +static int PageInBootloaderRegion(const BOOT_INFO* bootInfo, PHYSICAL_ADDRESS page) { + for (uint32_t i = 0; i < bootInfo->MemoryMap.RegionCount; i++) { + const MEMORY_REGION* r = &bootInfo->MemoryMap.Regions[i]; + if (r->Type != MemoryBootloader) continue; + + uintptr_t start = r->Base & PAGE_MASK; + uintptr_t end = (r->Base + r->Length + PAGE_SIZE - 1) & PAGE_MASK; + if ((uintptr_t)page >= start && (uintptr_t)page < end) { + return 1; + } + } + return 0; +} + +static void ReclaimBootloaderMemory(const BOOT_INFO* bootInfo) { + // Reclaim only the raw buffers backing boot-loaded .sys modules. + // The kernel/HAL images, page tables, and bootloader code remain live. + for (uint32_t i = 2; i < bootInfo->ModuleCount; i++) { + const BOOT_MODULE* mod = &bootInfo->Modules[i]; + if (!mod->PhysBase || !mod->ImageSize) continue; + + uintptr_t base = (uintptr_t)(mod->PhysBase & PAGE_MASK); + uintptr_t end = (uintptr_t)((mod->PhysBase + mod->ImageSize + PAGE_SIZE - 1) & PAGE_MASK); + + for (uintptr_t page = base; page < end; page += PAGE_SIZE) { + if (!PageInBootloaderRegion(bootInfo, (PHYSICAL_ADDRESS)page)) continue; + MmFreeFrame((PHYSICAL_ADDRESS)page); + } + } +} + +// --------------------------------------------------------------------------- +// Phase Helper: Resolve Imports for core modules +// --------------------------------------------------------------------------- +static void ResolveSystemImports(BOOT_INFO* bootInfo) { + // Only resolve imports for kernel.exe and hal.dll (typically indices 0 and 1). + // The bootloader maps these, but drivers are now passed as raw physical files. + uint32_t coreModuleCount = (bootInfo->ModuleCount > 2) ? 2 : bootInfo->ModuleCount; + + for (uint32_t i = 0; i < coreModuleCount; i++) { + if (bootInfo->Modules[i].VirtBase != 0) { + LdrResolveImports(bootInfo->Modules[i].VirtBase); + } + } +} + +// --------------------------------------------------------------------------- +// Phase Helper: Initialize dynamically loaded .sys drivers +// --------------------------------------------------------------------------- +static void DriversIniCb(const char* section, const char* key, + const char* val, void* ud) { + (void)val; + if (!strcmp(section, "runtime")) { + char*** pArray = (char***)ud; + size_t len = strlen(key) + 1; + char* keyCopy = MmAllocatePool(len); + if (keyCopy) { + memcpy(keyCopy, key, len); + arr_push(*pArray, keyCopy); + } + } +} + +static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { + DbgPrint("InitializeDrivers: Initializing drivers...\n"); + + // boot drivers (provided by the bootloader) + for (uint32_t i = 0; i < bootInfo->ModuleCount; i++) { + BOOT_MODULE* mod = &bootInfo->Modules[i]; + + // Skip modules that are already mapped (Kernel/HAL) + // or have no physical data (invalid) + if (mod->VirtBase != 0 || mod->PhysBase == 0) continue; + + DbgPrint("InitializeDrivers: Loading boot driver %s\n", mod->Name); + + // Calculate virtual address of the raw file in the physical window + void* rawPeBuffer = (void*)(bootInfo->PhysicalMapBase + mod->PhysBase); + + // Map the PE image into kernel space + uintptr_t mappedBase = 0; + KSTATUS st = LdrLoadImage(rawPeBuffer, mod->ImageSize, &mappedBase, false); + if (!KSUCCESS(st)) { + DbgPrint("ERROR: Failed to map driver %s (0x%X)\n", mod->Name, st); + continue; + } + + // Update the struct so other modules can find it + mod->VirtBase = mappedBase; + + // Resolve imports (can now see Kernel, HAL, and previous drivers) + if (!KSUCCESS(LdrResolveImports(mappedBase))) { + DbgPrint("ERROR: Failed to resolve imports for %s\n", mod->Name); + continue; + } + + // Register with the global module list so subsequent drivers can import from this one + LdrRegisterModule(mod->Name, mod->VirtBase); + + // Execute Entry Point + DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)LdrGetEntryPoint(mappedBase); + if (entry) { + KSTATUS ds = entry(hal); + if (!KSUCCESS(ds)) { + DbgPrint("WARNING: Driver %s returned failure status 0x%X\n", mod->Name, ds); + } + } + } + + // runtime drivers + KSTATUS s; + FILE_OBJECT* f; + + s = IoOpenFile("drivers\\drivers.ini", &f); + if (s != STATUS_SUCCESS) { + KePanic("InitializeDrivers: Failed to open drivers.ini (status %d)\n", s); + } + + uint64_t size = IoGetFileSize(f) + 1; + char* mem = MmAllocatePool(size); + size_t got; + if (!KSUCCESS(IoReadFile(f, mem, size, &got))) { + IoCloseFile(f); + KePanic("InitializeDrivers: Failed to read drivers.ini\n"); + } + mem[got] = '\0'; + + IoCloseFile(f); + + char** runtimeKeys = NULL; + IniParse(mem, DriversIniCb, &runtimeKeys); + + for (uint32_t i = 0; i < arr_len(runtimeKeys); i++) { + DbgPrint("Found runtime driver: %s\n", runtimeKeys[i]); + + // Load the driver + size_t len = strlen(runtimeKeys[i]); + char* path = MmAllocatePool(len + 10); + if (!path) { + DbgPrint("KernelMain: Failed to allocate memory for driver path. How?\n"); + continue; + } + + // memcpy maneuvers + memcpy(path, "drivers\\", 8); + memcpy(path + 8, runtimeKeys[i], len + 1); + + s = IoOpenFile(path, &f); + if (s != STATUS_SUCCESS) { + DbgPrint("InitializeDrivers: Failed to open runtime driver: %s (%s)\n", runtimeKeys[i], path); + continue; // Skip this driver but keep going with others + } + + MmFreePool(path); + + uint64_t drvSize = IoGetFileSize(f); + void* drvMem = MmAllocatePool(drvSize); + if (!KSUCCESS(IoReadFile(f, drvMem, drvSize, &got))) { + DbgPrint("InitializeDrivers: Failed to read runtime driver: %s\n", runtimeKeys[i]); + IoCloseFile(f); + continue; + } + IoCloseFile(f); + + uintptr_t mappedBase = 0; + if (!KSUCCESS(LdrLoadImage(drvMem, drvSize, &mappedBase, false))) { // Load into kernel space + DbgPrint("InitializeDrivers: Failed to load runtime driver: %s\n", runtimeKeys[i]); + MmFreePool(drvMem); + continue; + } + MmFreePool(drvMem); + + if (!KSUCCESS(LdrResolveImports(mappedBase))) { + DbgPrint("InitializeDrivers: Failed to resolve imports for runtime driver: %s\n", runtimeKeys[i]); + continue; + } + + DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)(LdrGetEntryPoint(mappedBase)); + + KSTATUS ds = entry(hal); + if (!KSUCCESS(ds)) { + DbgPrint("InitializeDrivers: Driver %s failed to init!\n", runtimeKeys[i]); + } + } + + // Free the individual strings allocated via strdup in the callback + if (runtimeKeys) { + for (uint32_t i = 0; i < arr_len(runtimeKeys); i++) { + if (runtimeKeys[i]) { + MmFreePool(runtimeKeys[i]); + } + } + // Free the dynamic array structure itself + arr_free(runtimeKeys); + } + + // Free the file buffer + MmFreePool(mem); +} + +// --------------------------------------------------------------------------- +// Phase Helper: Load and launch a process from the filesystem. +// Returns the new PROCESS* with one reference owned by the caller. +// Returns NULL on failure. +// --------------------------------------------------------------------------- +static PROCESS* LaunchUserProcess(const char* filePath, const char* args) { + KSTATUS s; + FILE_OBJECT* f; + + // Read the executable file into a raw buffer. + s = IoOpenFile(filePath, &f); + DbgPrint("LaunchUserProcess: IoOpenFile: %d\n", s); + if (s != STATUS_SUCCESS) { + DbgPrint("Failed to load file: \"%s\"!\n", filePath); + return 0; + } + + uint64_t size = IoGetFileSize(f); + DbgPrint("LaunchUserProcess: IoGetFileSize: %llu\n", size); + + void* mem = MmAllocateUserMemory(size); + DbgPrint("LaunchUserProcess: MmAllocateUserMemory: 0x%llX\n", mem); + if (!mem) { + IoCloseFile(f); + return 0; + } + + size_t got = 0; + s = IoReadFile(f, mem, size, &got); + DbgPrint("LaunchUserProcess: IoReadFile: %d\n", s); + IoCloseFile(f); + if (!KSUCCESS(s)) { + MmFreeUserMemory(mem, size); + return 0; + } + + // Create the process object. + PROCESS* proc; + THREAD* thread; + + s = KeCreateProcess(filePath, &proc); + if (!KSUCCESS(s)) { + DbgPrint("Failed to create process. System halted.\n"); + MmFreeUserMemory(mem, size); + return 0; + } + DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath); + + if (args && args[0]) + strncpy(proc->Cmdline, args, sizeof(proc->Cmdline) - 1); + else + strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1); + proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0'; + + // proc->Cmdline is kernel pool (ffffc000...) - not readable at CPL=3. + // Copy into user memory before handing the pointer to the initial thread. + size_t cmdlineLen = strlen(proc->Cmdline) + 1; + char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen); + if (!userCmdline) { + DbgPrint("LaunchUserProcess: Failed to allocate user cmdline buffer.\n"); + MmFreeUserMemory(mem, size); + ObDereferenceObject(&proc->Header); + return 0; + } + memcpy(userCmdline, proc->Cmdline, cmdlineLen); + proc->UserCmdlineBase = (uintptr_t)userCmdline; + proc->UserCmdlineSize = cmdlineLen; + + // Parse the raw PE file and map it into executable memory. + uintptr_t imageBase = 0; + s = LdrLoadImage(mem, size, &imageBase, true); + MmFreeUserMemory(mem, size); + if (!KSUCCESS(s)) { + DbgPrint("LaunchUserProcess: LdrLoadImage failed with status: %d\n", s); + ObDereferenceObject(&proc->Header); + return 0; + } + + s = LdrResolveImportsForProcess(proc, imageBase); + if (!KSUCCESS(s)) { + DbgPrint("LaunchUserProcess: Failed to resolve EXE imports for %s (status %d)\n", filePath, s); + MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); + ObDereferenceObject(&proc->Header); + return 0; + } + + uintptr_t entryPoint = LdrGetEntryPoint(imageBase); + DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint); + + // Create the main thread, pointing it to the PE Entry Point. + s = KeCreateThread(proc, (uintptr_t)entryPoint, (uintptr_t)userCmdline, PriorityNormal, &thread); + if (!KSUCCESS(s)) { + DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s); + MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); + ObDereferenceObject(&proc->Header); + return 0; + } + proc->ImageBase = imageBase; + proc->ImageSize = LdrGetImageSize(imageBase); + DbgPrint("LaunchUserProcess: Thread created and queued!\n"); + + // Caller owns the reference returned by KeCreateProcess. + return proc; +} +static void PrintProcessList() { + LIST_ENTRY* listHead = KeGetProcessList(); + LIST_ENTRY* current; + + DbgPrint("\nPID | Name | Threads | ExitCode\n"); + DbgPrint("---------------------------------------------\n"); + + for (current = listHead->Fwd; current != listHead; current = current->Fwd) { + // Translate the list entry back to the PROCESS structure + PROCESS* proc = CONTAINING_RECORD(current, PROCESS, GlobalLink); + + DbgPrint("%-5u | %-16s | %-7u | %d\n", + proc->ProcessId, + proc->Name, + proc->ThreadCount, + proc->ExitCode); + } +} + +void SystemInitThread(void* arg) { + SYSTEM_INIT_ARGS* args = (SYSTEM_INIT_ARGS*)arg; + + DbgPrint("SystemInitThread: Thread started. Igniting the system...\n"); + + InitializeDrivers(args->bootInfo, args->hal); + + ResolveSystemImports(args->bootInfo); + ReclaimBootloaderMemory(args->bootInfo); + + args->hal->Screen.Clear(0x000000); + + // Print memory status now that the system is fully alive + DbgPrint("Memory Status:\n"); + DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f)); + DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f)); + DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f)); + + // Launch User-Mode init.exe and restart it until it exits cleanly. + while (true) { + PROCESS* shellProc = LaunchUserProcess("system/init.exe", args->bootInfo->CommandLine); + if (!shellProc) { + DbgPrint("SystemInitThread: Failed to start system/init.exe; retrying.\n"); + KeYield(); + continue; + } + + KeWaitForEvent(shellProc->ExitEvent); + int exitCode = shellProc->ExitCode; + DbgPrint("SystemInitThread: init.exe exited with code %d.\n", exitCode); + ObDereferenceObject(&shellProc->Header); + + if (exitCode == 0) break; + DbgPrint("SystemInitThread: Restarting init.exe.\n"); + } + + DbgPrint("SystemInitThread: init.exe completed successfully; shutting down.\n"); + gKmainHal->Power.Shutdown(); + MmFreePool(args); +} + +// --------------------------------------------------------------------------- +// Main Kernel Entry +// --------------------------------------------------------------------------- +extern void KernelMain(BOOT_INFO* bootInfo) { + // + // Physical & Virtual Memory initialization + // + if (!MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase)) { + _disable(); while (true) __halt(); // there's nothing we can do + } + + PHYSICAL_ADDRESS pml4 = (PHYSICAL_ADDRESS)__readcr3(); + MmInitializeVirtual(pml4, 4 * 1024 * 1024 /* 4 MB initial pool */); + + // + // CPU Architecture setup (GDT/IDT/Faults) + // + KeInitializeGdt(); + KeInitializeIdt(); + + // + // Register boot modules (Allows LdrGetExport to work for drivers and the HAL) + // Note: Kept to a single call here to avoid the duplicate in your original code. + // + LdrRegisterModulesFromBootInfo(bootInfo); + + // + // HAL Initialization + // + HAL_INIT_PARAMS halParams = { + .PhysicalWindowBase = bootInfo->PhysicalMapBase, + .FramebufferBase = bootInfo->FramebufferBase, + .FramebufferWidth = bootInfo->FramebufferWidth, + .FramebufferHeight = bootInfo->FramebufferHeight, + .FramebufferStride = bootInfo->FramebufferStride, + .RsdpPhysical = bootInfo->RsdpPhysical, + .DebugEnabled = bootInfo->DebugEnabled + }; + + KSTATUS s = HalInitialize(&halParams); + if (!KSUCCESS(s)) { + _disable(); while (true) __halt(); // there's nothing we can do. + } + + const HAL_API* hal = HalGetApi(); + gKmainHal = hal; + + KeInitDebug(hal->Screen.PrintChar, bootInfo->DebugEnabled); // Enable debug + + KeIrqInitialize(hal); + + KeIrqRegister(hal->Timer.IrqNumber, hal->Timer.Isr); + KeIrqRegister(hal->Keyboard.IrqNumber, hal->Keyboard.Isr); + KeIrqRegister(hal->Mouse.IrqNumber, hal->Mouse.Isr); + + HalRegisterScheduler(KeSchedulerTick); + + KeSyscallSetHal(hal); + + // + // Dynamic Linking & Driver Execution + // + ResolveSystemImports(bootInfo); + //InitializeDrivers(bootInfo, hal); + + // + // Scheduler Prep & Status Update + // + KeInitializeScheduler(); + + // Pack the arguments for the init thread + SYSTEM_INIT_ARGS* initArgs = MmAllocatePool(sizeof(SYSTEM_INIT_ARGS)); + initArgs->bootInfo = bootInfo; + initArgs->hal = hal; + + // Spawn the Initialization Thread + THREAD* initThread = (THREAD*)0; + KSTATUS st = KeCreateKernelThread((uintptr_t)SystemInitThread, + (uintptr_t)initArgs, + PriorityNormal, &initThread); + + if (!KSUCCESS(st)) { + KePanic("KernelMain: Failed to create SystemInitThread (status 0x%x)\n", st); + } + + DbgPrint("KernelMain: Handing over to scheduler...\n"); + + // Ignite the system! + _enable(); + + // The Idle Loop: The CPU rests here whenever all other threads are waiting on events. + while (true) { + __halt(); + } +} diff --git a/code/kernel/mm_phys.c b/code/kernel/mm_phys.c index 74a1478..a46d4b1 100644 --- a/code/kernel/mm_phys.c +++ b/code/kernel/mm_phys.c @@ -1,177 +1,175 @@ -// mm_phys.c - physical memory manager -// -// The free-frame stack is a flat array of PHYSICAL_ADDRESS values carved out of -// the first suitable usable region before any allocation is served. All access -// to physical memory goes through the direct physical window established by the -// bootloader at PhysicalWindowBase (always 0xFFFF880000000000). -// -// Lock: a single spinlock guards Push/Pop. O(1) for both operations. -#include -#include - -// --------------------------------------------------------------------------- -// Physical-to-virtual translation via the direct map window. -// --------------------------------------------------------------------------- -static uint64_t gPhysBase = 0; // set by MmInitializePhysical - -static inline void* PhysToVirt(PHYSICAL_ADDRESS phys) { - return (void *)(phys + gPhysBase); -} - -// --------------------------------------------------------------------------- -// Frame stack -// --------------------------------------------------------------------------- - -typedef struct { - PHYSICAL_ADDRESS* Stack; // pointer into physical window - size_t Top; // index of next free slot (stack grows up) - size_t Capacity; - size_t Total; // total frames ever pushed - SPINLOCK Lock; -} FRAME_STACK; - -static FRAME_STACK gFrameStack; - -// Push a physical frame address onto the stack. -static void PushFrame(PHYSICAL_ADDRESS frame) { - uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); - if (gFrameStack.Top < gFrameStack.Capacity) { - gFrameStack.Stack[gFrameStack.Top++] = frame; - } - KeReleaseSpinlock(&gFrameStack.Lock, flags); -} - -// --------------------------------------------------------------------------- -// Initialisation -// --------------------------------------------------------------------------- -bool MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) { - gPhysBase = physicalWindowBase; - - // --- Phase 0: Count total usable frames so we can size the stack - // dynamically instead of capping at a hardcoded 1 GB. --- - uint64_t totalUsableBytes = 0; - for (uint32_t i = 0; i < map->RegionCount; i++) { - if (map->Regions[i].Type == MemoryUsable) - totalUsableBytes += map->Regions[i].Length; - } - const size_t maxFrames = (size_t)(totalUsableBytes / PAGE_SIZE); - const size_t stackBytes = maxFrames * sizeof(PHYSICAL_ADDRESS); - - // --- Phase 1: Find a usable region large enough to hold the stack. --- - PHYSICAL_ADDRESS stackPhys = 0; - for (uint32_t i = 0; i < map->RegionCount; i++) { - const MEMORY_REGION* r = &map->Regions[i]; - if (r->Type == MemoryUsable && r->Length >= stackBytes + PAGE_SIZE) { - stackPhys = r->Base; - break; - } - } - - if (!stackPhys) { - return false; - } - - gFrameStack.Stack = (PHYSICAL_ADDRESS *)PhysToVirt(stackPhys); - gFrameStack.Top = 0; - gFrameStack.Capacity = maxFrames; - gFrameStack.Total = 0; - gFrameStack.Lock = (SPINLOCK)SPINLOCK_INIT; - - // --- Phase 2: Push all free 4 KB frames, skipping the stack pages. --- - const PHYSICAL_ADDRESS stackEnd = - stackPhys + ((stackBytes + PAGE_SIZE - 1) & PAGE_MASK); - - for (uint32_t i = 0; i < map->RegionCount; i++) { - const MEMORY_REGION* r = &map->Regions[i]; - if (r->Type != MemoryUsable) continue; - - PHYSICAL_ADDRESS base = r->Base; - PHYSICAL_ADDRESS end = r->Base + r->Length; - - for (PHYSICAL_ADDRESS frame = base; frame < end; frame += PAGE_SIZE) { - if (frame >= stackPhys && frame < stackEnd) continue; - if (gFrameStack.Top < gFrameStack.Capacity) { - gFrameStack.Stack[gFrameStack.Top++] = frame; - gFrameStack.Total++; - } - } - } - - return true; -} - -// --------------------------------------------------------------------------- -// Public API -// --------------------------------------------------------------------------- -PHYSICAL_ADDRESS MmAllocateFrame(void) { - uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); - if (gFrameStack.Top == 0) { - KeReleaseSpinlock(&gFrameStack.Lock, flags); - return (PHYSICAL_ADDRESS)0; - } - PHYSICAL_ADDRESS frame = gFrameStack.Stack[--gFrameStack.Top]; - KeReleaseSpinlock(&gFrameStack.Lock, flags); - return frame; -} - -void MmFreeFrame(PHYSICAL_ADDRESS frame) { - if (!frame || (frame & (PAGE_SIZE - 1))) return; - uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); - if (gFrameStack.Top < gFrameStack.Capacity) - gFrameStack.Stack[gFrameStack.Top++] = frame; - // else: stack full - leak rather than corrupt - KeReleaseSpinlock(&gFrameStack.Lock, flags); -} - -__declspec(dllexport) PHYSICAL_ADDRESS MmAllocateContiguous(size_t count) { - if (!count) return (PHYSICAL_ADDRESS)0; - - uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); - - if (gFrameStack.Top < count) { - KeReleaseSpinlock(&gFrameStack.Lock, flags); - return (PHYSICAL_ADDRESS)0; - } - - // Frames are pushed in ascending address order by MmInitializePhysical, - // so physically consecutive pages sit at consecutive stack indices. - // Scan from bottom upward for a run of 'count' adjacent frames. - size_t run = (size_t)-1; - for (size_t i = 0; i + count <= gFrameStack.Top; i++) { - int ok = 1; - for (size_t j = 1; j < count; j++) { - if (gFrameStack.Stack[i + j] != gFrameStack.Stack[i] + j * PAGE_SIZE) { - ok = 0; - // Skip past this broken run; the next candidate can't start - // before the frame that broke the sequence. - i += j - 1; // outer loop adds 1 more - break; - } - } - if (ok) { run = i; break; } - } - - if (run == (size_t)-1) { - KeReleaseSpinlock(&gFrameStack.Lock, flags); - return (PHYSICAL_ADDRESS)0; - } - - PHYSICAL_ADDRESS base = gFrameStack.Stack[run]; - - // Splice out the 'count' entries by sliding the tail down. - size_t tail = gFrameStack.Top - run - count; - for (size_t i = 0; i < tail; i++) - gFrameStack.Stack[run + i] = gFrameStack.Stack[run + count + i]; - gFrameStack.Top -= count; - - KeReleaseSpinlock(&gFrameStack.Lock, flags); - return base; -} - -size_t MmFreeFrameCount(void) { - return gFrameStack.Top; -} - -size_t MmTotalFrameCount(void) { - return gFrameStack.Total; -} +// mm_phys.c - physical memory manager +// +// The free-frame stack is a flat array of PHYSICAL_ADDRESS values carved out of +// the first suitable usable region before any allocation is served. All access +// to physical memory goes through the direct physical window established by the +// bootloader at PhysicalWindowBase (always 0xFFFF880000000000). +// +// Lock: a single spinlock guards Push/Pop. O(1) for both operations. +#include +#include + +// --------------------------------------------------------------------------- +// Physical-to-virtual translation via the direct map window. +// --------------------------------------------------------------------------- +static uint64_t gPhysBase = 0; // set by MmInitializePhysical + +static inline void* PhysToVirt(PHYSICAL_ADDRESS phys) { + return (void *)(phys + gPhysBase); +} + +// --------------------------------------------------------------------------- +// Frame stack +// --------------------------------------------------------------------------- + +typedef struct { + PHYSICAL_ADDRESS* Stack; // pointer into physical window + size_t Top; // index of next free slot (stack grows up) + size_t Capacity; + size_t Total; // total frames ever pushed + SPINLOCK Lock; +} FRAME_STACK; + +static FRAME_STACK gFrameStack; + +// Push a physical frame address onto the stack. +static void PushFrame(PHYSICAL_ADDRESS frame) { + uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); + if (gFrameStack.Top < gFrameStack.Capacity) { + gFrameStack.Stack[gFrameStack.Top++] = frame; + gFrameStack.Total++; + } + KeReleaseSpinlock(&gFrameStack.Lock, flags); +} + +// --------------------------------------------------------------------------- +// Initialisation +// --------------------------------------------------------------------------- +bool MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) { + gPhysBase = physicalWindowBase; + + // --- Phase 0: Count total usable frames so we can size the stack + // dynamically instead of capping at a hardcoded 1 GB. --- + uint64_t totalUsableBytes = 0; + for (uint32_t i = 0; i < map->RegionCount; i++) { + if (map->Regions[i].Type == MemoryUsable) + totalUsableBytes += map->Regions[i].Length; + } + const size_t maxFrames = (size_t)(totalUsableBytes / PAGE_SIZE); + const size_t stackBytes = maxFrames * sizeof(PHYSICAL_ADDRESS); + + // --- Phase 1: Find a usable region large enough to hold the stack. --- + PHYSICAL_ADDRESS stackPhys = 0; + for (uint32_t i = 0; i < map->RegionCount; i++) { + const MEMORY_REGION* r = &map->Regions[i]; + if (r->Type == MemoryUsable && r->Length >= stackBytes + PAGE_SIZE) { + stackPhys = r->Base; + break; + } + } + + if (!stackPhys) { + return false; + } + + gFrameStack.Stack = (PHYSICAL_ADDRESS *)PhysToVirt(stackPhys); + gFrameStack.Top = 0; + gFrameStack.Capacity = maxFrames; + gFrameStack.Total = 0; + gFrameStack.Lock = (SPINLOCK)SPINLOCK_INIT; + + // --- Phase 2: Push all free 4 KB frames, skipping the stack pages. --- + const PHYSICAL_ADDRESS stackEnd = + stackPhys + ((stackBytes + PAGE_SIZE - 1) & PAGE_MASK); + + for (uint32_t i = 0; i < map->RegionCount; i++) { + const MEMORY_REGION* r = &map->Regions[i]; + if (r->Type != MemoryUsable) continue; + + PHYSICAL_ADDRESS base = r->Base; + PHYSICAL_ADDRESS end = r->Base + r->Length; + + for (PHYSICAL_ADDRESS frame = base; frame < end; frame += PAGE_SIZE) { + if (frame >= stackPhys && frame < stackEnd) continue; + PushFrame(frame); + } + } + + return true; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- +PHYSICAL_ADDRESS MmAllocateFrame(void) { + uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); + if (gFrameStack.Top == 0) { + KeReleaseSpinlock(&gFrameStack.Lock, flags); + return (PHYSICAL_ADDRESS)0; + } + PHYSICAL_ADDRESS frame = gFrameStack.Stack[--gFrameStack.Top]; + KeReleaseSpinlock(&gFrameStack.Lock, flags); + return frame; +} + +void MmFreeFrame(PHYSICAL_ADDRESS frame) { + if (!frame || (frame & (PAGE_SIZE - 1))) return; + uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); + if (gFrameStack.Top < gFrameStack.Capacity) + gFrameStack.Stack[gFrameStack.Top++] = frame; + // else: stack full - leak rather than corrupt + KeReleaseSpinlock(&gFrameStack.Lock, flags); +} + +__declspec(dllexport) PHYSICAL_ADDRESS MmAllocateContiguous(size_t count) { + if (!count) return (PHYSICAL_ADDRESS)0; + + uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock); + + if (gFrameStack.Top < count) { + KeReleaseSpinlock(&gFrameStack.Lock, flags); + return (PHYSICAL_ADDRESS)0; + } + + // Frames are pushed in ascending address order by MmInitializePhysical, + // so physically consecutive pages sit at consecutive stack indices. + // Scan from bottom upward for a run of 'count' adjacent frames. + size_t run = (size_t)-1; + for (size_t i = 0; i + count <= gFrameStack.Top; i++) { + int ok = 1; + for (size_t j = 1; j < count; j++) { + if (gFrameStack.Stack[i + j] != gFrameStack.Stack[i] + j * PAGE_SIZE) { + ok = 0; + // Skip past this broken run; the next candidate can't start + // before the frame that broke the sequence. + i += j - 1; // outer loop adds 1 more + break; + } + } + if (ok) { run = i; break; } + } + + if (run == (size_t)-1) { + KeReleaseSpinlock(&gFrameStack.Lock, flags); + return (PHYSICAL_ADDRESS)0; + } + + PHYSICAL_ADDRESS base = gFrameStack.Stack[run]; + + // Splice out the 'count' entries by sliding the tail down. + size_t tail = gFrameStack.Top - run - count; + for (size_t i = 0; i < tail; i++) + gFrameStack.Stack[run + i] = gFrameStack.Stack[run + count + i]; + gFrameStack.Top -= count; + + KeReleaseSpinlock(&gFrameStack.Lock, flags); + return base; +} + +size_t MmFreeFrameCount(void) { + return gFrameStack.Top; +} + +size_t MmTotalFrameCount(void) { + return gFrameStack.Total; +} diff --git a/code/kernel/mm_virt.c b/code/kernel/mm_virt.c index f0c43fb..ee42212 100644 --- a/code/kernel/mm_virt.c +++ b/code/kernel/mm_virt.c @@ -1,455 +1,488 @@ -// mm_virt.c - virtual memory manager -// -// Page tables: -// One PML4 shared by all processes. -// Upper half (bit 47 set) has U/S = 0 - ring-3 access always faults. -// Lower half pages are PAGE_USER. -// -// Kernel pool: -// A boundary-tag (header + footer) free-list allocator starting at POOL_BASE. -// Pages are committed on demand from the PMM. -// -// User heap: -// A single best-fit free list in the lower half. -// Backed on demand from the PMM. No per-process heaps. -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// Helpers - physical window access -// --------------------------------------------------------------------------- -static uint64_t gPhysBase = PHYS_MAP_BASE; - -static inline void* PhysToVirt(PHYSICAL_ADDRESS p) { - return (void *)(p + gPhysBase); -} -static inline uint64_t* PtePtr(PHYSICAL_ADDRESS tablePhys, unsigned idx) { - return (uint64_t *)PhysToVirt(tablePhys) + idx; -} - -// --------------------------------------------------------------------------- -// PML4 / page-table helpers -// --------------------------------------------------------------------------- -static PHYSICAL_ADDRESS gPml4Phys = 0; - -// Extract the page-table index for each level from a virtual address. -#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) - -// Ensure a page-table entry at 'parentPhys[index]' points to a child table. -// Allocates and zeros a new frame if the entry is not present. -// Returns the physical address of the child table, or 0 on OOM. -static PHYSICAL_ADDRESS EnsureTable(PHYSICAL_ADDRESS parentPhys, - unsigned index, uint64_t extraFlags) { - uint64_t* entry = PtePtr(parentPhys, index); - if (*entry & PAGE_PRESENT) { - *entry |= extraFlags; - return* entry & ~0xFFFULL; - } - PHYSICAL_ADDRESS childPhys = MmAllocateFrame(); - if (!childPhys) return 0; - memset(PhysToVirt(childPhys), 0, PAGE_SIZE); - *entry = childPhys | PAGE_PRESENT | PAGE_WRITE | extraFlags; - return childPhys; -} - -// --------------------------------------------------------------------------- -// Public mapping -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS MmMapPage(uintptr_t virt, PHYSICAL_ADDRESS phys, uint64_t flags) { - unsigned userFlag = (flags & PAGE_USER) ? PAGE_USER : 0; - - PHYSICAL_ADDRESS pdpt = EnsureTable(gPml4Phys, PML4_IDX(virt), userFlag); - if (!pdpt) return STATUS_NO_MEMORY; - PHYSICAL_ADDRESS pdt = EnsureTable(pdpt, PDPT_IDX(virt), userFlag); - if (!pdt) return STATUS_NO_MEMORY; - PHYSICAL_ADDRESS pt = EnsureTable(pdt, PDT_IDX(virt), userFlag); - if (!pt) return STATUS_NO_MEMORY; - - uint64_t* pte = PtePtr(pt, PT_IDX(virt)); - *pte = phys | flags; - __invlpg((void *)virt); - return STATUS_SUCCESS; -} - -__declspec(dllexport) KSTATUS MmUnmapPage(uintptr_t virt) { - uint64_t* e4 = PtePtr(gPml4Phys, PML4_IDX(virt)); - if (!(*e4 & PAGE_PRESENT)) return STATUS_NOT_FOUND; - uint64_t* e3 = PtePtr(*e4 & ~0xFFFULL, PDPT_IDX(virt)); - if (!(*e3 & PAGE_PRESENT)) return STATUS_NOT_FOUND; - uint64_t* e2 = PtePtr(*e3 & ~0xFFFULL, PDT_IDX(virt)); - if (!(*e2 & PAGE_PRESENT)) return STATUS_NOT_FOUND; - uint64_t* pte = PtePtr(*e2 & ~0xFFFULL, PT_IDX(virt)); - *pte = 0; - __invlpg((void *)virt); - return STATUS_SUCCESS; -} - -__declspec(dllexport) KSTATUS MmSetPageProtect(uintptr_t virt, size_t size, uint64_t flags) { - if (!size) return STATUS_SUCCESS; - - // Round down to page boundary, round size up. - uintptr_t base = virt & PAGE_MASK; - uintptr_t end = (virt + size + PAGE_SIZE - 1) & PAGE_MASK; - - for (uintptr_t va = base; va < end; va += PAGE_SIZE) { - uint64_t* e4 = PtePtr(gPml4Phys, PML4_IDX(va)); - if (!(*e4 & PAGE_PRESENT)) return STATUS_NOT_FOUND; - uint64_t* e3 = PtePtr(*e4 & ~0xFFFULL, PDPT_IDX(va)); - if (!(*e3 & PAGE_PRESENT)) return STATUS_NOT_FOUND; - uint64_t* e2 = PtePtr(*e3 & ~0xFFFULL, PDT_IDX(va)); - if (!(*e2 & PAGE_PRESENT)) return STATUS_NOT_FOUND; - uint64_t* pte = PtePtr(*e2 & ~0xFFFULL, PT_IDX(va)); - if (!(*pte & PAGE_PRESENT)) return STATUS_NOT_FOUND; - - // Preserve the physical address, replace everything else. - PHYSICAL_ADDRESS phys = *pte & ~0xFFFULL & ~PAGE_NX; - *pte = phys | flags; - __invlpg((void *)va); - } - - return STATUS_SUCCESS; -} - -__declspec(dllexport) PHYSICAL_ADDRESS MmGetPhysicalAddress(uintptr_t virt) { - if (!gPml4Phys) return 0; // Not initialized - - uint64_t pml4_idx = (virt >> 39) & 0x1FF; - uint64_t pdpt_idx = (virt >> 30) & 0x1FF; - uint64_t pd_idx = (virt >> 21) & 0x1FF; - uint64_t pt_idx = (virt >> 12) & 0x1FF; - - uint64_t* pml4 = PhysToVirt(gPml4Phys); - if (!(pml4[pml4_idx] & PAGE_PRESENT)) return 0; - - uint64_t* pdpt = PhysToVirt(pml4[pml4_idx] & ~0xFFFULL); - if (!(pdpt[pdpt_idx] & PAGE_PRESENT)) return 0; - - // Check for 1GB huge pages (bit 7 - PS) - if (pdpt[pdpt_idx] & 0x80) { - return (pdpt[pdpt_idx] & 0x000FFFFFC0000000ULL) + (virt & 0x3FFFFFFF); - } - - uint64_t* pd = PhysToVirt(pdpt[pdpt_idx] & ~0xFFFULL); - if (!(pd[pd_idx] & PAGE_PRESENT)) return 0; - - // Check for 2MB huge pages (bit 7 - PS) - if (pd[pd_idx] & 0x80) { - return (pd[pd_idx] & 0x000FFFFFFFE00000ULL) + (virt & 0x1FFFFF); - } - - uint64_t* pt = PhysToVirt(pd[pd_idx] & ~0xFFFULL); - if (!(pt[pt_idx] & PAGE_PRESENT)) return 0; - - // Extract the physical frame and add the offset within the page - return (pt[pt_idx] & ~0xFFFULL) + (virt & 0xFFF); -} - -// --------------------------------------------------------------------------- -// Boundary-tag allocator (used for both pool and user heap) -// --------------------------------------------------------------------------- -// Each allocation has a BLOCK_HDR immediately before the user data, and a -// BLOCK_FTR immediately after. Both carry the block size and a 'free' flag. -// This allows O(1) coalescing of adjacent free blocks. - -#define BLOCK_MAGIC 0xB10CB10CUL - -typedef struct BLOCK_HDR { - uint32_t Magic; - uint32_t Free; // 1 = free, 0 = used - size_t Size; // usable bytes (excludes header + footer) - struct BLOCK_HDR* PrevFree; - struct BLOCK_HDR* NextFree; -} BLOCK_HDR; - -typedef struct BLOCK_FTR { - size_t Size; - uint32_t Free; - uint32_t Magic; -} BLOCK_FTR; - -#define HDR_SIZE (sizeof(BLOCK_HDR)) -#define FTR_SIZE (sizeof(BLOCK_FTR)) -#define OVERHEAD (HDR_SIZE + FTR_SIZE) -#define MIN_BLOCK (OVERHEAD + sizeof(void*)) - -static inline BLOCK_FTR* HdrToFtr(BLOCK_HDR* h) { - return (BLOCK_FTR *)((char *)h + HDR_SIZE + h->Size); -} -static inline BLOCK_HDR* FtrToHdr(BLOCK_FTR* f) { - return (BLOCK_HDR *)((char *)f - HDR_SIZE - f->Size); -} -static inline BLOCK_HDR* NextBlock(BLOCK_HDR* h) { - return (BLOCK_HDR *)((char *)h + HDR_SIZE + h->Size + FTR_SIZE); -} -static inline BLOCK_FTR* PrevFtr(BLOCK_HDR* h) { - return (BLOCK_FTR *)((char *)h - FTR_SIZE); -} - -typedef struct HEAP { - BLOCK_HDR* FreeList; // doubly-linked list of free blocks - uintptr_t Start; // virtual base of the arena - uintptr_t Brk; // current committed end (page-aligned) - uintptr_t Limit; // hard upper bound - uint64_t MapFlags; // PAGE_* flags for new committed pages - SPINLOCK Lock; -} HEAP; - -// Extend heap by at least 'need' bytes (rounded up to page boundary). -static int HeapGrow(HEAP* h, size_t need) { - size_t grow = (need + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); - if (h->Brk + grow > h->Limit) return 0; - - for (uintptr_t va = h->Brk; va < h->Brk + grow; va += PAGE_SIZE) { - PHYSICAL_ADDRESS frame = MmAllocateFrame(); - if (!frame) return 0; - if (!KSUCCESS(MmMapPage(va, frame, h->MapFlags))) { - MmFreeFrame(frame); - return 0; - } - memset((void *)va, 0, PAGE_SIZE); - } - - // Make the new pages a single free block. - BLOCK_HDR* blk = (BLOCK_HDR *)h->Brk; - blk->Magic = BLOCK_MAGIC; - blk->Free = 1; - blk->Size = grow - OVERHEAD; - blk->PrevFree = (BLOCK_HDR *)0; - blk->NextFree = h->FreeList; - if (h->FreeList) h->FreeList->PrevFree = blk; - h->FreeList = blk; - HdrToFtr(blk)->Magic = BLOCK_MAGIC; - HdrToFtr(blk)->Free = 1; - HdrToFtr(blk)->Size = blk->Size; - - h->Brk += grow; - return 1; -} - -// Unlink a free block from the free list. -static void FreeListRemove(HEAP* h, BLOCK_HDR* blk) { - if (blk->PrevFree) blk->PrevFree->NextFree = blk->NextFree; - else h->FreeList = blk->NextFree; - if (blk->NextFree) blk->NextFree->PrevFree = blk->PrevFree; - blk->PrevFree = blk->NextFree = (BLOCK_HDR *)0; -} - -static void* HeapAlloc(HEAP* h, size_t size) { - if (!size) return (void *)0; - size = (size + 7) & ~7ULL; // 8-byte align - - uintptr_t flags = KeAcquireSpinlock(&h->Lock); - - // Best-fit search. - BLOCK_HDR* best = (BLOCK_HDR *)0; - for (BLOCK_HDR* cur = h->FreeList; cur; cur = cur->NextFree) { - if (cur->Size >= size) { - if (!best || cur->Size < best->Size) best = cur; - if (best->Size == size) break; - } - } - - if (!best) { - if (!HeapGrow(h, size + OVERHEAD)) { - KeReleaseSpinlock(&h->Lock, flags); - return (void *)0; - } - // Re-search; HeapGrow inserted at the front. - best = h->FreeList; - while (best && best->Size < size) best = best->NextFree; - if (!best) { - KeReleaseSpinlock(&h->Lock, flags); - return (void *)0; - } - } - - FreeListRemove(h, best); - - // Split the block if the remainder is big enough. - if (best->Size >= size + OVERHEAD + sizeof(void*)) { - BLOCK_HDR* rem = (BLOCK_HDR *)((char *)best + HDR_SIZE + size + FTR_SIZE); - rem->Magic = BLOCK_MAGIC; - rem->Free = 1; - rem->Size = best->Size - size - OVERHEAD; - rem->PrevFree = (BLOCK_HDR *)0; - rem->NextFree = h->FreeList; - if (h->FreeList) h->FreeList->PrevFree = rem; - h->FreeList = rem; - HdrToFtr(rem)->Magic = BLOCK_MAGIC; - HdrToFtr(rem)->Free = 1; - HdrToFtr(rem)->Size = rem->Size; - - best->Size = size; - } - - best->Free = 0; - HdrToFtr(best)->Free = 0; - HdrToFtr(best)->Magic = BLOCK_MAGIC; - HdrToFtr(best)->Size = best->Size; - - KeReleaseSpinlock(&h->Lock, flags); - return (char *)best + HDR_SIZE; -} - -static void HeapFree(HEAP* h, void* ptr) { - if (!ptr) return; - BLOCK_HDR* blk = (BLOCK_HDR *)((char *)ptr - HDR_SIZE); - if (blk->Magic != BLOCK_MAGIC || blk->Free) return; // double-free / corrupt - - uintptr_t flags = KeAcquireSpinlock(&h->Lock); - - blk->Free = 1; - - // Coalesce with next block if it is free. - BLOCK_HDR* next = NextBlock(blk); - if ((uintptr_t)next < h->Brk && next->Magic == BLOCK_MAGIC && next->Free) { - FreeListRemove(h, next); - blk->Size += OVERHEAD + next->Size; - HdrToFtr(blk)->Size = blk->Size; - HdrToFtr(blk)->Magic = BLOCK_MAGIC; - HdrToFtr(blk)->Free = 1; - } - - // Coalesce with previous block if it is free. - if ((uintptr_t)blk > h->Start) { - BLOCK_FTR* prevFtr = PrevFtr(blk); - if (prevFtr->Magic == BLOCK_MAGIC && prevFtr->Free) { - BLOCK_HDR* prev = FtrToHdr(prevFtr); - FreeListRemove(h, prev); - prev->Size += OVERHEAD + blk->Size; - HdrToFtr(prev)->Size = prev->Size; - HdrToFtr(prev)->Magic = BLOCK_MAGIC; - HdrToFtr(prev)->Free = 1; - blk = prev; - } - } - - // Insert at head of free list. - blk->PrevFree = (BLOCK_HDR *)0; - blk->NextFree = h->FreeList; - if (h->FreeList) h->FreeList->PrevFree = blk; - h->FreeList = blk; - - HdrToFtr(blk)->Free = 1; - HdrToFtr(blk)->Magic = BLOCK_MAGIC; - - KeReleaseSpinlock(&h->Lock, flags); -} - -static void* HeapRealloc(HEAP* h, void* ptr, size_t new_size) { - // If ptr is NULL, realloc acts like malloc - if (!ptr) { - return HeapAlloc(h, new_size); - } - - // If size is 0, realloc acts like free - if (new_size == 0) { - HeapFree(h, ptr); - return (void*)0; - } - - // Retrieve the header of the existing block - BLOCK_HDR* blk = (BLOCK_HDR *)((char *)ptr - HDR_SIZE); - - // Sanity checks to prevent operating on corrupted or already-freed pointers - if (blk->Magic != BLOCK_MAGIC || blk->Free) { - return (void*)0; - } - - // Align the requested size (matches HeapAlloc 8-byte alignment) - size_t aligned_size = (new_size + 7) & ~7ULL; - - // If the current block is already big enough, we can reuse it in-place. - // (Note: To save space, you could optionally split the block here if - // the new size is significantly smaller, similar to HeapAlloc's logic). - if (blk->Size >= aligned_size) { - return ptr; - } - - // The block is too small. Allocate a new, larger block. - void* new_ptr = HeapAlloc(h, aligned_size); - if (!new_ptr) { - return (void*)0; // Out of memory, original block is left untouched - } - - // Copy the old data to the new block. - // We only copy up to the old block's usable size. - memcpy(new_ptr, ptr, blk->Size); - - // Free the old block - HeapFree(h, ptr); - - return new_ptr; -} - -// --------------------------------------------------------------------------- -// Kernel pool -// --------------------------------------------------------------------------- -static HEAP gPoolHeap; - -// --------------------------------------------------------------------------- -// User heap -// --------------------------------------------------------------------------- -static HEAP gUserHeap; - -// --------------------------------------------------------------------------- -// Module init -// --------------------------------------------------------------------------- -void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize) { - gPml4Phys = bootPml4Phys; - - // Kernel pool heap. - gPoolHeap.FreeList = (BLOCK_HDR *)0; - gPoolHeap.Start = POOL_BASE; - gPoolHeap.Brk = POOL_BASE; - gPoolHeap.Limit = POOL_LIMIT; - gPoolHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_NX; - gPoolHeap.Lock = (SPINLOCK)SPINLOCK_INIT; - - // Pre-commit the requested pool size. - if (poolSize) { - poolSize = (poolSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); - HeapGrow(&gPoolHeap, poolSize); - } - - // User heap. - gUserHeap.FreeList = (BLOCK_HDR *)0; - gUserHeap.Start = USER_BASE; - gUserHeap.Brk = USER_BASE; - gUserHeap.Limit = USER_LIMIT; - gUserHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_USER; - gUserHeap.Lock = (SPINLOCK)SPINLOCK_INIT; -} - -void MmActivate(void) { - __writecr3(gPml4Phys); -} - -// --------------------------------------------------------------------------- -// Public pool API -// --------------------------------------------------------------------------- -__declspec(dllexport) void* MmAllocatePool(size_t size) { return HeapAlloc(&gPoolHeap, size); } -__declspec(dllexport) void MmFreePool(void* ptr) { HeapFree(&gPoolHeap, ptr); } -__declspec(dllexport) void* MmReallocatePool(void* ptr, size_t newSize) { return HeapRealloc(&gPoolHeap, ptr, newSize); } - -// --------------------------------------------------------------------------- -// Public user heap API -// --------------------------------------------------------------------------- -__declspec(dllexport) void* MmAllocateUserMemory(size_t size) { return HeapAlloc(&gUserHeap, size); } -__declspec(dllexport) void MmFreeUserMemory(void* ptr, size_t size) { (void)size; HeapFree(&gUserHeap, ptr); } -__declspec(dllexport) void* MmReallocateUserMemory(void* ptr, size_t newSize) { return HeapRealloc(&gUserHeap, ptr, newSize); } - -size_t MmGetKernelPoolUsage(void) { - // Returns bytes committed to the kernel pool - return (size_t)(gPoolHeap.Brk - gPoolHeap.Start); -} - -size_t MmGetUserHeapUsage(void) { - // Returns bytes committed to the user heap - return (size_t)(gUserHeap.Brk - gUserHeap.Start); -} +// mm_virt.c - virtual memory manager +// +// Page tables: +// One PML4 shared by all processes. +// Upper half (bit 47 set) has U/S = 0 - ring-3 access always faults. +// Lower half pages are PAGE_USER. +// +// Kernel pool: +// A boundary-tag (header + footer) free-list allocator starting at POOL_BASE. +// Pages are committed on demand from the PMM. +// +// User heap: +// A single best-fit free list in the lower half. +// Backed on demand from the PMM. No per-process heaps. +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Helpers - physical window access +// --------------------------------------------------------------------------- +static uint64_t gPhysBase = PHYS_MAP_BASE; + +static inline void* PhysToVirt(PHYSICAL_ADDRESS p) { + return (void *)(p + gPhysBase); +} +static inline uint64_t* PtePtr(PHYSICAL_ADDRESS tablePhys, unsigned idx) { + return (uint64_t *)PhysToVirt(tablePhys) + idx; +} + +// --------------------------------------------------------------------------- +// PML4 / page-table helpers +// --------------------------------------------------------------------------- +static PHYSICAL_ADDRESS gPml4Phys = 0; + +// Extract the page-table index for each level from a virtual address. +#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) + +// Ensure a page-table entry at 'parentPhys[index]' points to a child table. +// Allocates and zeros a new frame if the entry is not present. +// Returns the physical address of the child table, or 0 on OOM. +static PHYSICAL_ADDRESS EnsureTable(PHYSICAL_ADDRESS parentPhys, + unsigned index, uint64_t extraFlags) { + uint64_t* entry = PtePtr(parentPhys, index); + if (*entry & PAGE_PRESENT) { + *entry |= extraFlags; + return* entry & ~0xFFFULL; + } + PHYSICAL_ADDRESS childPhys = MmAllocateFrame(); + if (!childPhys) return 0; + memset(PhysToVirt(childPhys), 0, PAGE_SIZE); + *entry = childPhys | PAGE_PRESENT | PAGE_WRITE | extraFlags; + return childPhys; +} + +// --------------------------------------------------------------------------- +// Public mapping +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS MmMapPage(uintptr_t virt, PHYSICAL_ADDRESS phys, uint64_t flags) { + unsigned userFlag = (flags & PAGE_USER) ? PAGE_USER : 0; + + PHYSICAL_ADDRESS pdpt = EnsureTable(gPml4Phys, PML4_IDX(virt), userFlag); + if (!pdpt) return STATUS_NO_MEMORY; + PHYSICAL_ADDRESS pdt = EnsureTable(pdpt, PDPT_IDX(virt), userFlag); + if (!pdt) return STATUS_NO_MEMORY; + PHYSICAL_ADDRESS pt = EnsureTable(pdt, PDT_IDX(virt), userFlag); + if (!pt) return STATUS_NO_MEMORY; + + uint64_t* pte = PtePtr(pt, PT_IDX(virt)); + *pte = phys | flags; + __invlpg((void *)virt); + return STATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS MmUnmapPage(uintptr_t virt) { + uint64_t* e4 = PtePtr(gPml4Phys, PML4_IDX(virt)); + if (!(*e4 & PAGE_PRESENT)) return STATUS_NOT_FOUND; + uint64_t* e3 = PtePtr(*e4 & ~0xFFFULL, PDPT_IDX(virt)); + if (!(*e3 & PAGE_PRESENT)) return STATUS_NOT_FOUND; + uint64_t* e2 = PtePtr(*e3 & ~0xFFFULL, PDT_IDX(virt)); + if (!(*e2 & PAGE_PRESENT)) return STATUS_NOT_FOUND; + uint64_t* pte = PtePtr(*e2 & ~0xFFFULL, PT_IDX(virt)); + *pte = 0; + __invlpg((void *)virt); + return STATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS MmSetPageProtect(uintptr_t virt, size_t size, uint64_t flags) { + if (!size) return STATUS_SUCCESS; + + // Round down to page boundary, round size up. + uintptr_t base = virt & PAGE_MASK; + uintptr_t end = (virt + size + PAGE_SIZE - 1) & PAGE_MASK; + + for (uintptr_t va = base; va < end; va += PAGE_SIZE) { + uint64_t* e4 = PtePtr(gPml4Phys, PML4_IDX(va)); + if (!(*e4 & PAGE_PRESENT)) return STATUS_NOT_FOUND; + uint64_t* e3 = PtePtr(*e4 & ~0xFFFULL, PDPT_IDX(va)); + if (!(*e3 & PAGE_PRESENT)) return STATUS_NOT_FOUND; + uint64_t* e2 = PtePtr(*e3 & ~0xFFFULL, PDT_IDX(va)); + if (!(*e2 & PAGE_PRESENT)) return STATUS_NOT_FOUND; + uint64_t* pte = PtePtr(*e2 & ~0xFFFULL, PT_IDX(va)); + if (!(*pte & PAGE_PRESENT)) return STATUS_NOT_FOUND; + + // Preserve the physical address, replace everything else. + PHYSICAL_ADDRESS phys = *pte & ~0xFFFULL & ~PAGE_NX; + *pte = phys | flags; + __invlpg((void *)va); + } + + return STATUS_SUCCESS; +} + +__declspec(dllexport) PHYSICAL_ADDRESS MmGetPhysicalAddress(uintptr_t virt) { + if (!gPml4Phys) return 0; // Not initialized + + uint64_t pml4_idx = (virt >> 39) & 0x1FF; + uint64_t pdpt_idx = (virt >> 30) & 0x1FF; + uint64_t pd_idx = (virt >> 21) & 0x1FF; + uint64_t pt_idx = (virt >> 12) & 0x1FF; + + uint64_t* pml4 = PhysToVirt(gPml4Phys); + if (!(pml4[pml4_idx] & PAGE_PRESENT)) return 0; + + uint64_t* pdpt = PhysToVirt(pml4[pml4_idx] & ~0xFFFULL); + if (!(pdpt[pdpt_idx] & PAGE_PRESENT)) return 0; + + // Check for 1GB huge pages (bit 7 - PS) + if (pdpt[pdpt_idx] & 0x80) { + return (pdpt[pdpt_idx] & 0x000FFFFFC0000000ULL) + (virt & 0x3FFFFFFF); + } + + uint64_t* pd = PhysToVirt(pdpt[pdpt_idx] & ~0xFFFULL); + if (!(pd[pd_idx] & PAGE_PRESENT)) return 0; + + // Check for 2MB huge pages (bit 7 - PS) + if (pd[pd_idx] & 0x80) { + return (pd[pd_idx] & 0x000FFFFFFFE00000ULL) + (virt & 0x1FFFFF); + } + + uint64_t* pt = PhysToVirt(pd[pd_idx] & ~0xFFFULL); + if (!(pt[pt_idx] & PAGE_PRESENT)) return 0; + + // Extract the physical frame and add the offset within the page + return (pt[pt_idx] & ~0xFFFULL) + (virt & 0xFFF); +} + +// --------------------------------------------------------------------------- +// Boundary-tag allocator (used for both pool and user heap) +// --------------------------------------------------------------------------- +// Each allocation has a BLOCK_HDR immediately before the user data, and a +// BLOCK_FTR immediately after. Both carry the block size and a 'free' flag. +// This allows O(1) coalescing of adjacent free blocks. + +#define BLOCK_MAGIC 0xB10CB10CUL + +typedef struct BLOCK_HDR { + uint32_t Magic; + uint32_t Free; // 1 = free, 0 = used + size_t Size; // usable bytes (excludes header + footer) + struct BLOCK_HDR* PrevFree; + struct BLOCK_HDR* NextFree; +} BLOCK_HDR; + +typedef struct BLOCK_FTR { + size_t Size; + uint32_t Free; + uint32_t Magic; +} BLOCK_FTR; + +#define HDR_SIZE (sizeof(BLOCK_HDR)) +#define FTR_SIZE (sizeof(BLOCK_FTR)) +#define OVERHEAD (HDR_SIZE + FTR_SIZE) +#define MIN_BLOCK (OVERHEAD + sizeof(void*)) + +static inline BLOCK_FTR* HdrToFtr(BLOCK_HDR* h) { + return (BLOCK_FTR *)((char *)h + HDR_SIZE + h->Size); +} +static inline BLOCK_HDR* FtrToHdr(BLOCK_FTR* f) { + return (BLOCK_HDR *)((char *)f - HDR_SIZE - f->Size); +} +static inline BLOCK_HDR* NextBlock(BLOCK_HDR* h) { + return (BLOCK_HDR *)((char *)h + HDR_SIZE + h->Size + FTR_SIZE); +} +static inline BLOCK_FTR* PrevFtr(BLOCK_HDR* h) { + return (BLOCK_FTR *)((char *)h - FTR_SIZE); +} + +typedef struct HEAP { + BLOCK_HDR* FreeList; // doubly-linked list of free blocks + uintptr_t Start; // virtual base of the arena + uintptr_t Brk; // current committed end (page-aligned) + uintptr_t Limit; // hard upper bound + uint64_t MapFlags; // PAGE_* flags for new committed pages + SPINLOCK Lock; +} HEAP; + +// Extend heap by at least 'need' bytes (rounded up to page boundary). +static int HeapGrow(HEAP* h, size_t need) { + size_t grow = (need + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + if (h->Brk + grow > h->Limit) return 0; + + for (uintptr_t va = h->Brk; va < h->Brk + grow; va += PAGE_SIZE) { + PHYSICAL_ADDRESS frame = MmAllocateFrame(); + if (!frame) return 0; + if (!KSUCCESS(MmMapPage(va, frame, h->MapFlags))) { + MmFreeFrame(frame); + return 0; + } + memset((void *)va, 0, PAGE_SIZE); + } + + // Make the new pages a single free block. + BLOCK_HDR* blk = (BLOCK_HDR *)h->Brk; + blk->Magic = BLOCK_MAGIC; + blk->Free = 1; + blk->Size = grow - OVERHEAD; + blk->PrevFree = (BLOCK_HDR *)0; + blk->NextFree = h->FreeList; + if (h->FreeList) h->FreeList->PrevFree = blk; + h->FreeList = blk; + HdrToFtr(blk)->Magic = BLOCK_MAGIC; + HdrToFtr(blk)->Free = 1; + HdrToFtr(blk)->Size = blk->Size; + + h->Brk += grow; + return 1; +} + +// Unlink a free block from the free list. +static void FreeListRemove(HEAP* h, BLOCK_HDR* blk) { + if (blk->PrevFree) blk->PrevFree->NextFree = blk->NextFree; + else h->FreeList = blk->NextFree; + if (blk->NextFree) blk->NextFree->PrevFree = blk->PrevFree; + blk->PrevFree = blk->NextFree = (BLOCK_HDR *)0; +} + +static void HeapTryDecommitTail(HEAP* h, BLOCK_HDR* blk) { + if (!blk || blk->Free == 0) return; + + while (h->Brk > h->Start) { + uintptr_t blockEnd = (uintptr_t)blk + HDR_SIZE + blk->Size + FTR_SIZE; + if (blockEnd != h->Brk) break; + + // Keep at least one page for the block header/footer and a small payload. + if (blk->Size < PAGE_SIZE + sizeof(void*)) break; + + uintptr_t pageStart = h->Brk - PAGE_SIZE; + if (pageStart <= (uintptr_t)blk) break; // would unmap the page containing the header + + PHYSICAL_ADDRESS phys = MmGetPhysicalAddress(pageStart); + if (!phys) break; + if (!KSUCCESS(MmUnmapPage(pageStart))) { + break; + } + MmFreeFrame(phys); + + h->Brk -= PAGE_SIZE; + blk->Size -= PAGE_SIZE; + } + + if (blk->Free) { + HdrToFtr(blk)->Size = blk->Size; + HdrToFtr(blk)->Magic = BLOCK_MAGIC; + HdrToFtr(blk)->Free = 1; + } +} + +static void* HeapAlloc(HEAP* h, size_t size) { + if (!size) return (void *)0; + size = (size + 7) & ~7ULL; // 8-byte align + + uintptr_t flags = KeAcquireSpinlock(&h->Lock); + + // Best-fit search. + BLOCK_HDR* best = (BLOCK_HDR *)0; + for (BLOCK_HDR* cur = h->FreeList; cur; cur = cur->NextFree) { + if (cur->Size >= size) { + if (!best || cur->Size < best->Size) best = cur; + if (best->Size == size) break; + } + } + + if (!best) { + if (!HeapGrow(h, size + OVERHEAD)) { + KeReleaseSpinlock(&h->Lock, flags); + return (void *)0; + } + // Re-search; HeapGrow inserted at the front. + best = h->FreeList; + while (best && best->Size < size) best = best->NextFree; + if (!best) { + KeReleaseSpinlock(&h->Lock, flags); + return (void *)0; + } + } + + FreeListRemove(h, best); + + // Split the block if the remainder is big enough. + if (best->Size >= size + OVERHEAD + sizeof(void*)) { + BLOCK_HDR* rem = (BLOCK_HDR *)((char *)best + HDR_SIZE + size + FTR_SIZE); + rem->Magic = BLOCK_MAGIC; + rem->Free = 1; + rem->Size = best->Size - size - OVERHEAD; + rem->PrevFree = (BLOCK_HDR *)0; + rem->NextFree = h->FreeList; + if (h->FreeList) h->FreeList->PrevFree = rem; + h->FreeList = rem; + HdrToFtr(rem)->Magic = BLOCK_MAGIC; + HdrToFtr(rem)->Free = 1; + HdrToFtr(rem)->Size = rem->Size; + + best->Size = size; + } + + best->Free = 0; + HdrToFtr(best)->Free = 0; + HdrToFtr(best)->Magic = BLOCK_MAGIC; + HdrToFtr(best)->Size = best->Size; + + KeReleaseSpinlock(&h->Lock, flags); + return (char *)best + HDR_SIZE; +} + +static void HeapFree(HEAP* h, void* ptr) { + if (!ptr) return; + BLOCK_HDR* blk = (BLOCK_HDR *)((char *)ptr - HDR_SIZE); + if (blk->Magic != BLOCK_MAGIC || blk->Free) return; // double-free / corrupt + + uintptr_t flags = KeAcquireSpinlock(&h->Lock); + + blk->Free = 1; + + // Coalesce with next block if it is free. + BLOCK_HDR* next = NextBlock(blk); + if ((uintptr_t)next < h->Brk && next->Magic == BLOCK_MAGIC && next->Free) { + FreeListRemove(h, next); + blk->Size += OVERHEAD + next->Size; + HdrToFtr(blk)->Size = blk->Size; + HdrToFtr(blk)->Magic = BLOCK_MAGIC; + HdrToFtr(blk)->Free = 1; + } + + // Coalesce with previous block if it is free. + if ((uintptr_t)blk > h->Start) { + BLOCK_FTR* prevFtr = PrevFtr(blk); + if (prevFtr->Magic == BLOCK_MAGIC && prevFtr->Free) { + BLOCK_HDR* prev = FtrToHdr(prevFtr); + FreeListRemove(h, prev); + prev->Size += OVERHEAD + blk->Size; + HdrToFtr(prev)->Size = prev->Size; + HdrToFtr(prev)->Magic = BLOCK_MAGIC; + HdrToFtr(prev)->Free = 1; + blk = prev; + } + } + + // Insert at head of free list. + blk->PrevFree = (BLOCK_HDR *)0; + blk->NextFree = h->FreeList; + if (h->FreeList) h->FreeList->PrevFree = blk; + h->FreeList = blk; + + HdrToFtr(blk)->Free = 1; + HdrToFtr(blk)->Magic = BLOCK_MAGIC; + + HeapTryDecommitTail(h, blk); + + KeReleaseSpinlock(&h->Lock, flags); +} + +static void* HeapRealloc(HEAP* h, void* ptr, size_t new_size) { + // If ptr is NULL, realloc acts like malloc + if (!ptr) { + return HeapAlloc(h, new_size); + } + + // If size is 0, realloc acts like free + if (new_size == 0) { + HeapFree(h, ptr); + return (void*)0; + } + + // Retrieve the header of the existing block + BLOCK_HDR* blk = (BLOCK_HDR *)((char *)ptr - HDR_SIZE); + + // Sanity checks to prevent operating on corrupted or already-freed pointers + if (blk->Magic != BLOCK_MAGIC || blk->Free) { + return (void*)0; + } + + // Align the requested size (matches HeapAlloc 8-byte alignment) + size_t aligned_size = (new_size + 7) & ~7ULL; + + // If the current block is already big enough, we can reuse it in-place. + // (Note: To save space, you could optionally split the block here if + // the new size is significantly smaller, similar to HeapAlloc's logic). + if (blk->Size >= aligned_size) { + return ptr; + } + + // The block is too small. Allocate a new, larger block. + void* new_ptr = HeapAlloc(h, aligned_size); + if (!new_ptr) { + return (void*)0; // Out of memory, original block is left untouched + } + + // Copy the old data to the new block. + // We only copy up to the old block's usable size. + memcpy(new_ptr, ptr, blk->Size); + + // Free the old block + HeapFree(h, ptr); + + return new_ptr; +} + +// --------------------------------------------------------------------------- +// Kernel pool +// --------------------------------------------------------------------------- +static HEAP gPoolHeap; + +// --------------------------------------------------------------------------- +// User heap +// --------------------------------------------------------------------------- +static HEAP gUserHeap; + +// --------------------------------------------------------------------------- +// Module init +// --------------------------------------------------------------------------- +void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize) { + gPml4Phys = bootPml4Phys; + + // Kernel pool heap. + gPoolHeap.FreeList = (BLOCK_HDR *)0; + gPoolHeap.Start = POOL_BASE; + gPoolHeap.Brk = POOL_BASE; + gPoolHeap.Limit = POOL_LIMIT; + gPoolHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_NX; + gPoolHeap.Lock = (SPINLOCK)SPINLOCK_INIT; + + // Pre-commit the requested pool size. + if (poolSize) { + poolSize = (poolSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + HeapGrow(&gPoolHeap, poolSize); + } + + // User heap. + gUserHeap.FreeList = (BLOCK_HDR *)0; + gUserHeap.Start = USER_BASE; + gUserHeap.Brk = USER_BASE; + gUserHeap.Limit = USER_LIMIT; + gUserHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_USER; + gUserHeap.Lock = (SPINLOCK)SPINLOCK_INIT; +} + +void MmActivate(void) { + __writecr3(gPml4Phys); +} + +// --------------------------------------------------------------------------- +// Public pool API +// --------------------------------------------------------------------------- +__declspec(dllexport) void* MmAllocatePool(size_t size) { return HeapAlloc(&gPoolHeap, size); } +__declspec(dllexport) void MmFreePool(void* ptr) { HeapFree(&gPoolHeap, ptr); } +__declspec(dllexport) void* MmReallocatePool(void* ptr, size_t newSize) { return HeapRealloc(&gPoolHeap, ptr, newSize); } + +// --------------------------------------------------------------------------- +// Public user heap API +// --------------------------------------------------------------------------- +__declspec(dllexport) void* MmAllocateUserMemory(size_t size) { return HeapAlloc(&gUserHeap, size); } +__declspec(dllexport) void MmFreeUserMemory(void* ptr, size_t size) { (void)size; HeapFree(&gUserHeap, ptr); } +__declspec(dllexport) void* MmReallocateUserMemory(void* ptr, size_t newSize) { return HeapRealloc(&gUserHeap, ptr, newSize); } + +size_t MmGetKernelPoolUsage(void) { + // Returns bytes committed to the kernel pool + return (size_t)(gPoolHeap.Brk - gPoolHeap.Start); +} + +size_t MmGetUserHeapUsage(void) { + // Returns bytes committed to the user heap + return (size_t)(gUserHeap.Brk - gUserHeap.Start); +} diff --git a/code/video/video.c b/code/video/video.c index d436d94..1ee2907 100644 --- a/code/video/video.c +++ b/code/video/video.c @@ -106,7 +106,7 @@ int main(const char* cmdline) { BinkNextFrame(b); } - WriteConsoleA(h, "shell: goodbye\n", 15, 0, 0); + WriteConsoleA(h, "video: goodbye\n", 15, 0, 0); ExitProcess(0); } diff --git a/readme.md b/readme.md index 5d11b60..d1a4688 100644 --- a/readme.md +++ b/readme.md @@ -78,7 +78,6 @@ The Win32 personality supports programs that expect drive letters, familiar proc - open the VS x64 build tools as administrator - change into this directory -- run `mkimage.bat` - run `build.bat` - use one of `run.bat` (IDE), `rund.bat` (IDE, debug), `runa.bat` (AHCI), or `runad.bat` (AHCI, debug)