i think it works
This commit is contained in:
commit
0e1479b168
167 files changed
+27008
No files matched your search
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
// boot_info.h - shared between bootx64.efi and kernel.exe
|
||||
// The bootloader fills this structure and passes a pointer to KernelMain.
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
#define BOOT_INFO_MAGIC 0x424F4F54494E464FULL /* 'BOOTINFO' */
|
||||
#define MAX_BOOT_MODULES 32
|
||||
|
||||
typedef struct BOOT_MODULE {
|
||||
char Name[32]; /* e.g. "hal.dll", "ide.sys" */
|
||||
uintptr_t VirtBase; /* where it was mapped */
|
||||
size_t ImageSize;
|
||||
} BOOT_MODULE;
|
||||
|
||||
typedef struct BOOT_INFO {
|
||||
uint64_t Magic;
|
||||
|
||||
/* Physical memory. */
|
||||
MEMORY_MAP MemoryMap;
|
||||
|
||||
/* GOP framebuffer. */
|
||||
uintptr_t FramebufferBase;
|
||||
uint32_t FramebufferWidth;
|
||||
uint32_t FramebufferHeight;
|
||||
uint32_t FramebufferStride; /* pixels per row */
|
||||
uint32_t _pad0;
|
||||
|
||||
/* Loaded modules. */
|
||||
BOOT_MODULE Modules[MAX_BOOT_MODULES];
|
||||
uint32_t ModuleCount;
|
||||
uint32_t _pad1;
|
||||
|
||||
char CommandLine[64];
|
||||
|
||||
/* ACPI RSDP physical address (for APIC, HPET). */
|
||||
uintptr_t RsdpPhysical;
|
||||
|
||||
/* Direct physical window base - always 0xFFFF880000000000. */
|
||||
uintptr_t PhysicalMapBase;
|
||||
|
||||
/* From boot.ini */
|
||||
uint32_t DebugEnabled;
|
||||
uint32_t _pad2;
|
||||
} BOOT_INFO;
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
typedef void (*IniCallback)(const char* section, const char* key,
|
||||
const char* value, void* userdata);
|
||||
void IniParse(char* buf, IniCallback cb, void* userdata);
|
||||
@@ -0,0 +1,113 @@
|
||||
// io_file.c - VFS dispatch layer
|
||||
#include "ke_list.h"
|
||||
#include "io_file.h"
|
||||
#include "mm_virt.h"
|
||||
#include "ob_object.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_DRIVES 8
|
||||
|
||||
typedef struct {
|
||||
IO_FILESYSTEM *Fs;
|
||||
IO_VOLUME_PARAMS Vol;
|
||||
} VOLUME_ENTRY;
|
||||
|
||||
static VOLUME_ENTRY gVolumes[MAX_DRIVES];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registration
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS IoRegisterFilesystem(uint8_t volumeIndex,
|
||||
IO_FILESYSTEM *fs,
|
||||
const IO_VOLUME_PARAMS *vol) {
|
||||
if (volumeIndex >= MAX_DRIVES || !fs || !vol)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (gVolumes[volumeIndex].Fs)
|
||||
return STATUS_ALREADY_EXISTS;
|
||||
gVolumes[volumeIndex].Fs = fs;
|
||||
gVolumes[volumeIndex].Vol = *vol;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Path parsing - "0:\apps\shell.exe" or just "apps\shell.exe" (drive 0)
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint8_t ParseDrive(const char* path, const char **pathOut) {
|
||||
// Accept "N:\..." prefix, otherwise default to drive 0.
|
||||
if (path[0] >= '0' && path[0] <= '7' &&
|
||||
path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
|
||||
*pathOut = path + 3;
|
||||
return (uint8_t)(path[0] - '0');
|
||||
}
|
||||
*pathOut = path;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Destructor
|
||||
// ---------------------------------------------------------------------------
|
||||
static void FileDestroy(OBJECT* obj) {
|
||||
FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header);
|
||||
if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IoOpenFile
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
|
||||
if (!path || !out) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
const char* relPath;
|
||||
uint8_t drive = ParseDrive(path, &relPath);
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
if (!fs) return STATUS_NOT_FOUND;
|
||||
|
||||
void* fileCtx;
|
||||
KSTATUS s = fs->Open(fs, relPath, &fileCtx);
|
||||
if (!KSUCCESS(s)) return s;
|
||||
|
||||
FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */,
|
||||
sizeof(FILE_OBJECT));
|
||||
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
|
||||
f->Header.Destroy = FileDestroy;
|
||||
f->Fs = fs;
|
||||
f->FileCtx = fileCtx;
|
||||
f->Size = fs->GetSize(fs, fileCtx);
|
||||
f->Offset = 0;
|
||||
|
||||
*out = f;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IoReadFile - sequential read at current offset
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) {
|
||||
if (!file || !buf) return STATUS_INVALID_PARAMETER;
|
||||
if (file->Offset >= file->Size) { *bytesRead = 0; return STATUS_SUCCESS; }
|
||||
|
||||
// Clamp to remaining bytes.
|
||||
size_t remaining = (size_t)(file->Size - file->Offset);
|
||||
if (size > remaining) size = remaining;
|
||||
|
||||
KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx,
|
||||
file->Offset, buf, size, bytesRead);
|
||||
if (KSUCCESS(s)) file->Offset += *bytesRead;
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) {
|
||||
if (!file) return STATUS_INVALID_PARAMETER;
|
||||
file->Offset = offset;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
uint64_t IoGetFileSize(FILE_OBJECT* file) {
|
||||
return file ? file->Size : 0;
|
||||
}
|
||||
|
||||
void IoCloseFile(FILE_OBJECT* file) {
|
||||
if (file) ObDereferenceObject(&file->Header);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
// io_file.h - VFS layer
|
||||
// Filesystem drivers register an IO_FILESYSTEM vtable via IoRegisterFilesystem.
|
||||
// Kernel and user code open files through IoOpenFile.
|
||||
#include "ke_status.h"
|
||||
#include "ob_object.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filesystem driver vtable - filled in by fat32.sys (or any future fs driver)
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef struct IO_FILESYSTEM {
|
||||
// Open a file by absolute path (e.g. "apps\\shell.exe").
|
||||
// Returns an opaque per-file driver cookie in* fileCtx.
|
||||
KSTATUS (*Open)(struct IO_FILESYSTEM* fs,
|
||||
const char* path, void **fileCtx);
|
||||
|
||||
// Read up to 'size' bytes at 'offset' into 'buf'.
|
||||
// Sets* bytesRead to actual bytes copied.
|
||||
KSTATUS (*Read)(struct IO_FILESYSTEM* fs, void* fileCtx,
|
||||
uint64_t offset, void* buf,
|
||||
size_t size, size_t* bytesRead);
|
||||
|
||||
// Return the file size in bytes.
|
||||
uint64_t (*GetSize)(struct IO_FILESYSTEM* fs, void* fileCtx);
|
||||
|
||||
// Release any resources the driver allocated for fileCtx.
|
||||
void (*Close)(struct IO_FILESYSTEM* fs, void* fileCtx);
|
||||
|
||||
// Optional driver-private data.
|
||||
void* DriverCtx;
|
||||
} IO_FILESYSTEM;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FILE_OBJECT - the kernel object returned to callers
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef struct FILE_OBJECT {
|
||||
OBJECT Header; // must be first
|
||||
IO_FILESYSTEM* Fs;
|
||||
void* FileCtx; // driver cookie
|
||||
uint64_t Size;
|
||||
uint64_t Offset; // current position for sequential reads
|
||||
} FILE_OBJECT;
|
||||
|
||||
typedef struct IO_VOLUME_PARAMS {
|
||||
uint8_t DiskIndex; // which disk in HAL_DISK table
|
||||
uint64_t PartitionLba; // starting LBA of this partition
|
||||
uint64_t PartitionSize; // sectors, for bounds checking
|
||||
} IO_VOLUME_PARAMS;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registration - called by fat32.sys from DriverEntry
|
||||
// ---------------------------------------------------------------------------
|
||||
// driveIndex matches the index passed to IoRegisterDisk.
|
||||
KSTATUS IoRegisterFilesystem(uint8_t driveIndex, IO_FILESYSTEM* fs, const IO_VOLUME_PARAMS *vol);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kernel-side API
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out);
|
||||
KSTATUS IoReadFile(FILE_OBJECT* file, void* buf,
|
||||
size_t size, size_t* bytesRead);
|
||||
KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset);
|
||||
uint64_t IoGetFileSize(FILE_OBJECT* file);
|
||||
void IoCloseFile(FILE_OBJECT* file);
|
||||
@@ -0,0 +1,29 @@
|
||||
// ke_debug.c - debugging utilities
|
||||
#include "hal_api.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "printf.h"
|
||||
int _fltused; // fuckass
|
||||
|
||||
int gDebugEnabled = 0;
|
||||
void (*PrintChar)(char, uint32_t, uint32_t);
|
||||
|
||||
void kernel_putchar(char c) {
|
||||
PrintChar(c, 0x00FF00, 0x000000);
|
||||
}
|
||||
|
||||
void DbgPrint(const char* fmt, ...) {
|
||||
if (!gDebugEnabled) return;
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
vprintf(fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v) {
|
||||
gDebugEnabled = v;
|
||||
PrintChar = PrintPtr;
|
||||
printf_set_putchar(kernel_putchar);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef KE_DEBUG_H
|
||||
#define KE_DEBUG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v);
|
||||
void DbgPrint(const char* fmt, ...);
|
||||
|
||||
#endif // KE_DEBUG_H
|
||||
@@ -0,0 +1,72 @@
|
||||
// ke_event.c - synchronisation events
|
||||
#include "ke_event.h"
|
||||
#include "ke_thread.h"
|
||||
#include "ke_sched.h"
|
||||
#include "ob_object.h"
|
||||
|
||||
static void EventDestroy(OBJECT* obj) { (void)obj; }
|
||||
|
||||
KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT **out) {
|
||||
if (!out) return STATUS_INVALID_PARAMETER;
|
||||
EVENT* e = (EVENT *)ObAllocateObject(ObjectTypeEvent, sizeof(EVENT));
|
||||
if (!e) return STATUS_NO_MEMORY;
|
||||
e->Header.Destroy = EventDestroy;
|
||||
e->Lock = (SPINLOCK)SPINLOCK_INIT;
|
||||
e->Type = type;
|
||||
e->Signaled = initialState ? 1 : 0;
|
||||
LIST_INIT(&e->WaitList);
|
||||
*out = e;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void KeSignalEvent(EVENT* event) {
|
||||
if (!event) return;
|
||||
KeAcquireSpinlock(&event->Lock);
|
||||
|
||||
if (event->Type == EventManualReset) {
|
||||
// Wake all waiters.
|
||||
event->Signaled = 1;
|
||||
while (!LIST_IS_EMPTY(&event->WaitList)) {
|
||||
LIST_ENTRY* node = ListPopHead(&event->WaitList);
|
||||
THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
KeReleaseSpinlock(&event->Lock);
|
||||
KeUnblockThread(t);
|
||||
KeAcquireSpinlock(&event->Lock);
|
||||
}
|
||||
} else {
|
||||
// Auto-reset: wake one waiter or set the flag for the next waiter.
|
||||
if (!LIST_IS_EMPTY(&event->WaitList)) {
|
||||
LIST_ENTRY* node = ListPopHead(&event->WaitList);
|
||||
THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
KeReleaseSpinlock(&event->Lock);
|
||||
KeUnblockThread(t);
|
||||
return;
|
||||
}
|
||||
event->Signaled = 1;
|
||||
}
|
||||
KeReleaseSpinlock(&event->Lock);
|
||||
}
|
||||
|
||||
void KeClearEvent(EVENT* event) {
|
||||
if (!event) return;
|
||||
KeAcquireSpinlock(&event->Lock);
|
||||
event->Signaled = 0;
|
||||
KeReleaseSpinlock(&event->Lock);
|
||||
}
|
||||
|
||||
KSTATUS KeWaitForEvent(EVENT* event) {
|
||||
if (!event) return STATUS_INVALID_PARAMETER;
|
||||
THREAD* self = KeCurrentThread();
|
||||
|
||||
for (;;) {
|
||||
KeAcquireSpinlock(&event->Lock);
|
||||
if (event->Signaled) {
|
||||
if (event->Type == EventAutoReset) event->Signaled = 0;
|
||||
KeReleaseSpinlock(&event->Lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
ListInsertTail(&event->WaitList, &self->SchedulerLink);
|
||||
KeReleaseSpinlock(&event->Lock);
|
||||
KeBlockThread(self, &event->Header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
// ke_event.h - auto-reset and manual-reset events
|
||||
#include "ob_object.h"
|
||||
#include "ke_spinlock.h"
|
||||
#include "ke_list.h"
|
||||
#include "ke_status.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum EVENT_TYPE { EventAutoReset, EventManualReset } EVENT_TYPE;
|
||||
|
||||
typedef struct EVENT {
|
||||
OBJECT Header;
|
||||
SPINLOCK Lock;
|
||||
EVENT_TYPE Type;
|
||||
uint32_t Signaled;
|
||||
LIST_ENTRY WaitList;
|
||||
} EVENT;
|
||||
|
||||
KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT **out);
|
||||
void KeSignalEvent(EVENT* event);
|
||||
void KeClearEvent(EVENT* event);
|
||||
KSTATUS KeWaitForEvent(EVENT* event);
|
||||
@@ -0,0 +1,45 @@
|
||||
; ke_helpers.asm
|
||||
.code
|
||||
|
||||
; void KeFlushGdt(GDTR* pGdtr, uint16_t codeSel, uint16_t dataSel, uint16_t tssSel);
|
||||
KeFlushGdt PROC
|
||||
; RCX = pGdtr (pointer to limit/base struct)
|
||||
; RDX = codeSel
|
||||
; R8 = dataSel
|
||||
; R9 = tssSel
|
||||
|
||||
; 1. Load the GDT
|
||||
lgdt fword ptr [rcx]
|
||||
|
||||
; 2. Reload Data Segments
|
||||
mov ax, r8w
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov ss, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
; 3. Load Task Register
|
||||
mov ax, r9w
|
||||
ltr ax
|
||||
|
||||
; 4. Reload Code Segment (Far Return)
|
||||
; In MASM64, use OFFSET to get the address of the label.
|
||||
; We push the selector, then the offset, then issue retff / retfq.
|
||||
|
||||
push rdx ; Push new Code Selector (RDX)
|
||||
lea rax, [reload_cs] ; RIP-relative address of the label
|
||||
push rax ; Push "return" address
|
||||
db 048h, 0CBh ; rex_w retf (effectively retfq)
|
||||
|
||||
reload_cs:
|
||||
ret
|
||||
KeFlushGdt ENDP
|
||||
|
||||
; void KeFlushIdt(IDTR* pIdtr);
|
||||
KeFlushIdt PROC
|
||||
lidt fword ptr [rcx]
|
||||
ret
|
||||
KeFlushIdt ENDP
|
||||
|
||||
END
|
||||
@@ -0,0 +1,175 @@
|
||||
// ke_idt.c - GDT, IDT, and TSS initialisation
|
||||
//
|
||||
// GDT layout (matches segment selectors in ke_switch.asm / ke_syscall.asm):
|
||||
// 0x00 null
|
||||
// 0x08 ring-0 code (64-bit)
|
||||
// 0x10 ring-0 data
|
||||
// 0x18 ring-3 code (64-bit, DPL=3)
|
||||
// 0x20 ring-3 data (DPL=3) - note: 0x1B/0x23 = 0x18|3 / 0x20|3
|
||||
// 0x28 TSS low
|
||||
// 0x30 TSS high
|
||||
#include "ke_idt.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
// External assembly procedures
|
||||
extern void KeFlushGdt(void* pGdtr, uint16_t codeSel, uint16_t dataSel, uint16_t tssSel);
|
||||
extern void KeFlushIdt(void* pIdtr);
|
||||
|
||||
extern void KeIrqStub0(void);
|
||||
extern void KeIrqStub1(void);
|
||||
extern void KeIrqStub2(void);
|
||||
extern void KeIrqStub3(void);
|
||||
extern void KeIrqStub4(void);
|
||||
extern void KeIrqStub5(void);
|
||||
extern void KeIrqStub6(void);
|
||||
extern void KeIrqStub7(void);
|
||||
extern void KeIrqStub8(void);
|
||||
extern void KeIrqStub9(void);
|
||||
extern void KeIrqStub10(void);
|
||||
extern void KeIrqStub11(void);
|
||||
extern void KeIrqStub12(void);
|
||||
extern void KeIrqStub13(void);
|
||||
extern void KeIrqStub14(void);
|
||||
extern void KeIrqStub15(void);
|
||||
extern void KeIrqStub16(void);
|
||||
extern void KeIrqStub17(void);
|
||||
extern void KeIrqStub18(void);
|
||||
extern void KeIrqStub19(void);
|
||||
extern void KeIrqStub20(void);
|
||||
extern void KeIrqStub21(void);
|
||||
extern void KeIrqStub22(void);
|
||||
extern void KeIrqStub23(void);
|
||||
|
||||
#define NUM_IRQS 24
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GDT
|
||||
// ---------------------------------------------------------------------------
|
||||
#define GDT_ENTRIES 8
|
||||
#define KERNEL_IST_STACK_SIZE 0x4000
|
||||
|
||||
static uint64_t gGdt[GDT_ENTRIES];
|
||||
static uint8_t gKernelStack[KERNEL_IST_STACK_SIZE];
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct { uint16_t Limit; uint64_t Base; } GDTR;
|
||||
#pragma pack(pop)
|
||||
|
||||
static void GdtSetEntry(int idx, uint32_t base, uint32_t limit,
|
||||
uint8_t access, uint8_t flags) {
|
||||
gGdt[idx] =
|
||||
((uint64_t)(limit & 0xFFFF)) |
|
||||
((uint64_t)(base & 0xFFFFFF) << 16) |
|
||||
((uint64_t)access << 40) |
|
||||
((uint64_t)(limit >> 16 & 0xF)<< 48) |
|
||||
((uint64_t)(flags & 0xF) << 52) |
|
||||
((uint64_t)(base >> 24 & 0xFF)<< 56);
|
||||
}
|
||||
|
||||
static void GdtSetTss(int idx, uintptr_t base, uint32_t limit) {
|
||||
// TSS descriptor is 16 bytes (two GDT slots).
|
||||
gGdt[idx] = 0;
|
||||
gGdt[idx+1] = 0;
|
||||
|
||||
gGdt[idx] =
|
||||
((uint64_t)(limit & 0xFFFF)) |
|
||||
((uint64_t)(base & 0xFFFFFF) << 16) |
|
||||
(0x89ULL << 40) | // P=1,DPL=0,type=TSS64 avail
|
||||
((uint64_t)((limit>>16)&0xF) << 48) |
|
||||
((uint64_t)(base>>24 & 0xFF) << 56);
|
||||
|
||||
gGdt[idx+1] = (base >> 32);
|
||||
}
|
||||
|
||||
TSS64 gTssStruct;
|
||||
TSS64* gTss = &gTssStruct;
|
||||
|
||||
void KeInitializeGdt(void) {
|
||||
memset(gGdt, 0, sizeof(gGdt));
|
||||
memset(&gTssStruct, 0, sizeof(gTssStruct));
|
||||
|
||||
// 0x00: Null
|
||||
GdtSetEntry(0, 0, 0, 0, 0);
|
||||
// 0x08: Ring-0 Code (Access 0x9A, Flags 0xA for 64-bit)
|
||||
GdtSetEntry(1, 0, 0xFFFFF, 0x9A, 0xA);
|
||||
// 0x10: Ring-0 Data
|
||||
GdtSetEntry(2, 0, 0xFFFFF, 0x92, 0xC);
|
||||
// 0x18: Ring-3 Code
|
||||
GdtSetEntry(3, 0, 0xFFFFF, 0xFA, 0xA);
|
||||
// 0x20: Ring-3 Data
|
||||
GdtSetEntry(4, 0, 0xFFFFF, 0xF2, 0xC);
|
||||
|
||||
// 0x28: TSS (Occupies two slots: 0x28 and 0x30)
|
||||
gTssStruct.IoMapBase = sizeof(TSS64);
|
||||
gTssStruct.RSP0 = (uint64_t)(gKernelStack + KERNEL_IST_STACK_SIZE); // top of stack
|
||||
|
||||
GdtSetTss(5, (uintptr_t)&gTssStruct, sizeof(TSS64) - 1);
|
||||
|
||||
GDTR gdtr = { sizeof(gGdt) - 1, (uint64_t)gGdt };
|
||||
|
||||
// Call the MASM64 proc to reload everything
|
||||
// Selectors: CS=0x08, Data=0x10, TSS=0x28
|
||||
KeFlushGdt(&gdtr, 0x08, 0x10, 0x28);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IDT
|
||||
// ---------------------------------------------------------------------------
|
||||
#define IDT_ENTRIES 256
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint16_t OffsetLow;
|
||||
uint16_t Selector;
|
||||
uint8_t Ist;
|
||||
uint8_t TypeAttr;
|
||||
uint16_t OffsetMid;
|
||||
uint32_t OffsetHigh;
|
||||
uint32_t Reserved;
|
||||
} IDT_ENTRY;
|
||||
#pragma pack(pop)
|
||||
|
||||
static IDT_ENTRY gIdt[IDT_ENTRIES];
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct { uint16_t Limit; uint64_t Base; } IDTR;
|
||||
#pragma pack(pop)
|
||||
|
||||
static void IdtSetGate(int vec, uintptr_t handler,
|
||||
uint8_t typeAttr, uint8_t ist) {
|
||||
gIdt[vec].OffsetLow = (uint16_t)(handler & 0xFFFF);
|
||||
gIdt[vec].Selector = 0x08; // ring-0 code segment
|
||||
gIdt[vec].Ist = ist;
|
||||
gIdt[vec].TypeAttr = typeAttr;
|
||||
gIdt[vec].OffsetMid = (uint16_t)((handler >> 16) & 0xFFFF);
|
||||
gIdt[vec].OffsetHigh = (uint32_t)((handler >> 32) & 0xFFFFFFFF);
|
||||
gIdt[vec].Reserved = 0;
|
||||
}
|
||||
|
||||
void KeInitializeIdt(void) {
|
||||
memset(gIdt, 0, sizeof(gIdt));
|
||||
|
||||
IdtSetGate(0, (uintptr_t)KeFaultDE, 0x8E, 0);
|
||||
IdtSetGate(13, (uintptr_t)KeFaultGP, 0x8E, 0);
|
||||
IdtSetGate(14, (uintptr_t)KeFaultPF, 0x8E, 0);
|
||||
IdtSetGate(0x40, (uintptr_t)KeSyscallEntry, 0xEE, 0);
|
||||
|
||||
static void *irqStubs[NUM_IRQS] = {
|
||||
(void*)KeIrqStub0, (void*)KeIrqStub1, (void*)KeIrqStub2,
|
||||
(void*)KeIrqStub3, (void*)KeIrqStub4, (void*)KeIrqStub5,
|
||||
(void*)KeIrqStub6, (void*)KeIrqStub7, (void*)KeIrqStub8,
|
||||
(void*)KeIrqStub9, (void*)KeIrqStub10, (void*)KeIrqStub11,
|
||||
(void*)KeIrqStub12, (void*)KeIrqStub13, (void*)KeIrqStub14,
|
||||
(void*)KeIrqStub15, (void*)KeIrqStub16, (void*)KeIrqStub17,
|
||||
(void*)KeIrqStub18, (void*)KeIrqStub19, (void*)KeIrqStub20,
|
||||
(void*)KeIrqStub21, (void*)KeIrqStub22, (void*)KeIrqStub23,
|
||||
};
|
||||
for (int i = 0; i < NUM_IRQS; i++)
|
||||
IdtSetGate(0x20 + i, (uintptr_t)irqStubs[i], 0x8E, 0);
|
||||
|
||||
IDTR idtr = { sizeof(gIdt) - 1, (uint64_t)gIdt };
|
||||
|
||||
// Call the MASM64 proc to load IDT
|
||||
KeFlushIdt(&idtr);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
// ke_idt.h - IDT, GDT, and TSS setup
|
||||
#include <stdint.h>
|
||||
|
||||
// 64-bit TSS - minimal; we only care about RSP0.
|
||||
#pragma pack(push, 1)
|
||||
typedef struct TSS64 {
|
||||
uint32_t Reserved0;
|
||||
uint64_t RSP0; // loaded into RSP on ring-0 entry
|
||||
uint64_t RSP1;
|
||||
uint64_t RSP2;
|
||||
uint64_t Reserved1;
|
||||
uint64_t IST[7];
|
||||
uint64_t Reserved2;
|
||||
uint16_t Reserved3;
|
||||
uint16_t IoMapBase;
|
||||
} TSS64;
|
||||
#pragma pack(pop)
|
||||
|
||||
extern TSS64* gTss;
|
||||
|
||||
void KeInitializeGdt(void);
|
||||
void KeInitializeIdt(void);
|
||||
|
||||
// Called from hal_timer.c after HPET/APIC timer fires.
|
||||
void KeTimerInterrupt(void);
|
||||
|
||||
// Syscall entry - implemented in ke_syscall.asm.
|
||||
extern void KeSyscallEntry(void);
|
||||
|
||||
// Fault stubs installed by KeInitializeIdt.
|
||||
extern void KeFaultDE(void); // #DE divide error
|
||||
extern void KeFaultGP(void); // #GP general protection
|
||||
extern void KeFaultPF(void); // #PF page fault
|
||||
@@ -0,0 +1,26 @@
|
||||
// ke_irq.c — IRQ dispatch, kernel-side
|
||||
#include "ke_irq.h"
|
||||
#include "hal_api.h"
|
||||
|
||||
#define MAX_IRQS 24
|
||||
|
||||
static void (*gIrqHandlers[MAX_IRQS])(void);
|
||||
static const HAL_API *gHal;
|
||||
|
||||
void KeIrqInitialize(const HAL_API *hal) {
|
||||
gHal = hal;
|
||||
}
|
||||
|
||||
void KeIrqRegister(uint8_t irq, void (*handler)(void)) {
|
||||
if (irq >= MAX_IRQS || !handler) return;
|
||||
gIrqHandlers[irq] = handler;
|
||||
gHal->Intc.RouteIrq(irq); // programs IOAPIC, clears mask bit
|
||||
}
|
||||
|
||||
// Called from KeIrqCommon in assembly - this is the only symbol
|
||||
// the assembly needs to know.
|
||||
void KeIrqDispatch(uint8_t irq) {
|
||||
gHal->Intc.SendEoi();
|
||||
if (irq < MAX_IRQS && gIrqHandlers[irq])
|
||||
gIrqHandlers[irq]();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "hal_api.h"
|
||||
#include <stdint.h>
|
||||
|
||||
void KeIrqInitialize(const HAL_API *hal);
|
||||
void KeIrqRegister(uint8_t irq, void (*handler)(void));
|
||||
void KeIrqDispatch(uint8_t irq); // called from ke_syscall.asm
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
// ke_list.h - intrusive doubly-linked list
|
||||
// Used throughout the kernel for run queues, wait lists, thread lists, etc.
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct LIST_ENTRY {
|
||||
struct LIST_ENTRY* Fwd;
|
||||
struct LIST_ENTRY* Bwd;
|
||||
} LIST_ENTRY;
|
||||
|
||||
#define LIST_INIT(head) ((head)->Fwd = (head)->Bwd = (head))
|
||||
#define LIST_IS_EMPTY(head) ((head)->Fwd == (head))
|
||||
|
||||
// Recover the containing struct pointer from an embedded LIST_ENTRY.
|
||||
#define CONTAINING_RECORD(ptr, type, member) \
|
||||
((type *)((char *)(ptr) - offsetof(type, member)))
|
||||
|
||||
static inline void ListInsertTail(LIST_ENTRY* head, LIST_ENTRY* node) {
|
||||
node->Bwd = head->Bwd;
|
||||
node->Fwd = head;
|
||||
head->Bwd->Fwd = node;
|
||||
head->Bwd = node;
|
||||
}
|
||||
|
||||
static inline void ListInsertHead(LIST_ENTRY* head, LIST_ENTRY* node) {
|
||||
node->Fwd = head->Fwd;
|
||||
node->Bwd = head;
|
||||
head->Fwd->Bwd = node;
|
||||
head->Fwd = node;
|
||||
}
|
||||
|
||||
static inline void ListRemove(LIST_ENTRY* node) {
|
||||
node->Bwd->Fwd = node->Fwd;
|
||||
node->Fwd->Bwd = node->Bwd;
|
||||
node->Fwd = node->Bwd = node; /* self-loop: poison against double-remove */
|
||||
}
|
||||
|
||||
// Pop the first entry off the list, or NULL if empty.
|
||||
static inline LIST_ENTRY* ListPopHead(LIST_ENTRY* head) {
|
||||
if (LIST_IS_EMPTY(head)) return (LIST_ENTRY *)0;
|
||||
LIST_ENTRY* node = head->Fwd;
|
||||
ListRemove(node);
|
||||
return node;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// ke_mutex.c - binary ownership mutex
|
||||
#include "ke_mutex.h"
|
||||
#include "ke_thread.h"
|
||||
#include "ke_sched.h"
|
||||
#include "mm_virt.h"
|
||||
#include "ob_object.h"
|
||||
|
||||
static void MutexDestroy(OBJECT* obj) { (void)obj; }
|
||||
|
||||
KSTATUS MutexCreate(MUTEX **out) {
|
||||
if (!out) return STATUS_INVALID_PARAMETER;
|
||||
MUTEX* m = (MUTEX *)ObAllocateObject(ObjectTypeMutex, sizeof(MUTEX));
|
||||
if (!m) return STATUS_NO_MEMORY;
|
||||
m->Header.Destroy = MutexDestroy;
|
||||
m->Lock = (SPINLOCK)SPINLOCK_INIT;
|
||||
m->Owner = (struct THREAD *)0;
|
||||
LIST_INIT(&m->WaitList);
|
||||
*out = m;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
KSTATUS KeAcquireMutex(MUTEX* mutex) {
|
||||
if (!mutex) return STATUS_INVALID_PARAMETER;
|
||||
THREAD* self = KeCurrentThread();
|
||||
|
||||
for (;;) {
|
||||
KeAcquireSpinlock(&mutex->Lock);
|
||||
if (!mutex->Owner) {
|
||||
mutex->Owner = self;
|
||||
KeReleaseSpinlock(&mutex->Lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
// Already owned - add self to wait list and block.
|
||||
ListInsertTail(&mutex->WaitList, &self->SchedulerLink);
|
||||
KeReleaseSpinlock(&mutex->Lock);
|
||||
KeBlockThread(self, &mutex->Header);
|
||||
// KeBlockThread calls KeYield; when we return here we've been
|
||||
// unblocked by KeReleaseMutex - loop back and try to own it.
|
||||
}
|
||||
}
|
||||
|
||||
KSTATUS KeTryAcquireMutex(MUTEX* mutex) {
|
||||
if (!mutex) return STATUS_INVALID_PARAMETER;
|
||||
THREAD* self = KeCurrentThread();
|
||||
KeAcquireSpinlock(&mutex->Lock);
|
||||
if (!mutex->Owner) {
|
||||
mutex->Owner = self;
|
||||
KeReleaseSpinlock(&mutex->Lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
KeReleaseSpinlock(&mutex->Lock);
|
||||
return STATUS_FAILURE;
|
||||
}
|
||||
|
||||
void KeReleaseMutex(MUTEX* mutex) {
|
||||
if (!mutex) return;
|
||||
KeAcquireSpinlock(&mutex->Lock);
|
||||
mutex->Owner = (struct THREAD *)0;
|
||||
|
||||
// Wake the first waiter, if any.
|
||||
if (!LIST_IS_EMPTY(&mutex->WaitList)) {
|
||||
LIST_ENTRY* node = ListPopHead(&mutex->WaitList);
|
||||
THREAD* waiter = CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
KeReleaseSpinlock(&mutex->Lock);
|
||||
KeUnblockThread(waiter);
|
||||
return;
|
||||
}
|
||||
KeReleaseSpinlock(&mutex->Lock);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
// ke_mutex.h - binary ownership mutex
|
||||
#include "ob_object.h"
|
||||
#include "ke_spinlock.h"
|
||||
#include "ke_list.h"
|
||||
#include "ke_status.h"
|
||||
|
||||
typedef struct MUTEX {
|
||||
OBJECT Header;
|
||||
SPINLOCK Lock;
|
||||
struct THREAD* Owner; /* NULL if unowned */
|
||||
LIST_ENTRY WaitList;
|
||||
} MUTEX;
|
||||
|
||||
KSTATUS MutexCreate(MUTEX **out);
|
||||
KSTATUS KeAcquireMutex(MUTEX* mutex);
|
||||
KSTATUS KeTryAcquireMutex(MUTEX* mutex);
|
||||
void KeReleaseMutex(MUTEX* mutex);
|
||||
@@ -0,0 +1,143 @@
|
||||
// ke_process.c - process model
|
||||
#include "ke_process.h"
|
||||
#include "ke_thread.h" // KeExitThread (forward declared below)
|
||||
#include "mm_virt.h"
|
||||
#include "ob_object.h"
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global process list
|
||||
// ---------------------------------------------------------------------------
|
||||
static LIST_ENTRY gProcessList;
|
||||
static SPINLOCK gProcessListLock;
|
||||
static uint32_t gNextProcessId = 1;
|
||||
|
||||
static void ProcessInit(void) {
|
||||
static int inited = 0;
|
||||
if (!inited) {
|
||||
LIST_INIT(&gProcessList);
|
||||
gProcessListLock = (SPINLOCK)SPINLOCK_INIT;
|
||||
inited = 1;
|
||||
}
|
||||
}
|
||||
|
||||
LIST_ENTRY* KeGetProcessList(void) {
|
||||
return &gProcessList;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Destructor (called by ObDereferenceObject when RefCount hits zero)
|
||||
// ---------------------------------------------------------------------------
|
||||
static void ProcessDestroy(OBJECT* obj) {
|
||||
PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header);
|
||||
|
||||
// Release all remaining handles.
|
||||
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
|
||||
if (proc->Handles[i]) {
|
||||
ObDereferenceObject(proc->Handles[i]);
|
||||
proc->Handles[i] = (OBJECT *)0;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from global list.
|
||||
KeAcquireSpinlock(&gProcessListLock);
|
||||
ListRemove(&proc->GlobalLink);
|
||||
KeReleaseSpinlock(&gProcessListLock);
|
||||
|
||||
// Memory for the struct itself is freed by ObDereferenceObject after
|
||||
// Destroy returns.
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeCreateProcess
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
|
||||
ProcessInit();
|
||||
|
||||
if (!name || !out) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
|
||||
if (!proc) return STATUS_NO_MEMORY;
|
||||
|
||||
proc->Header.Destroy = ProcessDestroy;
|
||||
|
||||
proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1);
|
||||
strncpy(proc->Name, name, PROCESS_NAME_MAX - 1);
|
||||
proc->Name[PROCESS_NAME_MAX - 1] = '\0';
|
||||
|
||||
LIST_INIT(&proc->ThreadList);
|
||||
proc->ThreadCount = 0;
|
||||
proc->HandleLock = (SPINLOCK)SPINLOCK_INIT;
|
||||
proc->ExitCode = 0;
|
||||
|
||||
// Slot 0 is the invalid handle - leave it NULL.
|
||||
memset(proc->Handles, 0, sizeof(proc->Handles));
|
||||
|
||||
LIST_INIT(&proc->GlobalLink);
|
||||
|
||||
KeAcquireSpinlock(&gProcessListLock);
|
||||
ListInsertTail(&gProcessList, &proc->GlobalLink);
|
||||
KeReleaseSpinlock(&gProcessListLock);
|
||||
|
||||
*out = proc;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeExitProcess
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeExitProcess(PROCESS* process, int exitCode) {
|
||||
if (!process) return;
|
||||
process->ExitCode = exitCode;
|
||||
// The last thread exiting via KeExitThread will drop the process refcount.
|
||||
// This function is a policy hook; actual cleanup is in ProcessDestroy.
|
||||
ObDereferenceObject(&process->Header);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handle table
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) {
|
||||
if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
KeAcquireSpinlock(&process->HandleLock);
|
||||
|
||||
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
|
||||
if (!process->Handles[i]) {
|
||||
ObReferenceObject(object);
|
||||
process->Handles[i] = object;
|
||||
*handleOut = (uint32_t)i;
|
||||
KeReleaseSpinlock(&process->HandleLock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
KeReleaseSpinlock(&process->HandleLock);
|
||||
return STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
void KeCloseHandle(PROCESS* process, uint32_t handle) {
|
||||
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return;
|
||||
|
||||
KeAcquireSpinlock(&process->HandleLock);
|
||||
OBJECT* obj = process->Handles[handle];
|
||||
if (obj) {
|
||||
process->Handles[handle] = (OBJECT *)0;
|
||||
KeReleaseSpinlock(&process->HandleLock);
|
||||
ObDereferenceObject(obj);
|
||||
return;
|
||||
}
|
||||
KeReleaseSpinlock(&process->HandleLock);
|
||||
}
|
||||
|
||||
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) {
|
||||
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE)
|
||||
return (OBJECT *)0;
|
||||
|
||||
KeAcquireSpinlock(&process->HandleLock);
|
||||
OBJECT* obj = process->Handles[handle];
|
||||
if (obj) ObReferenceObject(obj);
|
||||
KeReleaseSpinlock(&process->HandleLock);
|
||||
return obj;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
// ke_process.h - process model
|
||||
// A process owns a handle table and a list of threads.
|
||||
// There is one shared address space - processes do not own one.
|
||||
#include "ob_object.h"
|
||||
#include "ke_list.h"
|
||||
#include "ke_spinlock.h"
|
||||
|
||||
#define PROCESS_NAME_MAX 64
|
||||
#define HANDLE_TABLE_SIZE 256
|
||||
|
||||
typedef struct PROCESS {
|
||||
OBJECT Header; /* must be first */
|
||||
|
||||
uint32_t ProcessId;
|
||||
char Name[PROCESS_NAME_MAX]; /* UTF-8 */
|
||||
|
||||
/* Thread list. */
|
||||
LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */
|
||||
uint32_t ThreadCount;
|
||||
|
||||
/* Handle table.
|
||||
Index 0 is always NULL (the invalid handle).
|
||||
Entries are OBJECT pointers, each holding a reference. */
|
||||
OBJECT* Handles[HANDLE_TABLE_SIZE];
|
||||
SPINLOCK HandleLock;
|
||||
|
||||
int ExitCode;
|
||||
LIST_ENTRY GlobalLink; /* node in the global process list */
|
||||
} PROCESS;
|
||||
|
||||
KSTATUS KeCreateProcess(const char* name, PROCESS **out);
|
||||
void KeExitProcess(PROCESS* process, int exitCode);
|
||||
|
||||
/* Handle table operations. */
|
||||
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut);
|
||||
void KeCloseHandle(PROCESS* process, uint32_t handle);
|
||||
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle);
|
||||
|
||||
/* Return the global process list head (kernel-internal). */
|
||||
LIST_ENTRY* KeGetProcessList(void);
|
||||
@@ -0,0 +1,85 @@
|
||||
// ke_queue.c - ring-buffer message queue
|
||||
#include "ke_queue.h"
|
||||
#include "ke_thread.h"
|
||||
#include "ke_sched.h"
|
||||
#include "ob_object.h"
|
||||
#include <string.h>
|
||||
|
||||
static void QueueDestroy(OBJECT* obj) { (void)obj; }
|
||||
|
||||
KSTATUS QueueCreate(QUEUE **out) {
|
||||
if (!out) return STATUS_INVALID_PARAMETER;
|
||||
QUEUE* q = (QUEUE *)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE));
|
||||
if (!q) return STATUS_NO_MEMORY;
|
||||
q->Header.Destroy = QueueDestroy;
|
||||
q->Lock = (SPINLOCK)SPINLOCK_INIT;
|
||||
q->Head = q->Tail = q->Count = 0;
|
||||
LIST_INIT(&q->SendWaiters);
|
||||
LIST_INIT(&q->RecvWaiters);
|
||||
*out = q;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking) {
|
||||
if (!queue || !msg) return STATUS_INVALID_PARAMETER;
|
||||
THREAD* self = KeCurrentThread();
|
||||
|
||||
for (;;) {
|
||||
KeAcquireSpinlock(&queue->Lock);
|
||||
if (queue->Count < QUEUE_DEPTH) {
|
||||
queue->Ring[queue->Tail] = *msg;
|
||||
queue->Tail = (queue->Tail + 1) % QUEUE_DEPTH;
|
||||
queue->Count++;
|
||||
|
||||
// Wake a receiver if one is waiting.
|
||||
if (!LIST_IS_EMPTY(&queue->RecvWaiters)) {
|
||||
LIST_ENTRY* node = ListPopHead(&queue->RecvWaiters);
|
||||
THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
KeUnblockThread(t);
|
||||
} else {
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
if (!blocking) {
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
return STATUS_QUEUE_FULL;
|
||||
}
|
||||
ListInsertTail(&queue->SendWaiters, &self->SchedulerLink);
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
KeBlockThread(self, &queue->Header);
|
||||
}
|
||||
}
|
||||
|
||||
KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking) {
|
||||
if (!queue || !msg) return STATUS_INVALID_PARAMETER;
|
||||
THREAD* self = KeCurrentThread();
|
||||
|
||||
for (;;) {
|
||||
KeAcquireSpinlock(&queue->Lock);
|
||||
if (queue->Count > 0) {
|
||||
*msg = queue->Ring[queue->Head];
|
||||
queue->Head = (queue->Head + 1) % QUEUE_DEPTH;
|
||||
queue->Count--;
|
||||
|
||||
// Wake a sender if one is waiting.
|
||||
if (!LIST_IS_EMPTY(&queue->SendWaiters)) {
|
||||
LIST_ENTRY* node = ListPopHead(&queue->SendWaiters);
|
||||
THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
KeUnblockThread(t);
|
||||
} else {
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
if (!blocking) {
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
return STATUS_QUEUE_EMPTY;
|
||||
}
|
||||
ListInsertTail(&queue->RecvWaiters, &self->SchedulerLink);
|
||||
KeReleaseSpinlock(&queue->Lock);
|
||||
KeBlockThread(self, &queue->Header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
// ke_queue.h - fixed-depth message queue
|
||||
// Processes share an address space, so a pointer in Param[] is valid
|
||||
// on both sides. The queue is only a synchronisation and ordering tool.
|
||||
#include "ob_object.h"
|
||||
#include "ke_spinlock.h"
|
||||
#include "ke_list.h"
|
||||
#include "ke_status.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define QUEUE_MESSAGE_SIZE 64 /* bytes, fixed */
|
||||
#define QUEUE_DEPTH 256
|
||||
|
||||
typedef struct QUEUE_MESSAGE {
|
||||
uint32_t Type;
|
||||
uint32_t Flags;
|
||||
uint64_t Param[6]; /* 48 bytes of payload */
|
||||
} QUEUE_MESSAGE; /* total: 64 bytes */
|
||||
|
||||
typedef struct QUEUE {
|
||||
OBJECT Header;
|
||||
SPINLOCK Lock;
|
||||
QUEUE_MESSAGE Ring[QUEUE_DEPTH];
|
||||
uint32_t Head, Tail, Count;
|
||||
LIST_ENTRY SendWaiters;
|
||||
LIST_ENTRY RecvWaiters;
|
||||
} QUEUE;
|
||||
|
||||
KSTATUS QueueCreate(QUEUE **out);
|
||||
KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking);
|
||||
KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking);
|
||||
@@ -0,0 +1,202 @@
|
||||
// ke_sched.c - MLFQ scheduler
|
||||
//
|
||||
// Three queues: PriorityHigh(2), PriorityNormal(1), PriorityLow(0).
|
||||
// Dispatch always picks the highest non-empty queue.
|
||||
// Within a queue: strict round-robin.
|
||||
//
|
||||
// Demotion threshold: a thread that consumes DEMOTION_QUANTA consecutive
|
||||
// ticks without blocking drops one level (floor PriorityLow).
|
||||
// Promotion: a thread that unblocks is bumped one level (ceiling PriorityHigh).
|
||||
//
|
||||
// The idle thread (created by KeInitializeScheduler) runs at PriorityLow
|
||||
// when nothing else is runnable.
|
||||
#include "ke_sched.h"
|
||||
#include "ke_thread.h"
|
||||
#include "ke_spinlock.h"
|
||||
#include "ke_list.h"
|
||||
#include "mm_virt.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#define DEMOTION_QUANTA 4 /* ticks at a level before demotion */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run queues
|
||||
// ---------------------------------------------------------------------------
|
||||
static LIST_ENTRY gRunQueue[PriorityCount]; // [0]=Low [1]=Normal [2]=High
|
||||
static SPINLOCK gSchedLock;
|
||||
|
||||
// The currently running thread (also in GS:[0] for fast access).
|
||||
// On a single-core system this is the only "per-CPU" slot we need.
|
||||
static THREAD* gCurrentThread;
|
||||
static THREAD* gIdleThread;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GS:[0] helpers - implemented in ke_switch.asm
|
||||
// (On single-core we just use a global; SMP would use per-core GS.)
|
||||
// ---------------------------------------------------------------------------
|
||||
THREAD* KeCurrentThread(void) { return gCurrentThread; }
|
||||
void KeSetCurrentThread(THREAD* t){ gCurrentThread = t; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Idle thread body - runs in ring 0 when every other thread is blocked.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void IdleLoop(void) {
|
||||
for (;;) { __halt(); }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Init
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeInitializeScheduler(void) {
|
||||
for (int i = 0; i < PriorityCount; i++) LIST_INIT(&gRunQueue[i]);
|
||||
gSchedLock = (SPINLOCK)SPINLOCK_INIT;
|
||||
|
||||
// Create the idle process / thread.
|
||||
// We call the internal alloc directly to avoid the full KeCreateThread
|
||||
// path (no user stack, no ring-3 entry - the idle thread never leaves
|
||||
// ring 0 and never calls KeExitThread).
|
||||
extern KSTATUS KeCreateProcess(const char *, PROCESS **);
|
||||
PROCESS* idleProc;
|
||||
KeCreateProcess("idle", &idleProc);
|
||||
|
||||
THREAD* idle = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
|
||||
idle->Header.Destroy = (void *)0;
|
||||
idle->ThreadId = 0;
|
||||
idle->State = ThreadReady;
|
||||
idle->Priority = PriorityLow;
|
||||
idle->QuantaConsumed = 0;
|
||||
idle->Owner = idleProc;
|
||||
idle->WaitObject = (OBJECT *)0;
|
||||
|
||||
void* kstack = MmAllocatePool(KSTACK_SIZE);
|
||||
idle->KernelStackBase = kstack;
|
||||
idle->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
|
||||
idle->TssRsp0 = idle->KernelStackTop;
|
||||
|
||||
// Initial context: RSP at top of kstack, Rip = IdleLoop.
|
||||
uintptr_t* ksp = (uintptr_t *)idle->KernelStackTop;
|
||||
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
|
||||
KCONTEXT* ctx = (KCONTEXT *)ksp;
|
||||
__stosq((unsigned __int64 *)ctx, 0, sizeof(KCONTEXT) / 8);
|
||||
ctx->Rip = (uint64_t)IdleLoop;
|
||||
idle->SavedContext = ctx;
|
||||
|
||||
gIdleThread = idle;
|
||||
gCurrentThread = idle;
|
||||
|
||||
// Don't add idle to the run queue here; KeSchedulerTick falls back to it.
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run-queue management
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeReadyThread(THREAD* thread) {
|
||||
if (!thread) return;
|
||||
thread->State = ThreadReady;
|
||||
int p = (int)thread->Priority;
|
||||
if (p < 0) p = 0;
|
||||
if (p >= PriorityCount) p = PriorityCount - 1;
|
||||
|
||||
KeAcquireSpinlock(&gSchedLock);
|
||||
ListInsertTail(&gRunQueue[p], &thread->SchedulerLink);
|
||||
KeReleaseSpinlock(&gSchedLock);
|
||||
}
|
||||
|
||||
void KeUnreadyThread(THREAD* thread) {
|
||||
if (!thread) return;
|
||||
KeAcquireSpinlock(&gSchedLock);
|
||||
ListRemove(&thread->SchedulerLink);
|
||||
KeReleaseSpinlock(&gSchedLock);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pick the highest-priority runnable thread (caller holds gSchedLock).
|
||||
// Returns gIdleThread if all queues are empty.
|
||||
// ---------------------------------------------------------------------------
|
||||
static THREAD* PickNext(void) {
|
||||
for (int p = PriorityCount - 1; p >= 0; p--) {
|
||||
if (!LIST_IS_EMPTY(&gRunQueue[p])) {
|
||||
LIST_ENTRY* node = gRunQueue[p].Fwd;
|
||||
ListRemove(node);
|
||||
return CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
}
|
||||
}
|
||||
return gIdleThread;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeSchedulerTick - called from the timer ISR every QUANTUM_MS
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeSchedulerTick(void) {
|
||||
THREAD* cur = gCurrentThread;
|
||||
if (!cur) return;
|
||||
|
||||
KeAcquireSpinlock(&gSchedLock);
|
||||
|
||||
cur->QuantaConsumed++;
|
||||
|
||||
// Demotion: CPU-bound thread exhausted its quanta budget.
|
||||
if (cur != gIdleThread && cur->QuantaConsumed >= DEMOTION_QUANTA) {
|
||||
cur->QuantaConsumed = 0;
|
||||
if (cur->Priority > PriorityLow) {
|
||||
cur->Priority = (THREAD_PRIORITY)(cur->Priority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a higher-priority thread is waiting.
|
||||
THREAD* next = PickNext();
|
||||
if (next == cur) {
|
||||
// Re-insert ourselves and continue.
|
||||
if (cur != gIdleThread) {
|
||||
ListInsertTail(&gRunQueue[cur->Priority], &cur->SchedulerLink);
|
||||
}
|
||||
KeReleaseSpinlock(&gSchedLock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch.
|
||||
cur->State = (cur == gIdleThread) ? ThreadReady : ThreadReady;
|
||||
next->State = ThreadRunning;
|
||||
gCurrentThread = next;
|
||||
|
||||
// Re-queue the outgoing thread if it is still runnable.
|
||||
if (cur != gIdleThread && cur->State == ThreadReady) {
|
||||
ListInsertTail(&gRunQueue[cur->Priority], &cur->SchedulerLink);
|
||||
}
|
||||
|
||||
KeReleaseSpinlock(&gSchedLock);
|
||||
KeSwitchContext(cur, next);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeYield
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeYield(void) {
|
||||
THREAD* cur = gCurrentThread;
|
||||
if (!cur) return;
|
||||
|
||||
KeAcquireSpinlock(&gSchedLock);
|
||||
|
||||
// Voluntary block → promote one level.
|
||||
if (cur != gIdleThread && cur->Priority < PriorityHigh) {
|
||||
cur->Priority = (THREAD_PRIORITY)(cur->Priority + 1);
|
||||
cur->QuantaConsumed = 0;
|
||||
}
|
||||
|
||||
THREAD* next = PickNext();
|
||||
if (next == cur) {
|
||||
if (cur != gIdleThread)
|
||||
ListInsertTail(&gRunQueue[cur->Priority], &cur->SchedulerLink);
|
||||
KeReleaseSpinlock(&gSchedLock);
|
||||
return;
|
||||
}
|
||||
|
||||
next->State = ThreadRunning;
|
||||
gCurrentThread = next;
|
||||
|
||||
if (cur != gIdleThread && cur->State == ThreadReady)
|
||||
ListInsertTail(&gRunQueue[cur->Priority], &cur->SchedulerLink);
|
||||
|
||||
KeReleaseSpinlock(&gSchedLock);
|
||||
KeSwitchContext(cur, next);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// ke_sched.h - Multi-Level Feedback Queue scheduler
|
||||
// Three run queues (Low / Normal / High). IRQ 0 drives KeSchedulerTick.
|
||||
#include "ke_thread.h"
|
||||
|
||||
#define QUANTUM_MS 10 /* base time slice in milliseconds */
|
||||
|
||||
// Called once by KernelMain before any threads are created.
|
||||
void KeInitializeScheduler(void);
|
||||
|
||||
// Add / remove a thread from the run queues.
|
||||
void KeReadyThread(THREAD* thread);
|
||||
void KeUnreadyThread(THREAD* thread);
|
||||
|
||||
// Called from the timer ISR every QUANTUM_MS.
|
||||
void KeSchedulerTick(void);
|
||||
|
||||
// Voluntarily yield the CPU. Picks the next runnable thread immediately.
|
||||
void KeYield(void);
|
||||
|
||||
// Returns the thread currently running on this CPU (reads GS:[0]).
|
||||
THREAD* KeCurrentThread(void);
|
||||
|
||||
// Set the current-thread GS slot. Called once per CPU at boot.
|
||||
void KeSetCurrentThread(THREAD* thread);
|
||||
@@ -0,0 +1,64 @@
|
||||
// ke_semaphore.c - counting semaphore
|
||||
#include "ke_semaphore.h"
|
||||
#include "ke_thread.h"
|
||||
#include "ke_sched.h"
|
||||
#include "ob_object.h"
|
||||
|
||||
static void SemDestroy(OBJECT* obj) { (void)obj; }
|
||||
|
||||
KSTATUS SemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out) {
|
||||
if (!out || initial < 0 || max < 1 || initial > max)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
SEMAPHORE* s = (SEMAPHORE *)ObAllocateObject(ObjectTypeSemaphore, sizeof(SEMAPHORE));
|
||||
if (!s) return STATUS_NO_MEMORY;
|
||||
s->Header.Destroy = SemDestroy;
|
||||
s->Lock = (SPINLOCK)SPINLOCK_INIT;
|
||||
s->Count = initial;
|
||||
s->MaxCount = max;
|
||||
LIST_INIT(&s->WaitList);
|
||||
*out = s;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
KSTATUS KeWaitSemaphore(SEMAPHORE* sem) {
|
||||
if (!sem) return STATUS_INVALID_PARAMETER;
|
||||
THREAD* self = KeCurrentThread();
|
||||
|
||||
for (;;) {
|
||||
KeAcquireSpinlock(&sem->Lock);
|
||||
if (sem->Count > 0) {
|
||||
sem->Count--;
|
||||
KeReleaseSpinlock(&sem->Lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
ListInsertTail(&sem->WaitList, &self->SchedulerLink);
|
||||
KeReleaseSpinlock(&sem->Lock);
|
||||
KeBlockThread(self, &sem->Header);
|
||||
}
|
||||
}
|
||||
|
||||
KSTATUS KeTrySemaphore(SEMAPHORE* sem) {
|
||||
if (!sem) return STATUS_INVALID_PARAMETER;
|
||||
KeAcquireSpinlock(&sem->Lock);
|
||||
if (sem->Count > 0) {
|
||||
sem->Count--;
|
||||
KeReleaseSpinlock(&sem->Lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
KeReleaseSpinlock(&sem->Lock);
|
||||
return STATUS_FAILURE;
|
||||
}
|
||||
|
||||
void KeSignalSemaphore(SEMAPHORE* sem) {
|
||||
if (!sem) return;
|
||||
KeAcquireSpinlock(&sem->Lock);
|
||||
if (!LIST_IS_EMPTY(&sem->WaitList)) {
|
||||
LIST_ENTRY* node = ListPopHead(&sem->WaitList);
|
||||
THREAD* waiter = CONTAINING_RECORD(node, THREAD, SchedulerLink);
|
||||
KeReleaseSpinlock(&sem->Lock);
|
||||
KeUnblockThread(waiter);
|
||||
return;
|
||||
}
|
||||
if (sem->Count < sem->MaxCount) sem->Count++;
|
||||
KeReleaseSpinlock(&sem->Lock);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
// ke_semaphore.h - counting semaphore
|
||||
#include "ob_object.h"
|
||||
#include "ke_spinlock.h"
|
||||
#include "ke_list.h"
|
||||
#include "ke_status.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct SEMAPHORE {
|
||||
OBJECT Header;
|
||||
SPINLOCK Lock;
|
||||
int32_t Count;
|
||||
int32_t MaxCount;
|
||||
LIST_ENTRY WaitList;
|
||||
} SEMAPHORE;
|
||||
|
||||
KSTATUS SemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out);
|
||||
KSTATUS KeWaitSemaphore(SEMAPHORE* semaphore);
|
||||
KSTATUS KeTrySemaphore(SEMAPHORE* semaphore);
|
||||
void KeSignalSemaphore(SEMAPHORE* semaphore);
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
// ke_spinlock.h - spinlock for short, non-sleeping critical sections
|
||||
// Uses MSVC interlocked intrinsics. Never hold across a scheduling boundary.
|
||||
#include <stdint.h>
|
||||
#include <intrin.h>
|
||||
|
||||
typedef struct SPINLOCK {
|
||||
volatile long Locked;
|
||||
} SPINLOCK;
|
||||
|
||||
#define SPINLOCK_INIT { 0 }
|
||||
|
||||
static __forceinline void KeAcquireSpinlock(SPINLOCK* sl) {
|
||||
while (_InterlockedCompareExchange(&sl->Locked, 1, 0) != 0)
|
||||
_mm_pause();
|
||||
}
|
||||
|
||||
static __forceinline void KeReleaseSpinlock(SPINLOCK* sl) {
|
||||
_InterlockedExchange(&sl->Locked, 0);
|
||||
}
|
||||
|
||||
static __forceinline int KeTryAcquireSpinlock(SPINLOCK* sl) {
|
||||
return _InterlockedCompareExchange(&sl->Locked, 1, 0) == 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// ke_status.h - kernel status codes
|
||||
// All fallible kernel functions return KSTATUS.
|
||||
// Success is always zero; failures are positive named constants.
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint32_t KSTATUS;
|
||||
|
||||
#define STATUS_SUCCESS ((KSTATUS)0x00000000)
|
||||
#define STATUS_FAILURE ((KSTATUS)0x00000001)
|
||||
#define STATUS_INVALID_PARAMETER ((KSTATUS)0x00000002)
|
||||
#define STATUS_NO_MEMORY ((KSTATUS)0x00000003)
|
||||
#define STATUS_NOT_FOUND ((KSTATUS)0x00000004)
|
||||
#define STATUS_ACCESS_DENIED ((KSTATUS)0x00000005)
|
||||
#define STATUS_ALREADY_EXISTS ((KSTATUS)0x00000006)
|
||||
#define STATUS_NOT_SUPPORTED ((KSTATUS)0x00000007)
|
||||
#define STATUS_TIMEOUT ((KSTATUS)0x00000008)
|
||||
#define STATUS_BUFFER_TOO_SMALL ((KSTATUS)0x00000009)
|
||||
#define STATUS_DEVICE_ERROR ((KSTATUS)0x0000000A)
|
||||
#define STATUS_OBJECT_DEAD ((KSTATUS)0x0000000B)
|
||||
#define STATUS_QUEUE_FULL ((KSTATUS)0x0000000C)
|
||||
#define STATUS_QUEUE_EMPTY ((KSTATUS)0x0000000D)
|
||||
#define STATUS_CHECKSUM_MISMATCH ((KSTATUS)0x0000000E)
|
||||
|
||||
#define KSUCCESS(s) ((s) == STATUS_SUCCESS)
|
||||
@@ -0,0 +1,72 @@
|
||||
; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64)
|
||||
;
|
||||
; Generated struct offsets (regenerate with gen_offsets.exe if structs change):
|
||||
; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8)
|
||||
; THREAD.SavedContext offset = 88
|
||||
; THREAD.TssRsp0 offset = 104
|
||||
; TSS64.RSP0 offset = 4
|
||||
;
|
||||
THREAD_SavedContext EQU 88
|
||||
THREAD_TssRsp0 EQU 104
|
||||
TSS_RSP0_OFFSET EQU 4
|
||||
|
||||
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
|
||||
|
||||
.CODE
|
||||
|
||||
; -------------------------------------------------------------------------
|
||||
; void KeSwitchContext(THREAD* from, THREAD* to)
|
||||
; RCX = from, RDX = to (Microsoft x64)
|
||||
; -------------------------------------------------------------------------
|
||||
KeSwitchContext PROC
|
||||
; Save non-volatile registers onto the current (outgoing) kernel stack.
|
||||
push rbx
|
||||
push rbp
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; from->SavedContext = rsp
|
||||
mov [rcx + THREAD_SavedContext], rsp
|
||||
|
||||
; rsp = to->SavedContext
|
||||
mov rsp, [rdx + THREAD_SavedContext]
|
||||
|
||||
; Update TSS.RSP0 so that the next interrupt/syscall uses the
|
||||
; incoming thread's kernel stack top.
|
||||
mov rax, [rdx + THREAD_TssRsp0]
|
||||
mov r8, gTss
|
||||
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
|
||||
|
||||
; Restore non-volatile registers of the incoming thread.
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop rbp
|
||||
pop rbx
|
||||
ret
|
||||
KeSwitchContext ENDP
|
||||
|
||||
; -------------------------------------------------------------------------
|
||||
; KeThreadEntry - landing pad for brand-new threads.
|
||||
;
|
||||
; Stack layout when we arrive here (set up by KeCreateThread):
|
||||
; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret)
|
||||
; After ret from KeSwitchContext RSP points here:
|
||||
; [rsp+00] = user RCX arg
|
||||
; [rsp+08] = user RIP
|
||||
; [rsp+16] = user CS (0x1B)
|
||||
; [rsp+24] = RFLAGS (0x202)
|
||||
; [rsp+32] = user RSP
|
||||
; [rsp+40] = user SS (0x23)
|
||||
;
|
||||
; We load RCX from the stack (first arg per MS x64), then IRETQ.
|
||||
; -------------------------------------------------------------------------
|
||||
KeThreadEntry PROC
|
||||
pop rcx ; user arg
|
||||
iretq ; pops RIP, CS, RFLAGS, RSP, SS -> ring 3
|
||||
KeThreadEntry ENDP
|
||||
|
||||
END
|
||||
@@ -0,0 +1,140 @@
|
||||
; ke_syscall.asm - INT 0x2E kernel entry (MASM64)
|
||||
;
|
||||
; On entry the CPU has pushed (ring-3 -> ring-0 transition):
|
||||
; SS, RSP, RFLAGS, CS, RIP (on the kernel stack via TSS.RSP0)
|
||||
; RAX = syscall number
|
||||
; RCX, RDX, R8, R9 = args 0-3 (MS x64)
|
||||
; R10, R11 = args 4-5 (our extension)
|
||||
|
||||
EXTERN KeSyscallDispatch:PROC
|
||||
|
||||
.CODE
|
||||
|
||||
KeSyscallEntry PROC
|
||||
sub rsp, 8 ; 16-byte align the stack frame
|
||||
|
||||
; We need to allocate 32 bytes for shadow space, plus 24 bytes for
|
||||
; the 3 stack arguments (a3, a4, a5), plus 8 bytes of padding to keep
|
||||
; the stack 16-byte aligned. Total = 64.
|
||||
sub rsp, 64
|
||||
|
||||
; Place the 5th, 6th, and 7th arguments onto the stack for KeSyscallDispatch
|
||||
mov [rsp + 32], r9 ; a3
|
||||
mov [rsp + 40], r10 ; a4
|
||||
mov [rsp + 48], r11 ; a5
|
||||
|
||||
; Shift the first 4 registers down one slot
|
||||
mov r9, r8 ; a2
|
||||
mov r8, rdx ; a1
|
||||
mov rdx, rcx ; a0
|
||||
mov rcx, rax ; nr (syscall number)
|
||||
|
||||
; Call the C dispatcher
|
||||
call KeSyscallDispatch
|
||||
|
||||
add rsp, 72 ; Clean up (64 + 8)
|
||||
iretq
|
||||
KeSyscallEntry ENDP
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Fault stubs - print a message and halt. A real kernel would do a bugcheck.
|
||||
; ---------------------------------------------------------------------------
|
||||
EXTERN DbgPrint:PROC
|
||||
|
||||
FaultMsg DB "KERNEL FAULT: vector %d at RIP %016llx", 0Ah, 0
|
||||
|
||||
KeFaultDE PROC
|
||||
sub rsp, 28h
|
||||
lea rcx, FaultMsg
|
||||
mov rdx, 0
|
||||
pop r8 ; RIP from the fault frame
|
||||
call DbgPrint
|
||||
add rsp, 28h
|
||||
@@: hlt
|
||||
jmp @B
|
||||
KeFaultDE ENDP
|
||||
|
||||
KeFaultGP PROC
|
||||
sub rsp, 28h
|
||||
lea rcx, FaultMsg
|
||||
mov rdx, 13
|
||||
pop rax ; error code
|
||||
pop r8 ; RIP
|
||||
call DbgPrint
|
||||
add rsp, 28h
|
||||
@@: hlt
|
||||
jmp @B
|
||||
KeFaultGP ENDP
|
||||
|
||||
KeFaultPF PROC
|
||||
sub rsp, 28h
|
||||
lea rcx, FaultMsg
|
||||
mov rdx, 14
|
||||
pop rax ; error code
|
||||
pop r8 ; RIP
|
||||
call DbgPrint
|
||||
add rsp, 28h
|
||||
@@: hlt
|
||||
jmp @B
|
||||
KeFaultPF ENDP
|
||||
|
||||
EXTERN KeIrqDispatch:PROC
|
||||
|
||||
IrqStub MACRO num
|
||||
PUBLIC KeIrqStub&num
|
||||
KeIrqStub&num PROC
|
||||
push rax
|
||||
mov al, num
|
||||
jmp KeIrqCommon
|
||||
KeIrqStub&num ENDP
|
||||
ENDM
|
||||
|
||||
IrqStub 0
|
||||
IrqStub 1
|
||||
IrqStub 2
|
||||
IrqStub 3
|
||||
IrqStub 4
|
||||
IrqStub 5
|
||||
IrqStub 6
|
||||
IrqStub 7
|
||||
IrqStub 8
|
||||
IrqStub 9
|
||||
IrqStub 10
|
||||
IrqStub 11
|
||||
IrqStub 12
|
||||
IrqStub 13
|
||||
IrqStub 14
|
||||
IrqStub 15
|
||||
IrqStub 16
|
||||
IrqStub 17
|
||||
IrqStub 18
|
||||
IrqStub 19
|
||||
IrqStub 20
|
||||
IrqStub 21
|
||||
IrqStub 22
|
||||
IrqStub 23
|
||||
|
||||
KeIrqCommon PROC
|
||||
; al = irq number, rax saved on stack
|
||||
; Build a minimal calling-convention-safe frame
|
||||
push rcx
|
||||
push rdx
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
movzx ecx, al ; first arg = irq number (rcx, Win64 ABI)
|
||||
sub rsp, 32 ; shadow space
|
||||
call KeIrqDispatch
|
||||
add rsp, 32
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
iretq
|
||||
KeIrqCommon ENDP
|
||||
|
||||
END
|
||||
@@ -0,0 +1,377 @@
|
||||
// ke_syscall.c - syscall dispatch
|
||||
//
|
||||
// KeSyscallEntry (MASM64) pushes all arguments onto the kernel stack and
|
||||
// calls KeSyscallDispatch. We read them from the stack frame.
|
||||
//#include "ke_syscall.h"
|
||||
#include "ke_thread.h"
|
||||
#include "ke_sched.h"
|
||||
#include "ke_process.h"
|
||||
#include "ke_mutex.h"
|
||||
#include "ke_semaphore.h"
|
||||
#include "ke_event.h"
|
||||
#include "ke_queue.h"
|
||||
#include "ke_debug.h"
|
||||
#include "mm_virt.h"
|
||||
#include "syscall_nr.h"
|
||||
#include "hal_api.h"
|
||||
#include "io_file.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// hal pointer set at boot
|
||||
static const HAL_API* gHal;
|
||||
void KeSyscallSetHal(const HAL_API* hal) { gHal = hal; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Individual syscall handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static uint64_t SyscallExitThread(uint64_t code, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
KeExitThread(KeCurrentThread(), (int)code);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallExitProcess(uint64_t code, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* t = KeCurrentThread();
|
||||
if (t && t->Owner) KeExitProcess(t->Owner, (int)code);
|
||||
KeExitThread(t, (int)code);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallYield(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
KeYield();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallSleep(uint64_t ms, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
// Simple busy-wait sleep via HAL timer.
|
||||
if (gHal) {
|
||||
uint64_t start = gHal->Timer.QueryTicks();
|
||||
while (gHal->Timer.QueryTicks() - start < ms) KeYield();
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallAlloc(uint64_t size, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
return (uint64_t)MmAllocateUserMemory((size_t)size);
|
||||
}
|
||||
|
||||
static uint64_t SyscallFree(uint64_t ptr, uint64_t size, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a2; (void)a3; (void)a4; (void)a5;
|
||||
MmFreeUserMemory((void *)ptr, (size_t)size);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallCreateThread(uint64_t entry, uint64_t arg,
|
||||
uint64_t priority, uint64_t a3,
|
||||
uint64_t a4, uint64_t a5) {
|
||||
(void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return (uint64_t)STATUS_FAILURE;
|
||||
THREAD* newThread;
|
||||
KSTATUS s = KeCreateThread(cur->Owner, entry, arg,
|
||||
(THREAD_PRIORITY)priority, &newThread);
|
||||
if (!KSUCCESS(s)) return (uint64_t)s;
|
||||
uint32_t handle;
|
||||
KeAllocateHandle(cur->Owner, &newThread->Header, &handle);
|
||||
return (uint64_t)handle;
|
||||
}
|
||||
|
||||
static uint64_t SyscallCloseHandle(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
KeCloseHandle(cur->Owner, (uint32_t)handle);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallDbgPrint(uint64_t strPtr, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
const char* s = (const char *)(uintptr_t)strPtr;
|
||||
DbgPrint(s);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
MUTEX* m; if (!KSUCCESS(MutexCreate(&m))) return 0;
|
||||
uint32_t h; KeAllocateHandle(cur->Owner, &m->Header, &h);
|
||||
ObDereferenceObject(&m->Header); // handle holds the ref
|
||||
return h;
|
||||
}
|
||||
|
||||
static uint64_t SyscallMutexAcquire(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeMutex) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KSTATUS s = KeAcquireMutex((MUTEX *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint64_t SyscallMutexRelease(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeMutex) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KeReleaseMutex((MUTEX *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallSemCreate(uint64_t initial, uint64_t max, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
SEMAPHORE* s; if (!KSUCCESS(SemaphoreCreate((int32_t)initial,(int32_t)max,&s))) return 0;
|
||||
uint32_t h; KeAllocateHandle(cur->Owner, &s->Header, &h);
|
||||
ObDereferenceObject(&s->Header);
|
||||
return h;
|
||||
}
|
||||
|
||||
static uint64_t SyscallSemWait(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeSemaphore) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KSTATUS s = KeWaitSemaphore((SEMAPHORE *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint64_t SyscallSemSignal(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeSemaphore) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KeSignalSemaphore((SEMAPHORE *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallEventCreate(uint64_t manualReset, uint64_t initial,
|
||||
uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
EVENT* e;
|
||||
EVENT_TYPE t = manualReset ? EventManualReset : EventAutoReset;
|
||||
if (!KSUCCESS(KeEventCreate(t, (uint32_t)initial, &e))) return 0;
|
||||
uint32_t h; KeAllocateHandle(cur->Owner, &e->Header, &h);
|
||||
ObDereferenceObject(&e->Header);
|
||||
return h;
|
||||
}
|
||||
|
||||
static uint64_t SyscallEventSet(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeEvent) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KeSignalEvent((EVENT *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallEventWait(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeEvent) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KSTATUS s = KeWaitForEvent((EVENT *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
QUEUE* q; if (!KSUCCESS(QueueCreate(&q))) return 0;
|
||||
uint32_t h; KeAllocateHandle(cur->Owner, &q->Header, &h);
|
||||
ObDereferenceObject(&q->Header);
|
||||
return h;
|
||||
}
|
||||
|
||||
static uint64_t SyscallQueueSend(uint64_t handle, uint64_t msgPtr,
|
||||
uint64_t blocking, uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeQueue) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KSTATUS s = KeSendMessage((QUEUE *)obj, (const QUEUE_MESSAGE *)msgPtr, (bool)blocking);
|
||||
ObDereferenceObject(obj);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint64_t SyscallQueueRecv(uint64_t handle, uint64_t msgPtr,
|
||||
uint64_t blocking, uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj || obj->Type != ObjectTypeQueue) { if(obj) ObDereferenceObject(obj); return STATUS_INVALID_PARAMETER; }
|
||||
KSTATUS s = KeReceiveMessage((QUEUE *)obj, (QUEUE_MESSAGE *)msgPtr, (bool)blocking);
|
||||
ObDereferenceObject(obj);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint64_t SyscallScreenBlit(uint64_t x, uint64_t y, uint64_t w,
|
||||
uint64_t h, uint64_t pixels, uint64_t a5) {
|
||||
(void)a5;
|
||||
if (gHal) {
|
||||
gHal->Screen.Blit((int)x, (int)y, (int)w, (int)h, (const uint32_t *)pixels);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
DbgPrint("!! SyscallScreenBlit: what?\n");
|
||||
return STATUS_FAILURE;
|
||||
}
|
||||
|
||||
static uint64_t SyscallGetTick(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
return gHal ? gHal->Timer.QueryTicks() : 0;
|
||||
}
|
||||
|
||||
static uint64_t SyscallOpen(uint64_t pathPtr, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
|
||||
FILE_OBJECT* f;
|
||||
KSTATUS s = IoOpenFile((const char *)pathPtr, &f);
|
||||
if (!KSUCCESS(s)) return 0;
|
||||
|
||||
uint32_t handle;
|
||||
s = KeAllocateHandle(cur->Owner, &f->Header, &handle);
|
||||
ObDereferenceObject(&f->Header); // handle holds the ref now
|
||||
if (!KSUCCESS(s)) return 0;
|
||||
return (uint64_t)handle;
|
||||
}
|
||||
|
||||
static uint64_t SyscallRead(uint64_t handle, uint64_t bufPtr, uint64_t size,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj) return 0;
|
||||
// FILE_OBJECT reuses ObjectTypeQueue slot - check it holds a Fs pointer.
|
||||
FILE_OBJECT* f = (FILE_OBJECT *)obj;
|
||||
size_t bytesRead = 0;
|
||||
IoReadFile(f, (void *)bufPtr, (size_t)size, &bytesRead);
|
||||
ObDereferenceObject(obj);
|
||||
return (uint64_t)bytesRead;
|
||||
}
|
||||
|
||||
static uint64_t SyscallFClose(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
KeCloseHandle(cur->Owner, (uint32_t)handle);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint64_t SyscallFSeek(uint64_t handle, uint64_t offset, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return STATUS_FAILURE;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj) return STATUS_NOT_FOUND;
|
||||
KSTATUS s = IoSeekFile((FILE_OBJECT *)obj, offset);
|
||||
ObDereferenceObject(obj);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint64_t SyscallFSize(uint64_t handle, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
THREAD* cur = KeCurrentThread();
|
||||
if (!cur || !cur->Owner) return 0;
|
||||
OBJECT* obj = KeLookupHandle(cur->Owner, (uint32_t)handle);
|
||||
if (!obj) return 0;
|
||||
uint64_t sz = IoGetFileSize((FILE_OBJECT *)obj);
|
||||
ObDereferenceObject(obj);
|
||||
return sz;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dispatch table
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef uint64_t (*SYSCALL_FN)(uint64_t, uint64_t, uint64_t,
|
||||
uint64_t, uint64_t, uint64_t);
|
||||
|
||||
static const SYSCALL_FN gSyscallTable[SYS_MAX] = {
|
||||
[SYS_EXIT_THREAD] = SyscallExitThread,
|
||||
[SYS_EXIT_PROCESS] = SyscallExitProcess,
|
||||
[SYS_SPAWN] = (SYSCALL_FN)0, // TODO: process spawn
|
||||
[SYS_SLEEP] = SyscallSleep,
|
||||
[SYS_YIELD] = SyscallYield,
|
||||
[SYS_ALLOC] = SyscallAlloc,
|
||||
[SYS_FREE] = SyscallFree,
|
||||
[SYS_CREATE_THREAD]= SyscallCreateThread,
|
||||
[SYS_OPEN_HANDLE] = (SYSCALL_FN)0, // TODO
|
||||
[SYS_CLOSE_HANDLE] = SyscallCloseHandle,
|
||||
[SYS_DBG_PRINT] = SyscallDbgPrint,
|
||||
[SYS_MUTEX_CREATE] = SyscallMutexCreate,
|
||||
[SYS_MUTEX_ACQUIRE]= SyscallMutexAcquire,
|
||||
[SYS_MUTEX_RELEASE]= SyscallMutexRelease,
|
||||
[SYS_SEM_CREATE] = SyscallSemCreate,
|
||||
[SYS_SEM_WAIT] = SyscallSemWait,
|
||||
[SYS_SEM_SIGNAL] = SyscallSemSignal,
|
||||
[SYS_EVENT_CREATE] = SyscallEventCreate,
|
||||
[SYS_EVENT_SET] = SyscallEventSet,
|
||||
[SYS_EVENT_WAIT] = SyscallEventWait,
|
||||
[SYS_QUEUE_CREATE] = SyscallQueueCreate,
|
||||
[SYS_QUEUE_SEND] = SyscallQueueSend,
|
||||
[SYS_QUEUE_RECV] = SyscallQueueRecv,
|
||||
[SYS_SCREEN_BLIT] = SyscallScreenBlit,
|
||||
[SYS_GET_TICK] = SyscallGetTick,
|
||||
[SYS_OPEN] = SyscallOpen,
|
||||
[SYS_READ] = SyscallRead,
|
||||
[SYS_FCLOSE] = SyscallFClose,
|
||||
[SYS_FSEEK] = SyscallFSeek,
|
||||
[SYS_FSIZE] = SyscallFSize,
|
||||
};
|
||||
|
||||
// Called from ke_syscall.asm - args already on the stack in order.
|
||||
uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1,
|
||||
uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
if (nr >= SYS_MAX || !gSyscallTable[nr]) return (uint64_t)STATUS_NOT_SUPPORTED;
|
||||
return gSyscallTable[nr](a0, a1, a2, a3, a4, a5);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "hal_api.h"
|
||||
|
||||
// Set the HAL API pointer for syscalls
|
||||
void KeSyscallSetHal(const HAL_API* hal);
|
||||
|
||||
// Syscall dispatcher, called from assembly entry point
|
||||
uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1,
|
||||
uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5);
|
||||
@@ -0,0 +1,157 @@
|
||||
// ke_thread.c - thread model
|
||||
#include "ke_thread.h"
|
||||
#include "ke_sched.h"
|
||||
#include "mm_virt.h"
|
||||
#include "ob_object.h"
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
|
||||
static uint32_t gNextThreadId = 1;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Initial kernel-side entry point for a new user thread
|
||||
//
|
||||
// When KeSwitchContext restores a brand-new thread, execution lands here.
|
||||
// We set up RFLAGS (enable interrupts) and IRETQ into ring 3.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Declared in ke_thread_entry.asm - see that file for the implementation.
|
||||
extern void KeThreadEntry(void);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Destructor
|
||||
// ---------------------------------------------------------------------------
|
||||
static void ThreadDestroy(OBJECT* obj) {
|
||||
THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header);
|
||||
|
||||
// Detach from owner process.
|
||||
if (thread->Owner) {
|
||||
KeAcquireSpinlock(&thread->Owner->HandleLock);
|
||||
ListRemove(&thread->ProcessLink);
|
||||
thread->Owner->ThreadCount--;
|
||||
KeReleaseSpinlock(&thread->Owner->HandleLock);
|
||||
ObDereferenceObject(&thread->Owner->Header);
|
||||
thread->Owner = (PROCESS *)0;
|
||||
}
|
||||
|
||||
// Free kernel stack.
|
||||
if (thread->KernelStackBase) {
|
||||
MmFreePool(thread->KernelStackBase);
|
||||
thread->KernelStackBase = (void *)0;
|
||||
}
|
||||
// User stack was allocated from user heap - it is up to the process to
|
||||
// track and free it. For now we leave it (shared heap, no per-process VA).
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeCreateThread
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
|
||||
THREAD_PRIORITY priority, THREAD **out) {
|
||||
if (!owner || !out) return STATUS_INVALID_PARAMETER;
|
||||
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
|
||||
if (!thread) return STATUS_NO_MEMORY;
|
||||
|
||||
thread->Header.Destroy = ThreadDestroy;
|
||||
thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1);
|
||||
thread->State = ThreadReady;
|
||||
thread->Priority = priority;
|
||||
thread->QuantaConsumed = 0;
|
||||
thread->WaitObject = (OBJECT *)0;
|
||||
|
||||
// Allocate kernel stack.
|
||||
void* kstack = MmAllocatePool(KSTACK_SIZE);
|
||||
if (!kstack) {
|
||||
ObDereferenceObject(&thread->Header);
|
||||
return STATUS_NO_MEMORY;
|
||||
}
|
||||
memset(kstack, 0, KSTACK_SIZE);
|
||||
thread->KernelStackBase = kstack;
|
||||
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
|
||||
thread->TssRsp0 = thread->KernelStackTop;
|
||||
|
||||
// Allocate user stack in the shared user heap.
|
||||
void* ustack = MmAllocateUserMemory(USTACK_SIZE);
|
||||
if (!ustack) {
|
||||
MmFreePool(kstack);
|
||||
ObDereferenceObject(&thread->Header);
|
||||
return STATUS_NO_MEMORY;
|
||||
}
|
||||
uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE;
|
||||
|
||||
thread->UserEntry = entry;
|
||||
thread->UserArg = arg;
|
||||
|
||||
// Set up the initial KCONTEXT so that when KeSwitchContext restores
|
||||
// this thread, execution lands in KeThreadEntry (in ke_switch.asm),
|
||||
// which will IRETQ into ring 3 at UserEntry.
|
||||
// The KCONTEXT sits at the top of the kernel stack.
|
||||
uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop;
|
||||
|
||||
// Push user RSP so KeThreadEntry can find it.
|
||||
*(--ksp) = userStackTop;
|
||||
|
||||
// Push the IRETQ frame: RIP, CS (ring3), RFLAGS, RSP, SS (ring3).
|
||||
// Segment selector values: 0x1B = ring-3 code, 0x23 = ring-3 data.
|
||||
*(--ksp) = 0x23ULL; // SS
|
||||
*(--ksp) = userStackTop; // RSP (already set above but IRETQ needs it)
|
||||
*(--ksp) = 0x202ULL; // RFLAGS: IF=1, reserved bit 1
|
||||
*(--ksp) = 0x1BULL; // CS
|
||||
*(--ksp) = entry; // RIP
|
||||
// RCX = arg (first argument per MS x64 ABI).
|
||||
*(--ksp) = arg; // RCX placeholder - KeThreadEntry loads it
|
||||
|
||||
// Now build the KCONTEXT that KeSwitchContext will restore.
|
||||
// Rip points to KeThreadEntry; all other regs zero; RSP = ksp.
|
||||
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
|
||||
KCONTEXT* ctx = (KCONTEXT *)ksp;
|
||||
memset(ctx, 0, sizeof(KCONTEXT));
|
||||
ctx->Rip = (uint64_t)KeThreadEntry;
|
||||
thread->SavedContext = ctx;
|
||||
|
||||
// Link into the owner process.
|
||||
ObReferenceObject(&owner->Header);
|
||||
thread->Owner = owner;
|
||||
KeAcquireSpinlock(&owner->HandleLock);
|
||||
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
|
||||
owner->ThreadCount++;
|
||||
KeReleaseSpinlock(&owner->HandleLock);
|
||||
|
||||
// Hand the thread to the scheduler.
|
||||
KeReadyThread(thread);
|
||||
|
||||
*out = thread;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeExitThread
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeExitThread(THREAD* thread, int code) {
|
||||
(void)code;
|
||||
if (!thread) return;
|
||||
|
||||
thread->State = ThreadDead;
|
||||
KeUnreadyThread(thread);
|
||||
ObDereferenceObject(&thread->Header);
|
||||
|
||||
// Yield - the scheduler will never pick this thread again.
|
||||
KeYield();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block / unblock
|
||||
// ---------------------------------------------------------------------------
|
||||
void KeBlockThread(THREAD* thread, OBJECT* waitObject) {
|
||||
thread->State = ThreadWaiting;
|
||||
thread->WaitObject = waitObject;
|
||||
KeUnreadyThread(thread);
|
||||
KeYield();
|
||||
}
|
||||
|
||||
void KeUnblockThread(THREAD* thread) {
|
||||
thread->WaitObject = (OBJECT *)0;
|
||||
thread->State = ThreadReady;
|
||||
KeReadyThread(thread);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
// ke_thread.h - thread model
|
||||
// A THREAD is the unit of scheduling. Every thread belongs to exactly one process.
|
||||
#include "ob_object.h"
|
||||
#include "ke_list.h"
|
||||
#include "ke_process.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */
|
||||
#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */
|
||||
|
||||
typedef enum THREAD_PRIORITY {
|
||||
PriorityLow = 0,
|
||||
PriorityNormal = 1,
|
||||
PriorityHigh = 2,
|
||||
PriorityCount = 3,
|
||||
} THREAD_PRIORITY;
|
||||
|
||||
typedef enum THREAD_STATE {
|
||||
ThreadReady = 0,
|
||||
ThreadRunning = 1,
|
||||
ThreadWaiting = 2,
|
||||
ThreadDead = 3,
|
||||
} THREAD_STATE;
|
||||
|
||||
// Saved register context - matches the layout pushed by KeSwitchContext.
|
||||
// The saved RSP points to this structure on the kernel stack.
|
||||
typedef struct KCONTEXT {
|
||||
uint64_t R15, R14, R13, R12;
|
||||
uint64_t Rbp, Rbx;
|
||||
uint64_t Rip; /* return address back into KeSwitchContext */
|
||||
} KCONTEXT;
|
||||
|
||||
typedef struct THREAD {
|
||||
OBJECT Header; /* must be first */
|
||||
|
||||
uint32_t ThreadId;
|
||||
THREAD_STATE State;
|
||||
THREAD_PRIORITY Priority;
|
||||
|
||||
// Quanta consumed without blocking - used by the MLFQ for demotion.
|
||||
uint32_t QuantaConsumed;
|
||||
|
||||
struct PROCESS* Owner;
|
||||
LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */
|
||||
LIST_ENTRY SchedulerLink; /* node in run queue or wait list */
|
||||
|
||||
// Kernel stack - never PAGE_USER, pointed to by TssRsp0.
|
||||
void* KernelStackBase;
|
||||
uintptr_t KernelStackTop;
|
||||
|
||||
// Saved context pointer - set by KeSwitchContext.
|
||||
KCONTEXT* SavedContext;
|
||||
|
||||
// Non-NULL while State == ThreadWaiting.
|
||||
OBJECT* WaitObject;
|
||||
|
||||
// Updated on every context switch so the CPU loads the right kernel stack.
|
||||
uintptr_t TssRsp0;
|
||||
|
||||
// Ring-3 entry point and argument for newly created threads.
|
||||
uintptr_t UserEntry;
|
||||
uintptr_t UserArg;
|
||||
} THREAD;
|
||||
|
||||
KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
|
||||
THREAD_PRIORITY priority, THREAD **out);
|
||||
|
||||
// Mark a thread dead and deschedule it permanently.
|
||||
void KeExitThread(THREAD* thread, int code);
|
||||
|
||||
// Block / unblock primitives used by sync objects.
|
||||
void KeBlockThread(THREAD* thread, OBJECT* waitObject);
|
||||
void KeUnblockThread(THREAD* thread);
|
||||
|
||||
// Assembly stub - implemented in ke_switch.asm.
|
||||
void KeSwitchContext(THREAD* from, THREAD* to);
|
||||
@@ -0,0 +1,414 @@
|
||||
// main.c - kernel entry point
|
||||
#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 "ldr_image.h"
|
||||
#include "hal_api.h"
|
||||
#include "io_file.h"
|
||||
#include "ini_parser.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
|
||||
// dynamic arrays bitch
|
||||
typedef struct {
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} Header;
|
||||
|
||||
#define ARR_INIT_CAPACITY 1
|
||||
|
||||
#define _arr_header(arr) ((Header*)(arr) - 1)
|
||||
|
||||
#define _arr_maybe_grow(arr) \
|
||||
do { \
|
||||
if (!(arr)) { \
|
||||
Header* h = MmAllocatePool(sizeof(Header) + sizeof(*(arr)) * ARR_INIT_CAPACITY); \
|
||||
h->count = 0; \
|
||||
h->capacity = ARR_INIT_CAPACITY; \
|
||||
(arr) = (void*)(h + 1); \
|
||||
} \
|
||||
Header* h = _arr_header(arr); \
|
||||
if (h->count >= h->capacity) { \
|
||||
h->capacity *= 2; \
|
||||
h = MmReallocatePool(h, sizeof(Header) + sizeof(*(arr)) * h->capacity); \
|
||||
(arr) = (void*)(h + 1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define arr_push(arr, x) \
|
||||
do { \
|
||||
_arr_maybe_grow(arr); \
|
||||
(arr)[_arr_header(arr)->count++] = (x); \
|
||||
} while (0)
|
||||
|
||||
#define arr_push_front(arr, x) \
|
||||
do { \
|
||||
_arr_maybe_grow(arr); \
|
||||
Header* h = _arr_header(arr); \
|
||||
memmove(&(arr)[1], &(arr)[0], sizeof(*(arr)) * h->count); \
|
||||
(arr)[0] = (x); \
|
||||
h->count++; \
|
||||
} while (0)
|
||||
|
||||
#define arr_remove(arr, index) \
|
||||
do { \
|
||||
if ((arr) && (index) < arr_len(arr)) { \
|
||||
Header* h = _arr_header(arr); \
|
||||
memmove(&(arr)[index], &(arr)[index + 1], sizeof(*(arr)) * (h->count - (index) - 1)); \
|
||||
h->count--; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define arr_len(arr) ((Header*)(arr) - 1)->count
|
||||
|
||||
#define arr_free(arr) MmFreePool((Header*)(arr) - 1)
|
||||
|
||||
typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API *);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase Helper: Resolve Imports for all registered modules
|
||||
// ---------------------------------------------------------------------------
|
||||
static void ResolveSystemImports(BOOT_INFO* bootInfo) {
|
||||
// Resolve imports for all loaded modules (drivers may import from hal.dll,
|
||||
// win32.dll imports from syscall.dll, etc).
|
||||
// Starting at 2 to skip the kernel and HAL base images, as per your design.
|
||||
for (uint32_t i = 2; i < bootInfo->ModuleCount; i++) {
|
||||
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("KernelMain: Initializing drivers...\n");
|
||||
|
||||
// boot drivers (provided by the bootloader)
|
||||
for (uint32_t i = 0; i < bootInfo->ModuleCount; i++) {
|
||||
const char* name = bootInfo->Modules[i].Name;
|
||||
|
||||
// Identify .sys files by extension
|
||||
size_t len = strlen(name);
|
||||
if (len < 4 || strcmp(name + len - 4, ".sys") != 0) continue;
|
||||
|
||||
DbgPrint("KernelMain: Executing DriverEntry for %s\n", name);
|
||||
|
||||
DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)(LdrGetEntryPoint(bootInfo->Modules[i].VirtBase));
|
||||
|
||||
KSTATUS ds = entry(hal);
|
||||
if (!KSUCCESS(ds)) {
|
||||
DbgPrint("KernelMain: Driver %s failed to init!\n", name);
|
||||
}
|
||||
}
|
||||
|
||||
// runtime drivers
|
||||
KSTATUS s;
|
||||
FILE_OBJECT* f;
|
||||
|
||||
s = IoOpenFile("drivers\\drivers.ini", &f);
|
||||
if (s != STATUS_SUCCESS) {
|
||||
DbgPrint("KernelMain: Failed to open drivers.ini\n");
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
|
||||
uint64_t size = IoGetFileSize(f) + 1;
|
||||
char* mem = MmAllocatePool(size);
|
||||
size_t got;
|
||||
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
|
||||
DbgPrint("KernelMain: Failed to read drivers.ini\n");
|
||||
IoCloseFile(f);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
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 manouvers
|
||||
memcpy(path, "drivers\\", 8);
|
||||
memcpy(path + 8, runtimeKeys[i], len + 1);
|
||||
|
||||
s = IoOpenFile(path, &f);
|
||||
if (s != STATUS_SUCCESS) {
|
||||
DbgPrint("KernelMain: Failed to open runtime driver: %s\n", runtimeKeys[i]);
|
||||
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("KernelMain: 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("KernelMain: Failed to load runtime driver: %s\n", runtimeKeys[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)(LdrGetEntryPoint(mappedBase));
|
||||
|
||||
KSTATUS ds = entry(hal);
|
||||
if (!KSUCCESS(ds)) {
|
||||
DbgPrint("KernelMain: 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 a DLL from disk and register it as a system module
|
||||
// ---------------------------------------------------------------------------
|
||||
static void LoadAndRegisterDll(const char* filename) {
|
||||
KSTATUS s;
|
||||
FILE_OBJECT* f;
|
||||
|
||||
// Read the DLL from disk
|
||||
s = IoOpenFile(filename, &f);
|
||||
if (s != STATUS_SUCCESS) {
|
||||
DbgPrint("KernelMain: Failed to open critical system DLL: %s\n", filename);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
|
||||
uint64_t size = IoGetFileSize(f);
|
||||
void* mem = MmAllocatePool(size); // Temporary pool for the raw file
|
||||
size_t got;
|
||||
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
|
||||
DbgPrint("KernelMain: Failed to read %s\n", filename);
|
||||
IoCloseFile(f);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
IoCloseFile(f);
|
||||
|
||||
// Map the DLL into memory
|
||||
// Passing 'true' for user-mode so init.exe can access its code
|
||||
uintptr_t mappedBase = 0;
|
||||
s = LdrLoadImage(mem, size, &mappedBase, true);
|
||||
if (!KSUCCESS(s)) {
|
||||
DbgPrint("KernelMain: Failed to map %s\n", filename);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
|
||||
MmFreePool(mem); // Free the raw file buffer, we only need the mapped image now
|
||||
|
||||
LdrRegisterModule(filename, mappedBase); // Register the module
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase Helper: Load and launch the init process from the filesystem
|
||||
// ---------------------------------------------------------------------------
|
||||
static void LaunchInitProcess(BOOT_INFO* bootInfo) {
|
||||
KSTATUS s;
|
||||
FILE_OBJECT* f;
|
||||
|
||||
// Read the executable file into a raw buffer
|
||||
s = IoOpenFile(bootInfo->CommandLine, &f);
|
||||
DbgPrint("KernelMain: IoOpenFile: %d\n", s);
|
||||
if (s != STATUS_SUCCESS) {
|
||||
DbgPrint("Failed to load file: \"%s\"!\n", bootInfo->CommandLine);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
|
||||
uint64_t size = IoGetFileSize(f);
|
||||
DbgPrint("KernelMain: IoGetFileSize: %llu\n", size);
|
||||
|
||||
void* mem = MmAllocateUserMemory(size);
|
||||
DbgPrint("KernelMain: MmAllocateUserMemory: 0x%llX\n", mem);
|
||||
|
||||
size_t got;
|
||||
s = IoReadFile(f, mem, size, &got);
|
||||
DbgPrint("KernelMain: IoReadFile: %d\n", s);
|
||||
IoCloseFile(f);
|
||||
DbgPrint("KernelMain: IoCloseFile\n");
|
||||
|
||||
// Create the process object
|
||||
PROCESS* proc;
|
||||
THREAD* thread;
|
||||
|
||||
s = KeCreateProcess("init", &proc);
|
||||
if (!KSUCCESS(s)) {
|
||||
DbgPrint("Failed to create init process. System halted.\n");
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
DbgPrint("Created init process (PID %u) with command line: %s\n", proc->ProcessId, bootInfo->CommandLine);
|
||||
|
||||
// Parse the raw PE file and map it into executable memory
|
||||
uintptr_t entryPoint = 0;
|
||||
s = LdrLoadImage(mem, size, &entryPoint, true);
|
||||
if (!KSUCCESS(s)) {
|
||||
DbgPrint("KernelMain: LdrLoadImage failed with status: %d\n", s);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
DbgPrint("KernelMain: PE Image mapped! Entry point at 0x%llX\n", (void*)entryPoint);
|
||||
|
||||
// Create the main thread, pointing it to the PE Entry Point
|
||||
s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread);
|
||||
if (!KSUCCESS(s)) {
|
||||
DbgPrint("KernelMain: Failed to create init thread. Status: %d\n", s);
|
||||
_disable(); while(1) __halt();
|
||||
}
|
||||
DbgPrint("KernelMain: Thread created and queued!\n");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main Kernel Entry
|
||||
// ---------------------------------------------------------------------------
|
||||
extern void KernelMain(BOOT_INFO* bootInfo) {
|
||||
//
|
||||
// Physical & Virtual Memory initialization
|
||||
//
|
||||
MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase);
|
||||
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)) for (;;) __halt(); // there's nothing we can do.
|
||||
const HAL_API* hal = HalGetApi();
|
||||
|
||||
KeInitDebug(hal->Screen.PrintChar, bootInfo->DebugEnabled); // Enable debug
|
||||
|
||||
KeIrqInitialize(hal);
|
||||
|
||||
KeIrqRegister(hal->Timer.IrqNumber, hal->Timer.Isr);
|
||||
KeIrqRegister(hal->Keyboard.IrqNumber, hal->Keyboard.Isr);
|
||||
|
||||
HalRegisterScheduler(KeSchedulerTick);
|
||||
|
||||
KeSyscallSetHal(hal);
|
||||
|
||||
//
|
||||
// Dynamic Linking & Driver Execution
|
||||
//
|
||||
ResolveSystemImports(bootInfo);
|
||||
InitializeDrivers(bootInfo, hal);
|
||||
|
||||
//
|
||||
// Scheduler Prep & Status Update
|
||||
//
|
||||
KeInitializeScheduler();
|
||||
// hal->Screen.Clear(0x000000);
|
||||
DbgPrint("KernelMain: initialisation complete. %u MB free.\n",
|
||||
(unsigned)(MmFreeFrameCount() * PAGE_SIZE / (1024 * 1024)));
|
||||
|
||||
DbgPrint("KernelMain: Loading system DLLs...\n");
|
||||
LoadAndRegisterDll("syscall.dll");
|
||||
LoadAndRegisterDll("win32.dll");
|
||||
|
||||
// Now that they are in memory and registered, we link them together
|
||||
// (e.g., win32.dll resolving its imports against syscall.dll)
|
||||
ResolveSystemImports(bootInfo);
|
||||
|
||||
//
|
||||
// User-Mode Launch
|
||||
//
|
||||
LaunchInitProcess(bootInfo);
|
||||
|
||||
//
|
||||
// Ignite the System
|
||||
//
|
||||
DbgPrint("KernelMain: Starting scheduler... Handing over control.\n");
|
||||
_enable();
|
||||
|
||||
while (1) {
|
||||
DbgPrint("KernelMain: Idle loop. Free memory: %u MB\n",
|
||||
(unsigned)(MmFreeFrameCount() * PAGE_SIZE / (1024 * 1024)));
|
||||
__halt(); // nothing to do
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
// ldr_image.c - PE32+ loader and export resolver
|
||||
//
|
||||
// At boot all images are already mapped by the bootloader; LdrLoadImage is
|
||||
// used for any image loaded later (currently unused - all modules come in via
|
||||
// BOOT_INFO). LdrGetExport scans the PE export directory of a registered module.
|
||||
#include "ldr_image.h"
|
||||
#include "boot_info.h"
|
||||
#include "mm_virt.h"
|
||||
#include "ke_debug.h"
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PE32+ structures (just what we need)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define PE_SIGNATURE 0x00004550UL // 'PE\0\0'
|
||||
#define IMAGE_FILE_MACHINE_AMD64 0x8664
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint16_t Magic;
|
||||
uint8_t MajorLinkerVersion;
|
||||
uint8_t MinorLinkerVersion;
|
||||
uint32_t SizeOfCode;
|
||||
uint32_t SizeOfInitializedData;
|
||||
uint32_t SizeOfUninitializedData;
|
||||
uint32_t AddressOfEntryPoint;
|
||||
uint32_t BaseOfCode;
|
||||
uint64_t ImageBase;
|
||||
uint32_t SectionAlignment;
|
||||
uint32_t FileAlignment;
|
||||
uint16_t MajorOSVersion;
|
||||
uint16_t MinorOSVersion;
|
||||
uint16_t MajorImageVersion;
|
||||
uint16_t MinorImageVersion;
|
||||
uint16_t MajorSubsystemVersion;
|
||||
uint16_t MinorSubsystemVersion;
|
||||
uint32_t Win32VersionValue;
|
||||
uint32_t SizeOfImage;
|
||||
uint32_t SizeOfHeaders;
|
||||
uint32_t CheckSum;
|
||||
uint16_t Subsystem;
|
||||
uint16_t DllCharacteristics;
|
||||
uint64_t SizeOfStackReserve;
|
||||
uint64_t SizeOfStackCommit;
|
||||
uint64_t SizeOfHeapReserve;
|
||||
uint64_t SizeOfHeapCommit;
|
||||
uint32_t LoaderFlags;
|
||||
uint32_t NumberOfRvaAndSizes;
|
||||
struct { uint32_t VirtualAddress; uint32_t Size; } DataDirectory[16];
|
||||
} OPT_HEADER64;
|
||||
|
||||
typedef struct {
|
||||
uint32_t Signature;
|
||||
uint16_t Machine;
|
||||
uint16_t NumberOfSections;
|
||||
uint32_t TimeDateStamp;
|
||||
uint32_t PointerToSymbolTable;
|
||||
uint32_t NumberOfSymbols;
|
||||
uint16_t SizeOfOptionalHeader;
|
||||
uint16_t Characteristics;
|
||||
OPT_HEADER64 OptionalHeader;
|
||||
} PE_HEADER;
|
||||
|
||||
typedef struct {
|
||||
char Name[8];
|
||||
uint32_t VirtualSize;
|
||||
uint32_t VirtualAddress;
|
||||
uint32_t SizeOfRawData;
|
||||
uint32_t PointerToRawData;
|
||||
uint32_t PointerToRelocations;
|
||||
uint32_t PointerToLinenumbers;
|
||||
uint16_t NumberOfRelocations;
|
||||
uint16_t NumberOfLinenumbers;
|
||||
uint32_t Characteristics;
|
||||
} SECTION_HEADER;
|
||||
|
||||
typedef struct {
|
||||
uint32_t Characteristics;
|
||||
uint32_t TimeDateStamp;
|
||||
uint16_t MajorVersion;
|
||||
uint16_t MinorVersion;
|
||||
uint32_t Name;
|
||||
uint32_t Base;
|
||||
uint32_t NumberOfFunctions;
|
||||
uint32_t NumberOfNames;
|
||||
uint32_t AddressOfFunctions;
|
||||
uint32_t AddressOfNames;
|
||||
uint32_t AddressOfNameOrdinals;
|
||||
} EXPORT_DIR;
|
||||
|
||||
typedef struct {
|
||||
uint32_t OriginalFirstThunk;
|
||||
uint32_t TimeDateStamp;
|
||||
uint32_t ForwarderChain;
|
||||
uint32_t Name;
|
||||
uint32_t FirstThunk;
|
||||
} IMPORT_DESC;
|
||||
|
||||
typedef struct {
|
||||
uint32_t VirtualAddress;
|
||||
uint32_t SizeOfBlock;
|
||||
} BASE_RELOC;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define IMAGE_REL_BASED_DIR64 10
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module registry
|
||||
// ---------------------------------------------------------------------------
|
||||
#define MAX_MODULES 32
|
||||
|
||||
typedef struct {
|
||||
char Name[32];
|
||||
uintptr_t Base;
|
||||
} MODULE_ENTRY;
|
||||
|
||||
static MODULE_ENTRY gModules[MAX_MODULES];
|
||||
static int gModuleCount = 0;
|
||||
|
||||
void LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) {
|
||||
for (uint32_t i = 0; i < bootInfo->ModuleCount && i < MAX_MODULES; i++) {
|
||||
strncpy(gModules[gModuleCount].Name, bootInfo->Modules[i].Name, 31);
|
||||
gModules[gModuleCount].Base = bootInfo->Modules[i].VirtBase;
|
||||
gModuleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void LdrRegisterModule(const char* name, uintptr_t base) {
|
||||
if (gModuleCount < MAX_MODULES) {
|
||||
strncpy(gModules[gModuleCount].Name, name, 31);
|
||||
gModules[gModuleCount].Base = base;
|
||||
gModuleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
static uintptr_t FindModuleBase(const char* name) {
|
||||
for (int i = 0; i < gModuleCount; i++) {
|
||||
if (_stricmp(gModules[i].Name, name) == 0) return gModules[i].Base;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export lookup
|
||||
// ---------------------------------------------------------------------------
|
||||
void* LdrGetExport(const char* moduleName, const char* functionName) {
|
||||
uintptr_t base = FindModuleBase(moduleName);
|
||||
if (!base) return (void *)0;
|
||||
|
||||
uint32_t peOffset = *(uint32_t *)(base + 0x3C);
|
||||
PE_HEADER* pe = (PE_HEADER *)(base + peOffset);
|
||||
if (pe->Signature != PE_SIGNATURE) return (void *)0;
|
||||
|
||||
OPT_HEADER64* opt = &pe->OptionalHeader;
|
||||
if (opt->NumberOfRvaAndSizes < 1) return (void *)0;
|
||||
|
||||
uint32_t expRva = opt->DataDirectory[0].VirtualAddress;
|
||||
if (!expRva) return (void *)0;
|
||||
|
||||
EXPORT_DIR* exp = (EXPORT_DIR *)(base + expRva);
|
||||
uint32_t* names = (uint32_t *)(base + exp->AddressOfNames);
|
||||
uint16_t* ordinals = (uint16_t *)(base + exp->AddressOfNameOrdinals);
|
||||
uint32_t* functions = (uint32_t *)(base + exp->AddressOfFunctions);
|
||||
|
||||
for (uint32_t i = 0; i < exp->NumberOfNames; i++) {
|
||||
const char* name = (const char *)(base + names[i]);
|
||||
if (strcmp(name, functionName) == 0) {
|
||||
uint16_t ord = ordinals[i];
|
||||
return (void *)(base + functions[ord]);
|
||||
}
|
||||
}
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Entry point lookup
|
||||
// ---------------------------------------------------------------------------
|
||||
uintptr_t LdrGetEntryPoint(uintptr_t base) {
|
||||
uint32_t pe_offset = *(uint32_t*)(base + 0x3C);
|
||||
uint32_t entry_rva = *(uint32_t*)(base + pe_offset + 0x28);
|
||||
return base + entry_rva;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Base Relocation
|
||||
// ---------------------------------------------------------------------------
|
||||
static KSTATUS LdrApplyRelocations(uintptr_t base, uintptr_t delta) {
|
||||
uint32_t peOffset = *(uint32_t *)(base + 0x3C);
|
||||
PE_HEADER* pe = (PE_HEADER *)(base + peOffset);
|
||||
OPT_HEADER64* opt = &pe->OptionalHeader;
|
||||
|
||||
if (opt->NumberOfRvaAndSizes < 6) return STATUS_SUCCESS;
|
||||
uint32_t relocRva = opt->DataDirectory[5].VirtualAddress;
|
||||
uint32_t relocSize = opt->DataDirectory[5].Size;
|
||||
if (!relocRva || !relocSize) return STATUS_SUCCESS;
|
||||
|
||||
BASE_RELOC* block = (BASE_RELOC *)(base + relocRva);
|
||||
uintptr_t end = (uintptr_t)block + relocSize;
|
||||
|
||||
while ((uintptr_t)block < end && block->SizeOfBlock > 0) {
|
||||
uint32_t count = (block->SizeOfBlock - sizeof(BASE_RELOC)) / sizeof(uint16_t);
|
||||
uint16_t* entries = (uint16_t *)(block + 1);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
int type = entries[i] >> 12;
|
||||
int offset = entries[i] & 0xFFF;
|
||||
|
||||
// For 64-bit PEs, we mostly care about DIR64 (Type 10)
|
||||
if (type == IMAGE_REL_BASED_DIR64) {
|
||||
uint64_t* ptr = (uint64_t *)(base + block->VirtualAddress + offset);
|
||||
*ptr += delta;
|
||||
}
|
||||
}
|
||||
block = (BASE_RELOC *)((uint8_t *)block + block->SizeOfBlock);
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Import resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS LdrResolveImports(uintptr_t imageBase) {
|
||||
uint32_t peOffset = *(uint32_t *)(imageBase + 0x3C);
|
||||
PE_HEADER* pe = (PE_HEADER *)(imageBase + peOffset);
|
||||
OPT_HEADER64* opt = &pe->OptionalHeader;
|
||||
|
||||
if (opt->NumberOfRvaAndSizes < 2) return STATUS_SUCCESS; // no imports
|
||||
uint32_t impRva = opt->DataDirectory[1].VirtualAddress;
|
||||
if (!impRva) return STATUS_SUCCESS;
|
||||
|
||||
IMPORT_DESC* imp = (IMPORT_DESC *)(imageBase + impRva);
|
||||
while (imp->Name) {
|
||||
const char* dllName = (const char *)(imageBase + imp->Name);
|
||||
uint64_t* thunk = (uint64_t *)(imageBase + imp->FirstThunk);
|
||||
uint64_t* origThunk = (uint64_t *)(imageBase + (imp->OriginalFirstThunk
|
||||
? imp->OriginalFirstThunk
|
||||
: imp->FirstThunk));
|
||||
while (*origThunk) {
|
||||
if (*origThunk & (1ULL << 63)) {
|
||||
// Import by ordinal - not supported.
|
||||
DbgPrint("LdrResolveImports: Ordinal imports not supported! (%s)\n", dllName);
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
const char* funcName = (const char *)(imageBase + (*origThunk & 0xFFFFFFFF) + 2);
|
||||
void* addr = LdrGetExport(dllName, funcName);
|
||||
if (!addr) {
|
||||
DbgPrint("LdrResolveImports: Function not found! (%s)\n", funcName);
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
*thunk = (uint64_t)addr;
|
||||
thunk++;
|
||||
origThunk++;
|
||||
}
|
||||
imp++;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// LdrLoadImage - load a PE from a raw buffer into virtual space.
|
||||
KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool user) {
|
||||
if (!buffer || !size || !baseOut) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
uint32_t peOffset = *(uint32_t *)((uint8_t *)buffer + 0x3C);
|
||||
PE_HEADER* pe = (PE_HEADER *)((uint8_t *)buffer + peOffset);
|
||||
if (pe->Signature != PE_SIGNATURE) return STATUS_FAILURE;
|
||||
|
||||
OPT_HEADER64* opt = &pe->OptionalHeader;
|
||||
size_t imageSize = opt->SizeOfImage;
|
||||
|
||||
void* mem = user ? MmAllocateUserMemory(imageSize) : MmAllocatePool(imageSize);
|
||||
if (!mem) return STATUS_NO_MEMORY;
|
||||
memset(mem, 0, imageSize);
|
||||
|
||||
// Copy headers and sections
|
||||
memcpy(mem, buffer, opt->SizeOfHeaders);
|
||||
SECTION_HEADER* sec = (SECTION_HEADER *)((uint8_t *)&pe->OptionalHeader + pe->SizeOfOptionalHeader);
|
||||
for (int i = 0; i < pe->NumberOfSections; i++) {
|
||||
void* dst = (uint8_t *)mem + sec[i].VirtualAddress;
|
||||
const void* src = (const uint8_t *)buffer + sec[i].PointerToRawData;
|
||||
if (sec[i].SizeOfRawData) {
|
||||
memcpy(dst, src, sec[i].SizeOfRawData < sec[i].VirtualSize ? sec[i].SizeOfRawData : sec[i].VirtualSize);
|
||||
}
|
||||
|
||||
uint64_t prot = PAGE_PRESENT | PAGE_WRITE | (user ? PAGE_USER : 0);
|
||||
MmSetPageProtect((uintptr_t)mem + sec[i].VirtualAddress, sec[i].VirtualSize, prot);
|
||||
}
|
||||
|
||||
// Apply Relocations
|
||||
uintptr_t delta = (uintptr_t)mem - opt->ImageBase;
|
||||
if (delta != 0) {
|
||||
LdrApplyRelocations((uintptr_t)mem, delta);
|
||||
}
|
||||
|
||||
// Resolve Imports
|
||||
KSTATUS status = LdrResolveImports((uintptr_t)mem);
|
||||
if (status != STATUS_SUCCESS) return status;
|
||||
|
||||
*baseOut = (uintptr_t)mem + opt->AddressOfEntryPoint;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
// ldr_image.h - minimal PE32+ loader
|
||||
// Loads an already-mapped PE image and resolves its imports.
|
||||
#include "ke_status.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Map a PE32+ image from 'buffer' into the kernel virtual address space.
|
||||
// On success, *baseOut receives the virtual base address.
|
||||
KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool user);
|
||||
|
||||
// Look up a named export in a loaded module.
|
||||
// 'moduleName' must match a name in BOOT_INFO.Modules (e.g., "hal.dll").
|
||||
void* LdrGetExport(const char* moduleName, const char* functionName);
|
||||
|
||||
// Get the entry point address of a loaded image.
|
||||
uintptr_t LdrGetEntryPoint(uintptr_t base);
|
||||
|
||||
// Resolve the import table of an already-loaded image.
|
||||
KSTATUS LdrResolveImports(uintptr_t imageBase);
|
||||
|
||||
// Called by KernelMain to register all modules from BOOT_INFO so that
|
||||
// LdrGetExport can find them.
|
||||
struct BOOT_INFO; // forward declare for the compiler to shut the fuck up
|
||||
void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo);
|
||||
void LdrRegisterModule(const char* name, uintptr_t base);
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
// memory_map.h - shared between bootx64.efi and kernel.exe
|
||||
// Describes physical memory layout passed from bootloader to kernel.
|
||||
#include <stdint.h>
|
||||
|
||||
#define MEMORY_MAP_MAGIC 0x4D454D4D41500000ULL /* 'MEMMAP\0\0' */
|
||||
#define MEMORY_MAX_REGIONS 256
|
||||
|
||||
typedef enum MEMORY_TYPE {
|
||||
MemoryUsable = 0, /* free RAM, available to the PMM */
|
||||
MemoryReserved = 1, /* firmware-reserved, do not touch */
|
||||
MemoryMappedIO = 2, /* device MMIO regions */
|
||||
MemoryFramebuffer = 3, /* GOP framebuffer */
|
||||
MemoryBootloader = 4, /* bootloader code/data (reclaimable) */
|
||||
MemoryKernel = 5, /* kernel + HAL + drivers image pages */
|
||||
} MEMORY_TYPE;
|
||||
|
||||
typedef struct MEMORY_REGION {
|
||||
uint64_t Base;
|
||||
uint64_t Length; /* in bytes, always page-aligned */
|
||||
MEMORY_TYPE Type;
|
||||
uint32_t _pad;
|
||||
} MEMORY_REGION;
|
||||
|
||||
typedef struct MEMORY_MAP {
|
||||
uint64_t Magic;
|
||||
uint32_t RegionCount;
|
||||
uint32_t _pad;
|
||||
MEMORY_REGION Regions[MEMORY_MAX_REGIONS];
|
||||
} MEMORY_MAP;
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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"
|
||||
#include <stdint.h>
|
||||
#include <stddef.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
|
||||
// ---------------------------------------------------------------------------
|
||||
#define MAX_FRAMES (256ULL * 1024) // up to 1 GB worth of 4 KB frames
|
||||
|
||||
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) {
|
||||
KeAcquireSpinlock(&gFrameStack.Lock);
|
||||
if (gFrameStack.Top < gFrameStack.Capacity) {
|
||||
gFrameStack.Stack[gFrameStack.Top++] = frame;
|
||||
}
|
||||
KeReleaseSpinlock(&gFrameStack.Lock);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Initialisation
|
||||
// ---------------------------------------------------------------------------
|
||||
void MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) {
|
||||
gPhysBase = physicalWindowBase;
|
||||
|
||||
// --- Phase 1: Find a region large enough to hold the stack array. ---
|
||||
// We need MAX_FRAMES * sizeof(PHYSICAL_ADDRESS) = 2 MB worst-case.
|
||||
// Take the first usable region that is large enough.
|
||||
const size_t stackBytes = MAX_FRAMES * sizeof(PHYSICAL_ADDRESS);
|
||||
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) {
|
||||
// Should never happen on any machine with >= 4 MB RAM.
|
||||
// Hang - there is nothing else we can do.
|
||||
for (;;) { __halt(); }
|
||||
}
|
||||
|
||||
gFrameStack.Stack = (PHYSICAL_ADDRESS *)PhysToVirt(stackPhys);
|
||||
gFrameStack.Top = 0;
|
||||
gFrameStack.Capacity = MAX_FRAMES;
|
||||
gFrameStack.Total = 0;
|
||||
gFrameStack.Lock = (SPINLOCK)SPINLOCK_INIT;
|
||||
|
||||
// --- Phase 2: Push all free 4 KB frames from MemoryUsable regions. ---
|
||||
// Skip the pages occupied by the stack itself.
|
||||
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) {
|
||||
// Skip the frames reserved for our own stack.
|
||||
if (frame >= stackPhys && frame < stackEnd) continue;
|
||||
if (gFrameStack.Top < gFrameStack.Capacity) {
|
||||
gFrameStack.Stack[gFrameStack.Top++] = frame;
|
||||
gFrameStack.Total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
PHYSICAL_ADDRESS MmAllocateFrame(void) {
|
||||
KeAcquireSpinlock(&gFrameStack.Lock);
|
||||
if (gFrameStack.Top == 0) {
|
||||
KeReleaseSpinlock(&gFrameStack.Lock);
|
||||
return (PHYSICAL_ADDRESS)0;
|
||||
}
|
||||
PHYSICAL_ADDRESS frame = gFrameStack.Stack[--gFrameStack.Top];
|
||||
KeReleaseSpinlock(&gFrameStack.Lock);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void MmFreeFrame(PHYSICAL_ADDRESS frame) {
|
||||
if (!frame || (frame & (PAGE_SIZE - 1))) return; // must be page-aligned
|
||||
PushFrame(frame);
|
||||
}
|
||||
|
||||
PHYSICAL_ADDRESS MmAllocateContiguous(size_t count) {
|
||||
if (!count) return (PHYSICAL_ADDRESS)0;
|
||||
|
||||
// Brute-force: scan the stack for 'count' consecutive frames.
|
||||
// Acceptable for small DMA buffers; not for general use.
|
||||
KeAcquireSpinlock(&gFrameStack.Lock);
|
||||
|
||||
// Build a temporary sorted view - too expensive; instead do a simple
|
||||
// linear scan for a contiguous run already on the stack (lucky case),
|
||||
// or fall back to pulling frames one by one and checking adjacency.
|
||||
// For now: pull 'count' frames and verify they are contiguous.
|
||||
// If not, return them all and report failure.
|
||||
if (gFrameStack.Top < count) {
|
||||
KeReleaseSpinlock(&gFrameStack.Lock);
|
||||
return (PHYSICAL_ADDRESS)0;
|
||||
}
|
||||
|
||||
// Attempt: take the top 'count' entries and check.
|
||||
PHYSICAL_ADDRESS base = gFrameStack.Stack[gFrameStack.Top - 1];
|
||||
int ok = 1;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
PHYSICAL_ADDRESS f = gFrameStack.Stack[gFrameStack.Top - 1 - i];
|
||||
if (f != base + i * PAGE_SIZE) { ok = 0; break; }
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
gFrameStack.Top -= count;
|
||||
KeReleaseSpinlock(&gFrameStack.Lock);
|
||||
return base;
|
||||
}
|
||||
|
||||
KeReleaseSpinlock(&gFrameStack.Lock);
|
||||
return (PHYSICAL_ADDRESS)0; // caller should retry or fail gracefully
|
||||
}
|
||||
|
||||
size_t MmFreeFrameCount(void) {
|
||||
return gFrameStack.Top;
|
||||
}
|
||||
|
||||
size_t MmTotalFrameCount(void) {
|
||||
return gFrameStack.Total;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
// mm_phys.h - physical memory manager (PMM)
|
||||
// Stack allocator over 4 KB frames, seeded from MEMORY_MAP.
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
#define PAGE_SIZE 4096ULL
|
||||
#define PAGE_MASK (~(PAGE_SIZE - 1))
|
||||
|
||||
typedef uint64_t PHYSICAL_ADDRESS;
|
||||
|
||||
// Called once by KernelMain with the MEMORY_MAP from BOOT_INFO.
|
||||
void MmInitializePhysical(const MEMORY_MAP* map,
|
||||
uint64_t physicalWindowBase);
|
||||
|
||||
// Allocate / free a single 4 KB frame.
|
||||
// MmAllocateFrame returns 0 on failure.
|
||||
PHYSICAL_ADDRESS MmAllocateFrame(void);
|
||||
void MmFreeFrame(PHYSICAL_ADDRESS frame);
|
||||
|
||||
// Allocate 'count' physically contiguous 4 KB frames.
|
||||
// Returns 0 on failure. Use sparingly (DMA, page tables).
|
||||
PHYSICAL_ADDRESS MmAllocateContiguous(size_t count);
|
||||
|
||||
size_t MmFreeFrameCount(void);
|
||||
size_t MmTotalFrameCount(void);
|
||||
@@ -0,0 +1,445 @@
|
||||
// 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 "mm_virt.h"
|
||||
#include "ke_spinlock.h"
|
||||
#include <string.h>
|
||||
#include <stddef.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
|
||||
// ---------------------------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
void* MmAllocatePool(size_t size) { return HeapAlloc(&gPoolHeap, size); }
|
||||
void MmFreePool(void* ptr) { HeapFree(&gPoolHeap, ptr); }
|
||||
void* MmReallocatePool(void* ptr, size_t newSize) { return HeapRealloc(&gPoolHeap, ptr, newSize); }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public user heap API
|
||||
// ---------------------------------------------------------------------------
|
||||
void* MmAllocateUserMemory(size_t size) { return HeapAlloc(&gUserHeap, size); }
|
||||
void MmFreeUserMemory(void* ptr, size_t size) { (void)size; HeapFree(&gUserHeap, ptr); }
|
||||
void* MmReallocateUserMemory(void* ptr, size_t newSize) { return HeapRealloc(&gUserHeap, ptr, newSize); }
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
// mm_virt.h - virtual memory manager (VMM)
|
||||
// Manages the single shared PML4, kernel pool, and user heap.
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "mm_phys.h"
|
||||
#include "ke_status.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Page flags (x86-64 PTE bits)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define PAGE_PRESENT (1ULL << 0)
|
||||
#define PAGE_WRITE (1ULL << 1)
|
||||
#define PAGE_USER (1ULL << 2)
|
||||
#define PAGE_NOCACHE (1ULL << 4)
|
||||
#define PAGE_NX (1ULL << 63)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Virtual address space constants
|
||||
// ---------------------------------------------------------------------------
|
||||
#define USER_BASE 0x0000000000001000ULL
|
||||
#define USER_LIMIT 0x00007FFFFFFFF000ULL
|
||||
|
||||
#define KERNEL_BASE 0xFFFF800000000000ULL
|
||||
#define PHYS_MAP_BASE 0xFFFF880000000000ULL
|
||||
#define POOL_BASE 0xFFFFC00000000000ULL
|
||||
#define POOL_LIMIT 0xFFFFE00000000000ULL
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Initialisation
|
||||
// ---------------------------------------------------------------------------
|
||||
// Called by KernelMain after MmInitializePhysical.
|
||||
// bootPml4Phys: the PML4 set up by the bootloader.
|
||||
// poolSize: initial reservation for kernel pool (bytes, page-aligned).
|
||||
void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize);
|
||||
|
||||
// Install the shared PML4 into CR3. Called once at the end of KernelMain init.
|
||||
void MmActivate(void);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Page mapping
|
||||
// ---------------------------------------------------------------------------
|
||||
KSTATUS MmMapPage(uintptr_t virt, PHYSICAL_ADDRESS phys, uint64_t flags);
|
||||
KSTATUS MmUnmapPage(uintptr_t virt);
|
||||
|
||||
// Modify protection flags on an already-mapped virtual range.
|
||||
// 'flags' is the complete new set of PAGE_* bits - it replaces the old flags
|
||||
// entirely (except the physical address, which is preserved).
|
||||
// Returns STATUS_NOT_FOUND if any page in the range is not mapped.
|
||||
KSTATUS MmSetPageProtect(uintptr_t virt, size_t size, uint64_t flags);
|
||||
|
||||
// Walk the page tables to translate a virtual address to physical.
|
||||
// Returns 0 if the address is not mapped.
|
||||
PHYSICAL_ADDRESS MmGetPhysicalAddress(uintptr_t virt);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kernel pool - ring 0 only, never PAGE_USER.
|
||||
// Best-fit free-list allocator, thread-safe via spinlock.
|
||||
// ---------------------------------------------------------------------------
|
||||
void* MmAllocatePool(size_t size);
|
||||
void MmFreePool(void* ptr);
|
||||
void* MmReallocatePool(void* ptr, size_t newSize);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// User heap - shared flat space, ring 3 accessible.
|
||||
// All allocations are PAGE_PRESENT | PAGE_WRITE | PAGE_USER.
|
||||
// ---------------------------------------------------------------------------
|
||||
void* MmAllocateUserMemory(size_t size);
|
||||
void MmFreeUserMemory(void* ptr, size_t size);
|
||||
void* MmReallocateUserMemory(void* ptr, size_t newSize);
|
||||
@@ -0,0 +1,35 @@
|
||||
// ob_object.c - executive object manager
|
||||
#include "ob_object.h"
|
||||
#include "mm_virt.h" // MmAllocatePool / MmFreePool
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
|
||||
OBJECT* ObAllocateObject(OBJECT_TYPE type, size_t size) {
|
||||
if (size < sizeof(OBJECT)) size = sizeof(OBJECT);
|
||||
|
||||
OBJECT* obj = (OBJECT *)MmAllocatePool(size);
|
||||
if (!obj) return (OBJECT *)0;
|
||||
|
||||
memset(obj, 0, size);
|
||||
obj->Type = type;
|
||||
obj->RefCount = 1;
|
||||
obj->Destroy = (void *)0; /* caller fills this in */
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ObReferenceObject(OBJECT* object) {
|
||||
if (!object) return;
|
||||
_InterlockedIncrement(&object->RefCount);
|
||||
}
|
||||
|
||||
void ObDereferenceObject(OBJECT* object) {
|
||||
if (!object) return;
|
||||
long prev = _InterlockedDecrement(&object->RefCount);
|
||||
if (prev == 0) {
|
||||
/* Reference count hit zero - run the destructor then free. */
|
||||
if (object->Destroy) {
|
||||
object->Destroy(object);
|
||||
}
|
||||
MmFreePool(object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
// ob_object.h - executive object model
|
||||
// Every resource the kernel tracks is a typed, reference-counted OBJECT.
|
||||
// The OBJECT header must be the first member of any derived struct.
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "ke_status.h"
|
||||
|
||||
typedef enum OBJECT_TYPE {
|
||||
ObjectTypeProcess = 1,
|
||||
ObjectTypeThread = 2,
|
||||
ObjectTypeMutex = 3,
|
||||
ObjectTypeSemaphore = 4,
|
||||
ObjectTypeEvent = 5,
|
||||
ObjectTypeQueue = 6,
|
||||
ObjectTypeFile = 7,
|
||||
} OBJECT_TYPE;
|
||||
|
||||
typedef struct OBJECT {
|
||||
OBJECT_TYPE Type;
|
||||
volatile long RefCount; /* interlocked; long = 4 bytes on MSVC x64 */
|
||||
void (*Destroy)(struct OBJECT* self);
|
||||
} OBJECT;
|
||||
|
||||
// Increment the reference count. Call whenever ownership is shared.
|
||||
void ObReferenceObject(OBJECT* object);
|
||||
|
||||
// Decrement. Calls Destroy() and frees the object when count reaches zero.
|
||||
void ObDereferenceObject(OBJECT* object);
|
||||
|
||||
// Allocate a zeroed object of the given size from kernel pool.
|
||||
// Caller must fill Type and Destroy before publishing the pointer.
|
||||
OBJECT* ObAllocateObject(OBJECT_TYPE type, size_t size);
|
||||
@@ -0,0 +1,119 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// \author (c) Marco Paland (info@paland.com)
|
||||
// 2014-2019, PALANDesign Hannover, Germany
|
||||
//
|
||||
// \license The MIT License (MIT)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
|
||||
// embedded systems with a very limited resources.
|
||||
// Use this instead of bloated standard/newlib printf.
|
||||
// These routines are thread safe and reentrant.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _PRINTF_H_
|
||||
#define _PRINTF_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// maksw: yeah that hack i mentioned earlier
|
||||
void printf_set_putchar(void (*fn)(char));
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Output a character to a custom device like UART, used by the printf() function
|
||||
* This function is declared here only. You have to write your custom implementation somewhere
|
||||
* \param character Character to output
|
||||
*/
|
||||
//void _putchar(char character);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny printf implementation
|
||||
* You have to implement _putchar if you use printf()
|
||||
* To avoid conflicts with the regular printf() API it is overridden by macro defines
|
||||
* and internal underscore-appended functions like printf_() are used
|
||||
* \param format A string that specifies the format of the output
|
||||
* \return The number of characters that are written into the array, not counting the terminating null character
|
||||
*/
|
||||
#define printf printf_
|
||||
int printf_(const char* format, ...);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny sprintf implementation
|
||||
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
|
||||
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
|
||||
* \param format A string that specifies the format of the output
|
||||
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||
*/
|
||||
#define sprintf sprintf_
|
||||
int sprintf_(char* buffer, const char* format, ...);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny snprintf/vsnprintf implementation
|
||||
* \param buffer A pointer to the buffer where to store the formatted string
|
||||
* \param count The maximum number of characters to store in the buffer, including a terminating null character
|
||||
* \param format A string that specifies the format of the output
|
||||
* \param va A value identifying a variable arguments list
|
||||
* \return The number of characters that COULD have been written into the buffer, not counting the terminating
|
||||
* null character. A value equal or larger than count indicates truncation. Only when the returned value
|
||||
* is non-negative and less than count, the string has been completely written.
|
||||
*/
|
||||
#define snprintf snprintf_
|
||||
#define vsnprintf vsnprintf_
|
||||
int snprintf_(char* buffer, size_t count, const char* format, ...);
|
||||
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny vprintf implementation
|
||||
* \param format A string that specifies the format of the output
|
||||
* \param va A value identifying a variable arguments list
|
||||
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||
*/
|
||||
#define vprintf vprintf_
|
||||
int vprintf_(const char* format, va_list va);
|
||||
|
||||
|
||||
/**
|
||||
* printf with output function
|
||||
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
|
||||
* \param out An output function which takes one character and an argument pointer
|
||||
* \param arg An argument pointer for user data passed to output function
|
||||
* \param format A string that specifies the format of the output
|
||||
* \return The number of characters that are sent to the output function, not counting the terminating null character
|
||||
*/
|
||||
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _PRINTF_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
// syscall_nr.h - system call numbers
|
||||
// Shared between kernel (ke_syscall.c) and user mode (syscall.dll).
|
||||
// Keep in sync with the dispatch table in ke_syscall.c.
|
||||
|
||||
#define SYS_EXIT_THREAD 0
|
||||
#define SYS_EXIT_PROCESS 1
|
||||
#define SYS_SPAWN 2
|
||||
#define SYS_SLEEP 3
|
||||
#define SYS_YIELD 4
|
||||
#define SYS_ALLOC 5
|
||||
#define SYS_FREE 6
|
||||
#define SYS_CREATE_THREAD 7
|
||||
#define SYS_OPEN_HANDLE 8
|
||||
#define SYS_CLOSE_HANDLE 9
|
||||
#define SYS_DBG_PRINT 10
|
||||
#define SYS_MUTEX_CREATE 11
|
||||
#define SYS_MUTEX_ACQUIRE 12
|
||||
#define SYS_MUTEX_RELEASE 13
|
||||
#define SYS_SEM_CREATE 14
|
||||
#define SYS_SEM_WAIT 15
|
||||
#define SYS_SEM_SIGNAL 16
|
||||
#define SYS_EVENT_CREATE 17
|
||||
#define SYS_EVENT_SET 18
|
||||
#define SYS_EVENT_WAIT 19
|
||||
#define SYS_QUEUE_CREATE 20
|
||||
#define SYS_QUEUE_SEND 21
|
||||
#define SYS_QUEUE_RECV 22
|
||||
#define SYS_SCREEN_BLIT 23
|
||||
#define SYS_GET_TICK 24
|
||||
#define SYS_OPEN 25
|
||||
#define SYS_READ 26
|
||||
#define SYS_FCLOSE 27
|
||||
#define SYS_FSEEK 28
|
||||
#define SYS_FSIZE 29
|
||||
|
||||
#define SYS_MAX 30
|
||||
Reference in new issue
Block a user