i think it works

This commit is contained in:
maksw committed 2026-04-30 23:52:46 +02:00
commit 0e1479b168
167 files changed
+27008

No files matched your search

+45
View File
@@ -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;
+4
View File
@@ -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);
+113
View File
@@ -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);
}
+66
View File
@@ -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);
+29
View 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);
}
+9
View File
@@ -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
+72
View File
@@ -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);
}
}
+22
View File
@@ -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);
+45
View File
@@ -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
+175
View File
@@ -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);
}
+34
View File
@@ -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
+26
View File
@@ -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]();
}
+7
View File
@@ -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
+44
View File
@@ -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;
}
+69
View File
@@ -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);
}
+18
View File
@@ -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);
+143
View File
@@ -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;
}
+41
View File
@@ -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);
+85
View File
@@ -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);
}
}
+32
View File
@@ -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);
+202
View File
@@ -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);
}
+25
View File
@@ -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);
+64
View File
@@ -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);
}
+20
View File
@@ -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);
+24
View File
@@ -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;
}
+25
View File
@@ -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)
+72
View File
@@ -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
+140
View File
@@ -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
+377
View File
@@ -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);
}
+11
View File
@@ -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);
+157
View File
@@ -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);
}
+77
View File
@@ -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);
+414
View File
@@ -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
}
}
+301
View File
@@ -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;
}
+27
View File
@@ -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);
+30
View File
@@ -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;
+162
View File
@@ -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;
}
+27
View File
@@ -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);
+445
View File
@@ -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); }
+70
View File
@@ -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);
+35
View File
@@ -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);
}
}
+33
View File
@@ -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);
+119
View File
@@ -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_
+37
View File
@@ -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