memory savings
This commit is contained in:
1 parent
ddb074f0e0
commit
def649b995
7 files changed
+2049
-1883
No files matched your search
Binary file not shown.
+894
-790
File diff suppressed because it is too large.
Load diff
+491
-459
@@ -1,459 +1,491 @@
|
||||
// kmain.c - kernel entry point
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <boot_info.h>
|
||||
#include <mm_phys.h>
|
||||
#include <mm_virt.h>
|
||||
#include <ob_object.h>
|
||||
#include <ke_process.h>
|
||||
#include <ke_thread.h>
|
||||
#include <ke_sched.h>
|
||||
#include <ke_idt.h>
|
||||
#include <ke_irq.h>
|
||||
#include <ke_syscall.h>
|
||||
#include <ke_debug.h>
|
||||
#include <ke_util.h>
|
||||
#include <ldr_image.h>
|
||||
#include <hal_api.h>
|
||||
#include <io_file.h>
|
||||
#include <ini_parser.h>
|
||||
|
||||
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 <stdint.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <boot_info.h>
|
||||
#include <mm_phys.h>
|
||||
#include <mm_virt.h>
|
||||
#include <ob_object.h>
|
||||
#include <ke_process.h>
|
||||
#include <ke_thread.h>
|
||||
#include <ke_sched.h>
|
||||
#include <ke_idt.h>
|
||||
#include <ke_irq.h>
|
||||
#include <ke_syscall.h>
|
||||
#include <ke_debug.h>
|
||||
#include <ke_util.h>
|
||||
#include <ldr_image.h>
|
||||
#include <hal_api.h>
|
||||
#include <io_file.h>
|
||||
#include <ini_parser.h>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
+175
-177
@@ -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 <mm_phys.h>
|
||||
#include <ke_spinlock.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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 <mm_phys.h>
|
||||
#include <ke_spinlock.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
+488
-455
@@ -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 <stddef.h>
|
||||
#include <string.h>
|
||||
#include <mm_virt.h>
|
||||
#include <ke_spinlock.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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 <stddef.h>
|
||||
#include <string.h>
|
||||
#include <mm_virt.h>
|
||||
#include <ke_spinlock.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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);
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in new issue
Block a user