feat: scheduling, usermode, shell
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Syscall.cpp
|
||||
* SYSCALL/SYSRET setup and number-based dispatch
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Syscall.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Libraries/String.hpp>
|
||||
#include <Drivers/PS2/Keyboard.hpp>
|
||||
#include <Net/Icmp.hpp>
|
||||
#include <Net/ByteOrder.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
|
||||
// Assembly entry point
|
||||
extern "C" void SyscallEntry();
|
||||
|
||||
namespace Zenith {
|
||||
|
||||
// ---- Syscall implementations ----
|
||||
|
||||
static void Sys_Exit(int exitCode) {
|
||||
(void)exitCode;
|
||||
Sched::ExitProcess();
|
||||
}
|
||||
|
||||
static void Sys_Yield() {
|
||||
Sched::Schedule();
|
||||
}
|
||||
|
||||
static void Sys_SleepMs(uint64_t ms) {
|
||||
Timekeeping::Sleep(ms);
|
||||
}
|
||||
|
||||
static int Sys_GetPid() {
|
||||
return Sched::GetCurrentPid();
|
||||
}
|
||||
|
||||
static void Sys_Print(const char* text) {
|
||||
Kt::Print(text);
|
||||
}
|
||||
|
||||
static void Sys_Putchar(char c) {
|
||||
Kt::Putchar(c);
|
||||
}
|
||||
|
||||
static int Sys_Open(const char* path) {
|
||||
return Fs::Vfs::VfsOpen(path);
|
||||
}
|
||||
|
||||
static int Sys_Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
return Fs::Vfs::VfsRead(handle, buffer, offset, size);
|
||||
}
|
||||
|
||||
static uint64_t Sys_GetSize(int handle) {
|
||||
return Fs::Vfs::VfsGetSize(handle);
|
||||
}
|
||||
|
||||
static void Sys_Close(int handle) {
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
}
|
||||
|
||||
static int Sys_ReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||
// Get entries from VFS into a kernel-local array
|
||||
const char* kernelNames[64];
|
||||
int max = maxEntries;
|
||||
if (max > 64) max = 64;
|
||||
int count = Fs::Vfs::VfsReadDir(path, kernelNames, max);
|
||||
if (count <= 0) return count;
|
||||
|
||||
// Allocate a user-accessible page for string data via process heap
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return -1;
|
||||
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) return -1;
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
uint64_t userVa = proc->heapNext;
|
||||
proc->heapNext += 0x1000;
|
||||
Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa);
|
||||
|
||||
// Copy strings into the user page and write pointers to outNames
|
||||
uint64_t offset = 0;
|
||||
uint8_t* pageBuf = (uint8_t*)Memory::HHDM(physAddr);
|
||||
int copied = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int len = Lib::strlen(kernelNames[i]) + 1;
|
||||
if (offset + len > 0x1000) break;
|
||||
memcpy(pageBuf + offset, kernelNames[i], len);
|
||||
outNames[i] = (const char*)(userVa + offset);
|
||||
offset += len;
|
||||
copied++;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
static uint64_t Sys_Alloc(uint64_t size) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return 0;
|
||||
|
||||
// Round up to page boundary
|
||||
size = (size + 0xFFF) & ~0xFFFULL;
|
||||
if (size == 0) size = 0x1000;
|
||||
|
||||
uint64_t userVa = proc->heapNext;
|
||||
uint64_t numPages = size / 0x1000;
|
||||
|
||||
for (uint64_t i = 0; i < numPages; i++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) return 0;
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa + i * 0x1000);
|
||||
}
|
||||
|
||||
proc->heapNext += size;
|
||||
return userVa;
|
||||
}
|
||||
|
||||
static void Sys_Free(uint64_t) {
|
||||
// No-op for now (pages leak). Proper freeing can come later.
|
||||
}
|
||||
|
||||
static uint64_t Sys_GetTicks() {
|
||||
return Timekeeping::GetTicks();
|
||||
}
|
||||
|
||||
static uint64_t Sys_GetMilliseconds() {
|
||||
return Timekeeping::GetMilliseconds();
|
||||
}
|
||||
|
||||
static void Sys_GetInfo(SysInfo* outInfo) {
|
||||
if (outInfo == nullptr) return;
|
||||
|
||||
// Copy strings into fixed-size arrays (user-accessible)
|
||||
const char* name = "ZenithOS";
|
||||
const char* ver = "0.1.0";
|
||||
for (int i = 0; name[i]; i++) outInfo->osName[i] = name[i];
|
||||
outInfo->osName[8] = '\0';
|
||||
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
|
||||
outInfo->osVersion[5] = '\0';
|
||||
|
||||
outInfo->apiVersion = 2;
|
||||
outInfo->maxProcesses = Sched::MaxProcesses;
|
||||
}
|
||||
|
||||
static bool Sys_IsKeyAvailable() {
|
||||
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
||||
}
|
||||
|
||||
static void Sys_GetKey(KeyEvent* outEvent) {
|
||||
if (outEvent == nullptr) return;
|
||||
auto k = Drivers::PS2::Keyboard::GetKey();
|
||||
outEvent->scancode = k.Scancode;
|
||||
outEvent->ascii = k.Ascii;
|
||||
outEvent->pressed = k.Pressed;
|
||||
outEvent->shift = k.Shift;
|
||||
outEvent->ctrl = k.Ctrl;
|
||||
outEvent->alt = k.Alt;
|
||||
}
|
||||
|
||||
static char Sys_GetChar() {
|
||||
return Drivers::PS2::Keyboard::GetChar();
|
||||
}
|
||||
|
||||
static uint16_t g_pingSeq = 0;
|
||||
static constexpr uint16_t PING_ID = 0x2E01; // "ZE"
|
||||
|
||||
static int32_t Sys_Ping(uint32_t ipAddr, uint32_t timeoutMs) {
|
||||
uint16_t seq = g_pingSeq++;
|
||||
|
||||
Net::Icmp::ResetReply();
|
||||
Net::Icmp::SendEchoRequest(ipAddr, PING_ID, seq);
|
||||
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (!Net::Icmp::HasReply(PING_ID, seq)) {
|
||||
if (Timekeeping::GetMilliseconds() - start >= timeoutMs) {
|
||||
return -1;
|
||||
}
|
||||
Sched::Schedule();
|
||||
}
|
||||
|
||||
return (int32_t)(Timekeeping::GetMilliseconds() - start);
|
||||
}
|
||||
|
||||
// ---- Dispatch ----
|
||||
|
||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||
switch (frame->syscall_nr) {
|
||||
case SYS_EXIT:
|
||||
Sys_Exit((int)frame->arg1);
|
||||
return 0;
|
||||
case SYS_YIELD:
|
||||
Sys_Yield();
|
||||
return 0;
|
||||
case SYS_SLEEP_MS:
|
||||
Sys_SleepMs(frame->arg1);
|
||||
return 0;
|
||||
case SYS_GETPID:
|
||||
return (int64_t)Sys_GetPid();
|
||||
case SYS_PRINT:
|
||||
Sys_Print((const char*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_PUTCHAR:
|
||||
Sys_Putchar((char)frame->arg1);
|
||||
return 0;
|
||||
case SYS_OPEN:
|
||||
return (int64_t)Sys_Open((const char*)frame->arg1);
|
||||
case SYS_READ:
|
||||
return (int64_t)Sys_Read((int)frame->arg1, (uint8_t*)frame->arg2,
|
||||
frame->arg3, frame->arg4);
|
||||
case SYS_GETSIZE:
|
||||
return (int64_t)Sys_GetSize((int)frame->arg1);
|
||||
case SYS_CLOSE:
|
||||
Sys_Close((int)frame->arg1);
|
||||
return 0;
|
||||
case SYS_READDIR:
|
||||
return (int64_t)Sys_ReadDir((const char*)frame->arg1,
|
||||
(const char**)frame->arg2,
|
||||
(int)frame->arg3);
|
||||
case SYS_ALLOC:
|
||||
return (int64_t)Sys_Alloc(frame->arg1);
|
||||
case SYS_FREE:
|
||||
Sys_Free(frame->arg1);
|
||||
return 0;
|
||||
case SYS_GETTICKS:
|
||||
return (int64_t)Sys_GetTicks();
|
||||
case SYS_GETMILLISECONDS:
|
||||
return (int64_t)Sys_GetMilliseconds();
|
||||
case SYS_GETINFO:
|
||||
Sys_GetInfo((SysInfo*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_ISKEYAVAILABLE:
|
||||
return (int64_t)Sys_IsKeyAvailable();
|
||||
case SYS_GETKEY:
|
||||
Sys_GetKey((KeyEvent*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_GETCHAR:
|
||||
return (int64_t)Sys_GetChar();
|
||||
case SYS_PING:
|
||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- SYSCALL MSR initialization ----
|
||||
|
||||
void InitializeSyscalls() {
|
||||
// Enable SYSCALL/SYSRET in EFER
|
||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
||||
efer |= 1; // SCE bit (Syscall Enable)
|
||||
Hal::WriteMSR(Hal::IA32_EFER, efer);
|
||||
|
||||
// STAR: kernel CS in [47:32], sysret base in [63:48]
|
||||
// SYSCALL: CS=0x08, SS=0x10
|
||||
// SYSRET: CS=0x10+16=0x20|RPL3=0x23, SS=0x10+8=0x18|RPL3=0x1B
|
||||
uint64_t star = (0x0010ULL << 48) | (0x0008ULL << 32);
|
||||
Hal::WriteMSR(Hal::IA32_STAR, star);
|
||||
|
||||
// LSTAR: SYSCALL entry point
|
||||
Hal::WriteMSR(Hal::IA32_LSTAR, (uint64_t)SyscallEntry);
|
||||
|
||||
// FMASK: mask IF on SYSCALL entry (bit 9 = IF)
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 20 syscalls)";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Syscall.hpp
|
||||
* ZenithOS syscall definitions -- shared between kernel and programs
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Zenith {
|
||||
|
||||
// Syscall numbers
|
||||
static constexpr uint64_t SYS_EXIT = 0;
|
||||
static constexpr uint64_t SYS_YIELD = 1;
|
||||
static constexpr uint64_t SYS_SLEEP_MS = 2;
|
||||
static constexpr uint64_t SYS_GETPID = 3;
|
||||
static constexpr uint64_t SYS_PRINT = 4;
|
||||
static constexpr uint64_t SYS_PUTCHAR = 5;
|
||||
static constexpr uint64_t SYS_OPEN = 6;
|
||||
static constexpr uint64_t SYS_READ = 7;
|
||||
static constexpr uint64_t SYS_GETSIZE = 8;
|
||||
static constexpr uint64_t SYS_CLOSE = 9;
|
||||
static constexpr uint64_t SYS_READDIR = 10;
|
||||
static constexpr uint64_t SYS_ALLOC = 11;
|
||||
static constexpr uint64_t SYS_FREE = 12;
|
||||
static constexpr uint64_t SYS_GETTICKS = 13;
|
||||
static constexpr uint64_t SYS_GETMILLISECONDS = 14;
|
||||
static constexpr uint64_t SYS_GETINFO = 15;
|
||||
static constexpr uint64_t SYS_ISKEYAVAILABLE = 16;
|
||||
static constexpr uint64_t SYS_GETKEY = 17;
|
||||
static constexpr uint64_t SYS_GETCHAR = 18;
|
||||
static constexpr uint64_t SYS_PING = 19;
|
||||
|
||||
struct SysInfo {
|
||||
char osName[32];
|
||||
char osVersion[32];
|
||||
uint32_t apiVersion;
|
||||
uint32_t maxProcesses;
|
||||
};
|
||||
|
||||
struct KeyEvent {
|
||||
uint8_t scancode;
|
||||
char ascii;
|
||||
bool pressed;
|
||||
bool shift;
|
||||
bool ctrl;
|
||||
bool alt;
|
||||
};
|
||||
|
||||
// Stack frame pushed by SyscallEntry.asm
|
||||
struct SyscallFrame {
|
||||
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
|
||||
uint64_t arg6, arg5, arg4, arg3, arg2, arg1;
|
||||
uint64_t syscall_nr;
|
||||
uint64_t user_rflags, user_rip, user_rsp;
|
||||
};
|
||||
|
||||
// Kernel-only: set up SYSCALL MSRs and initialize dispatch
|
||||
void InitializeSyscalls();
|
||||
|
||||
}
|
||||
@@ -153,7 +153,7 @@ namespace Drivers::PS2 {
|
||||
SendCommand(CmdReadConfig);
|
||||
config = ReadData();
|
||||
|
||||
config |= ConfigPort1Interrupt;
|
||||
config |= ConfigPort1Interrupt | ConfigPort1Translation;
|
||||
if (g_DualChannel) {
|
||||
config |= ConfigPort2Interrupt;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Ramdisk.cpp
|
||||
* USTAR tar-based ramdisk filesystem backed by Limine modules
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Ramdisk.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Libraries/String.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
namespace Fs::Ramdisk {
|
||||
|
||||
static FileEntry fileTable[MaxFiles];
|
||||
static int fileCount = 0;
|
||||
|
||||
static uint64_t OctalToUint(const char* str, int len) {
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < len && str[i] != '\0' && str[i] != ' '; i++) {
|
||||
result = result * 8 + (str[i] - '0');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool StrEqual(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
if (*a != *b) return false;
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
static int StrLen(const char* s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool StartsWith(const char* str, const char* prefix) {
|
||||
while (*prefix) {
|
||||
if (*str != *prefix) return false;
|
||||
str++;
|
||||
prefix++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Initialize(void* moduleData, uint64_t moduleSize) {
|
||||
Kt::KernelLogStream(Kt::OK, "Ramdisk") << "Parsing USTAR archive (" << moduleSize << " bytes)";
|
||||
|
||||
uint8_t* ptr = (uint8_t*)moduleData;
|
||||
uint8_t* end = ptr + moduleSize;
|
||||
fileCount = 0;
|
||||
|
||||
while (ptr + 512 <= end && fileCount < MaxFiles) {
|
||||
// Check for end-of-archive (two consecutive zero blocks)
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
if (ptr[i] != 0) {
|
||||
allZero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allZero) break;
|
||||
|
||||
// Verify USTAR magic at offset 257
|
||||
const char* magic = (const char*)(ptr + 257);
|
||||
if (magic[0] != 'u' || magic[1] != 's' || magic[2] != 't' ||
|
||||
magic[3] != 'a' || magic[4] != 'r') {
|
||||
Kt::KernelLogStream(Kt::WARNING, "Ramdisk") << "Invalid USTAR magic, stopping parse";
|
||||
break;
|
||||
}
|
||||
|
||||
// File name at offset 0 (100 bytes)
|
||||
const char* name = (const char*)ptr;
|
||||
// File size at offset 124 (12 bytes, octal ASCII)
|
||||
uint64_t size = OctalToUint((const char*)(ptr + 124), 12);
|
||||
// Type flag at offset 156
|
||||
char typeFlag = (char)ptr[156];
|
||||
|
||||
FileEntry& entry = fileTable[fileCount];
|
||||
|
||||
// Copy name
|
||||
int nameLen = 0;
|
||||
while (nameLen < MaxNameLen - 1 && name[nameLen] != '\0') {
|
||||
entry.name[nameLen] = name[nameLen];
|
||||
nameLen++;
|
||||
}
|
||||
entry.name[nameLen] = '\0';
|
||||
|
||||
// Strip leading "./" if present
|
||||
if (entry.name[0] == '.' && entry.name[1] == '/') {
|
||||
char temp[MaxNameLen];
|
||||
int srcIdx = 2;
|
||||
int dstIdx = 0;
|
||||
while (entry.name[srcIdx] && dstIdx < MaxNameLen - 1) {
|
||||
temp[dstIdx++] = entry.name[srcIdx++];
|
||||
}
|
||||
temp[dstIdx] = '\0';
|
||||
for (int i = 0; i <= dstIdx; i++) {
|
||||
entry.name[i] = temp[i];
|
||||
}
|
||||
}
|
||||
|
||||
entry.isDirectory = (typeFlag == '5');
|
||||
entry.size = size;
|
||||
|
||||
// Data starts at next 512-byte block
|
||||
entry.data = ptr + 512;
|
||||
|
||||
// Skip entries that are just the root "." or empty name
|
||||
if (entry.name[0] == '\0' || (entry.name[0] == '.' && entry.name[1] == '\0')) {
|
||||
// Advance past header + data blocks
|
||||
uint64_t dataBlocks = (size + 511) / 512;
|
||||
ptr += 512 + dataBlocks * 512;
|
||||
continue;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::INFO, "Ramdisk") << " " << entry.name
|
||||
<< " (" << entry.size << " bytes"
|
||||
<< (entry.isDirectory ? ", dir" : "") << ")";
|
||||
|
||||
fileCount++;
|
||||
|
||||
// Advance past header + data (rounded up to 512-byte blocks)
|
||||
uint64_t dataBlocks = (size + 511) / 512;
|
||||
ptr += 512 + dataBlocks * 512;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Ramdisk") << "Loaded " << fileCount << " entries";
|
||||
}
|
||||
|
||||
int Open(const char* path) {
|
||||
// Normalize: skip leading '/'
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
if (StrEqual(fileTable[i].name, path)) {
|
||||
return i;
|
||||
}
|
||||
// Also try matching with trailing slash stripped from table entry
|
||||
int entryLen = StrLen(fileTable[i].name);
|
||||
if (entryLen > 0 && fileTable[i].name[entryLen - 1] == '/') {
|
||||
// Compare without trailing slash
|
||||
bool match = true;
|
||||
int pathLen = StrLen(path);
|
||||
if (pathLen == entryLen - 1) {
|
||||
for (int j = 0; j < pathLen; j++) {
|
||||
if (path[j] != fileTable[i].name[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
if (handle < 0 || handle >= fileCount) return -1;
|
||||
|
||||
const FileEntry& entry = fileTable[handle];
|
||||
if (offset >= entry.size) return 0;
|
||||
|
||||
uint64_t bytesToRead = size;
|
||||
if (offset + bytesToRead > entry.size) {
|
||||
bytesToRead = entry.size - offset;
|
||||
}
|
||||
|
||||
memcpy(buffer, entry.data + offset, bytesToRead);
|
||||
return (int)bytesToRead;
|
||||
}
|
||||
|
||||
uint64_t GetSize(int handle) {
|
||||
if (handle < 0 || handle >= fileCount) return 0;
|
||||
return fileTable[handle].size;
|
||||
}
|
||||
|
||||
void Close(int handle) {
|
||||
// No-op for ramdisk: files are memory-mapped and read-only
|
||||
(void)handle;
|
||||
}
|
||||
|
||||
int ReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||
// Normalize path: skip leading '/'
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
int pathLen = StrLen(path);
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < fileCount && count < maxEntries; i++) {
|
||||
const char* entryName = fileTable[i].name;
|
||||
|
||||
if (pathLen == 0) {
|
||||
// Root directory: find entries without '/' in them (or only trailing '/')
|
||||
bool hasSlash = false;
|
||||
int entryLen = StrLen(entryName);
|
||||
for (int j = 0; j < entryLen; j++) {
|
||||
if (entryName[j] == '/' && j < entryLen - 1) {
|
||||
hasSlash = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasSlash) {
|
||||
outNames[count++] = entryName;
|
||||
}
|
||||
} else {
|
||||
// Subdirectory: match entries starting with "path/"
|
||||
// and that are direct children (no additional '/' beyond the prefix)
|
||||
if (!StartsWith(entryName, path)) continue;
|
||||
|
||||
// Check that path prefix is followed by '/'
|
||||
char separator = entryName[pathLen];
|
||||
if (separator != '/') continue;
|
||||
|
||||
// Check it's a direct child (no more '/' except trailing)
|
||||
const char* rest = entryName + pathLen + 1;
|
||||
int restLen = StrLen(rest);
|
||||
bool hasDeepSlash = false;
|
||||
for (int j = 0; j < restLen; j++) {
|
||||
if (rest[j] == '/' && j < restLen - 1) {
|
||||
hasDeepSlash = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasDeepSlash && restLen > 0) {
|
||||
outNames[count++] = entryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int GetFileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Ramdisk.hpp
|
||||
* USTAR tar-based ramdisk filesystem backed by Limine modules
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Fs::Ramdisk {
|
||||
|
||||
static constexpr int MaxFiles = 128;
|
||||
static constexpr int MaxNameLen = 100;
|
||||
|
||||
struct FileEntry {
|
||||
char name[MaxNameLen];
|
||||
uint8_t* data;
|
||||
uint64_t size;
|
||||
bool isDirectory;
|
||||
};
|
||||
|
||||
void Initialize(void* moduleData, uint64_t moduleSize);
|
||||
|
||||
int Open(const char* path);
|
||||
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t GetSize(int handle);
|
||||
void Close(int handle);
|
||||
|
||||
int ReadDir(const char* path, const char** outNames, int maxEntries);
|
||||
int GetFileCount();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Vfs.cpp
|
||||
* Virtual File System with numerical logical drive identifiers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Vfs.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
namespace Fs::Vfs {
|
||||
|
||||
struct HandleEntry {
|
||||
bool inUse;
|
||||
int driveNumber;
|
||||
int localHandle;
|
||||
};
|
||||
|
||||
static FsDriver* driveTable[MaxDrives];
|
||||
static HandleEntry handleTable[MaxHandles];
|
||||
|
||||
// Parse "N:/path" into drive number and local path.
|
||||
// Returns true on success, sets outDrive and outPath.
|
||||
static bool ParsePath(const char* path, int& outDrive, const char*& outPath) {
|
||||
if (path == nullptr) return false;
|
||||
|
||||
// Parse decimal drive number before ':'
|
||||
int drive = 0;
|
||||
int i = 0;
|
||||
bool hasDigit = false;
|
||||
|
||||
while (path[i] >= '0' && path[i] <= '9') {
|
||||
drive = drive * 10 + (path[i] - '0');
|
||||
hasDigit = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!hasDigit) return false;
|
||||
if (path[i] != ':') return false;
|
||||
|
||||
// Everything after "N:" is the local path
|
||||
outDrive = drive;
|
||||
outPath = &path[i + 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static int AllocHandle() {
|
||||
for (int i = 0; i < MaxHandles; i++) {
|
||||
if (!handleTable[i].inUse) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
for (int i = 0; i < MaxDrives; i++) {
|
||||
driveTable[i] = nullptr;
|
||||
}
|
||||
for (int i = 0; i < MaxHandles; i++) {
|
||||
handleTable[i].inUse = false;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives, " << MaxHandles << " handles)";
|
||||
}
|
||||
|
||||
int RegisterDrive(int driveNumber, FsDriver* driver) {
|
||||
if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
|
||||
if (driver == nullptr) return -1;
|
||||
|
||||
driveTable[driveNumber] = driver;
|
||||
Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VfsOpen(const char* path) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Invalid path format";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Drive " << drive << " not registered";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int localHandle = driveTable[drive]->Open(localPath);
|
||||
if (localHandle < 0) return -1;
|
||||
|
||||
int globalHandle = AllocHandle();
|
||||
if (globalHandle < 0) {
|
||||
driveTable[drive]->Close(localHandle);
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "No free handles";
|
||||
return -1;
|
||||
}
|
||||
|
||||
handleTable[globalHandle].inUse = true;
|
||||
handleTable[globalHandle].driveNumber = drive;
|
||||
handleTable[globalHandle].localHandle = localHandle;
|
||||
|
||||
return globalHandle;
|
||||
}
|
||||
|
||||
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return -1;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
return driveTable[entry.driveNumber]->Read(entry.localHandle, buffer, offset, size);
|
||||
}
|
||||
|
||||
uint64_t VfsGetSize(int handle) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return 0;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
return driveTable[entry.driveNumber]->GetSize(entry.localHandle);
|
||||
}
|
||||
|
||||
void VfsClose(int handle) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
driveTable[entry.driveNumber]->Close(entry.localHandle);
|
||||
entry.inUse = false;
|
||||
}
|
||||
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Invalid path format for ReadDir";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Drive " << drive << " not registered";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return driveTable[drive]->ReadDir(localPath, outNames, maxEntries);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Vfs.hpp
|
||||
* Virtual File System with numerical logical drive identifiers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Fs::Vfs {
|
||||
|
||||
static constexpr int MaxDrives = 16;
|
||||
static constexpr int MaxHandles = 64;
|
||||
|
||||
struct FsDriver {
|
||||
int (*Open)(const char* path);
|
||||
int (*Read)(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t (*GetSize)(int handle);
|
||||
void (*Close)(int handle);
|
||||
int (*ReadDir)(const char* path, const char** outNames, int maxEntries);
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
int RegisterDrive(int driveNumber, FsDriver* driver);
|
||||
|
||||
int VfsOpen(const char* path);
|
||||
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t VfsGetSize(int handle);
|
||||
void VfsClose(int handle);
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ section .text ; Text/code section
|
||||
|
||||
global ReloadSegments
|
||||
global LoadGDT
|
||||
global LoadTR
|
||||
|
||||
LoadGDT:
|
||||
lgdt [rdi] ; Run LGDT on the contents of 1st C parameter
|
||||
@@ -28,4 +29,9 @@ ReloadSegments:
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
ret
|
||||
ret
|
||||
|
||||
LoadTR:
|
||||
mov ax, 0x28 ; TSS selector
|
||||
ltr ax
|
||||
ret
|
||||
|
||||
+47
-14
@@ -1,43 +1,76 @@
|
||||
/*
|
||||
* gdt.hpp
|
||||
* gdt.cpp
|
||||
* Intel Global Descriptor Table
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "GDT.hpp"
|
||||
#include "../Terminal/Terminal.hpp"
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
namespace Hal {
|
||||
using namespace Kt;
|
||||
|
||||
GDTPointer gdtPointer{};
|
||||
BasicGDT kernelGDT{};
|
||||
|
||||
void PrepareGDT() {
|
||||
kernelGDT = {
|
||||
{0xFFFF, 0, 0, 0x00, 0x00, 0},
|
||||
{0xFFFF, 0, 0, 0x9A, 0xA0, 0},
|
||||
{0xFFFF, 0, 0, 0x92, 0xA0, 0},
|
||||
{0xFFFF, 0, 0, 0x9A, 0xA0, 0},
|
||||
{0xFFFF, 0, 0, 0x92, 0xA0, 0},
|
||||
TSS64 g_tss{};
|
||||
|
||||
{0, 0, 0, 0xFA, 0x00, 0x0},
|
||||
void PrepareGDT() {
|
||||
// Zero the TSS
|
||||
memset(&g_tss, 0, sizeof(g_tss));
|
||||
g_tss.iopbOffset = sizeof(TSS64);
|
||||
|
||||
kernelGDT = {
|
||||
{0xFFFF, 0, 0, 0x00, 0x00, 0}, // Null
|
||||
{0xFFFF, 0, 0, 0x9A, 0xA0, 0}, // KernelCode (DPL=0, code, 64-bit)
|
||||
{0xFFFF, 0, 0, 0x92, 0xA0, 0}, // KernelData (DPL=0, data)
|
||||
{0xFFFF, 0, 0, 0xF2, 0xA0, 0}, // UserData (DPL=3, data)
|
||||
{0xFFFF, 0, 0, 0xFA, 0xA0, 0}, // UserCode (DPL=3, code, 64-bit)
|
||||
{0, 0, 0, 0, 0, 0}, // TSS low (filled below)
|
||||
{0, 0, 0, 0, 0, 0}, // TSS high (filled below)
|
||||
};
|
||||
|
||||
|
||||
// Encode 16-byte TSS descriptor
|
||||
uint64_t base = (uint64_t)&g_tss;
|
||||
uint32_t limit = sizeof(TSS64) - 1;
|
||||
|
||||
// Low 8 bytes (normal GDT entry format)
|
||||
kernelGDT.TSS.LimitLow = limit & 0xFFFF;
|
||||
kernelGDT.TSS.BaseLow = base & 0xFFFF;
|
||||
kernelGDT.TSS.BaseMiddle = (base >> 16) & 0xFF;
|
||||
kernelGDT.TSS.AccessByte = 0x89; // Present, 64-bit TSS Available
|
||||
kernelGDT.TSS.GranularityByte = (limit >> 16) & 0x0F;
|
||||
kernelGDT.TSS.BaseHigh = (base >> 24) & 0xFF;
|
||||
|
||||
// High 8 bytes (base[63:32] + reserved)
|
||||
uint32_t baseUpper = (uint32_t)(base >> 32);
|
||||
kernelGDT.TSSHigh.LimitLow = baseUpper & 0xFFFF;
|
||||
kernelGDT.TSSHigh.BaseLow = (baseUpper >> 16) & 0xFFFF;
|
||||
kernelGDT.TSSHigh.BaseMiddle = 0;
|
||||
kernelGDT.TSSHigh.AccessByte = 0;
|
||||
kernelGDT.TSSHigh.GranularityByte = 0;
|
||||
kernelGDT.TSSHigh.BaseHigh = 0;
|
||||
|
||||
gdtPointer = GDTPointer{
|
||||
.Size = sizeof(kernelGDT) - 1,
|
||||
.GDTAddress = (uint64_t)&kernelGDT
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Helpers implemented in gdt.asm
|
||||
extern "C" void LoadGDT(GDTPointer *gdtPointer);
|
||||
extern "C" void ReloadSegments();
|
||||
|
||||
extern "C" void LoadTR();
|
||||
|
||||
void BridgeLoadGDT() {
|
||||
LoadGDT(&gdtPointer);
|
||||
ReloadSegments();
|
||||
|
||||
KernelLogStream(DEBUG, "Hal") << "Set new GDT (0x" << base::hex << (uint64_t)&kernelGDT << ")";
|
||||
}
|
||||
};
|
||||
|
||||
void LoadTSS() {
|
||||
LoadTR();
|
||||
KernelLogStream(OK, "Hal") << "Loaded TSS (selector 0x28)";
|
||||
}
|
||||
};
|
||||
|
||||
+33
-13
@@ -17,25 +17,45 @@ namespace Hal {
|
||||
uint8_t BaseMiddle;
|
||||
uint8_t AccessByte;
|
||||
uint8_t GranularityByte;
|
||||
uint8_t BaseHigh;
|
||||
uint8_t BaseHigh;
|
||||
}__attribute__((packed));
|
||||
|
||||
|
||||
struct BasicGDT {
|
||||
GDTEntry Null;
|
||||
GDTEntry KernelCode;
|
||||
GDTEntry KernelData;
|
||||
GDTEntry UserCode;
|
||||
GDTEntry UserData;
|
||||
|
||||
// Task State Segment
|
||||
GDTEntry TSS;
|
||||
GDTEntry Null; // 0x00
|
||||
GDTEntry KernelCode; // 0x08
|
||||
GDTEntry KernelData; // 0x10
|
||||
GDTEntry UserData; // 0x18 (before UserCode for SYSRET)
|
||||
GDTEntry UserCode; // 0x20
|
||||
GDTEntry TSS; // 0x28 (low 8 bytes of 16-byte TSS descriptor)
|
||||
GDTEntry TSSHigh; // 0x30 (high 8 bytes of 16-byte TSS descriptor)
|
||||
}__attribute__((packed));
|
||||
|
||||
|
||||
struct GDTPointer {
|
||||
uint16_t Size;
|
||||
uint64_t GDTAddress;
|
||||
}__attribute__((packed));
|
||||
|
||||
|
||||
struct TSS64 {
|
||||
uint32_t reserved0;
|
||||
uint64_t rsp0;
|
||||
uint64_t rsp1;
|
||||
uint64_t rsp2;
|
||||
uint64_t reserved1;
|
||||
uint64_t ist1;
|
||||
uint64_t ist2;
|
||||
uint64_t ist3;
|
||||
uint64_t ist4;
|
||||
uint64_t ist5;
|
||||
uint64_t ist6;
|
||||
uint64_t ist7;
|
||||
uint64_t reserved2;
|
||||
uint16_t reserved3;
|
||||
uint16_t iopbOffset;
|
||||
}__attribute__((packed));
|
||||
|
||||
extern TSS64 g_tss;
|
||||
|
||||
void BridgeLoadGDT();
|
||||
void PrepareGDT();
|
||||
};
|
||||
void LoadTSS();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* MSR.hpp
|
||||
* Model-Specific Register read/write helpers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hal {
|
||||
|
||||
inline uint64_t ReadMSR(uint32_t msr) {
|
||||
uint32_t lo, hi;
|
||||
asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
|
||||
return ((uint64_t)hi << 32) | lo;
|
||||
}
|
||||
|
||||
inline void WriteMSR(uint32_t msr, uint64_t value) {
|
||||
uint32_t lo = (uint32_t)(value & 0xFFFFFFFF);
|
||||
uint32_t hi = (uint32_t)(value >> 32);
|
||||
asm volatile("wrmsr" : : "a"(lo), "d"(hi), "c"(msr));
|
||||
}
|
||||
|
||||
// Well-known MSR addresses
|
||||
static constexpr uint32_t IA32_EFER = 0xC0000080;
|
||||
static constexpr uint32_t IA32_STAR = 0xC0000081;
|
||||
static constexpr uint32_t IA32_LSTAR = 0xC0000082;
|
||||
static constexpr uint32_t IA32_FMASK = 0xC0000084;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
;
|
||||
; SyscallEntry.asm
|
||||
; SYSCALL/SYSRET entry point and user-mode transition
|
||||
; Copyright (c) 2025 Daniel Hammer
|
||||
;
|
||||
|
||||
[bits 64]
|
||||
section .text
|
||||
|
||||
extern SyscallDispatch
|
||||
extern g_kernelRsp
|
||||
|
||||
; ============================================================
|
||||
; SyscallEntry — called by the SYSCALL instruction
|
||||
; RCX = user RIP, R11 = user RFLAGS, RAX = syscall number
|
||||
; Args: RDI, RSI, RDX, R10, R8, R9
|
||||
; Interrupts are masked (FMASK clears IF)
|
||||
; ============================================================
|
||||
global SyscallEntry
|
||||
SyscallEntry:
|
||||
mov [rel g_userRsp], rsp ; stash user RSP
|
||||
mov rsp, [rel g_kernelRsp] ; switch to kernel stack
|
||||
|
||||
; Build SyscallFrame on kernel stack (push order matches struct)
|
||||
push qword [rel g_userRsp] ; user_rsp
|
||||
push rcx ; user_rip
|
||||
push r11 ; user_rflags
|
||||
push rax ; syscall_nr
|
||||
push rdi ; arg1
|
||||
push rsi ; arg2
|
||||
push rdx ; arg3
|
||||
push r10 ; arg4
|
||||
push r8 ; arg5
|
||||
push r9 ; arg6
|
||||
|
||||
; Callee-saved registers (preserve for user)
|
||||
push rbx
|
||||
push rbp
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
|
||||
sti ; safe to take interrupts now
|
||||
|
||||
mov rdi, rsp ; arg1 = pointer to SyscallFrame
|
||||
call SyscallDispatch ; returns int64_t in rax
|
||||
|
||||
cli ; disable interrupts for sysret
|
||||
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop rbp
|
||||
pop rbx
|
||||
|
||||
add rsp, 56 ; skip arg6..arg1 (6*8) + syscall_nr (1*8) = 56
|
||||
|
||||
pop r11 ; user RFLAGS
|
||||
pop rcx ; user RIP
|
||||
pop rsp ; user RSP
|
||||
|
||||
o64 sysret
|
||||
|
||||
; ============================================================
|
||||
; JumpToUserMode — initial transition to ring 3 via IRETQ
|
||||
; RDI = user RIP (entry point)
|
||||
; RSI = user RSP (top of user stack)
|
||||
; ============================================================
|
||||
global JumpToUserMode
|
||||
JumpToUserMode:
|
||||
mov ax, 0x1B ; UserData | RPL3
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
|
||||
push 0x1B ; SS = UserData | RPL3
|
||||
push rsi ; RSP = user stack top
|
||||
push 0x202 ; RFLAGS (IF=1)
|
||||
push 0x23 ; CS = UserCode | RPL3
|
||||
push rdi ; RIP = entry point
|
||||
iretq
|
||||
|
||||
; ============================================================
|
||||
; BSS: scratch space for user RSP save
|
||||
; ============================================================
|
||||
section .bss
|
||||
global g_userRsp
|
||||
g_userRsp: resq 1
|
||||
+56
-14
@@ -37,7 +37,10 @@
|
||||
#include <Net/Net.hpp>
|
||||
#include <CppLib/BoxUI.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
|
||||
#include <Fs/Ramdisk.hpp>
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
using namespace Kt;
|
||||
|
||||
namespace Memory {
|
||||
@@ -95,22 +98,20 @@ extern "C" void kmain() {
|
||||
uint64_t hhdm_offset = hhdm_request.response->offset;
|
||||
Memory::HHDMBase = hhdm_offset;
|
||||
|
||||
if (memmap_request.response != nullptr) {
|
||||
Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator";
|
||||
|
||||
Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response));
|
||||
Memory::g_pfa = &pmm;
|
||||
|
||||
Kt::KernelLogStream(OK, "Mem") << "Creating HeapAllocator";
|
||||
Memory::HeapAllocator heap{};
|
||||
Memory::g_heap = &heap;
|
||||
|
||||
heap.Walk();
|
||||
|
||||
} else {
|
||||
if (memmap_request.response == nullptr) {
|
||||
Panic("System memory map missing!", nullptr);
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator";
|
||||
Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response));
|
||||
Memory::g_pfa = &pmm;
|
||||
|
||||
Kt::KernelLogStream(OK, "Mem") << "Creating HeapAllocator";
|
||||
Memory::HeapAllocator heap{};
|
||||
Memory::g_heap = &heap;
|
||||
|
||||
heap.Walk();
|
||||
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Hal::IDTInitialize();
|
||||
@@ -142,8 +143,49 @@ extern "C" void kmain() {
|
||||
Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address);
|
||||
Efi::Init(ST);
|
||||
|
||||
// Initialize ramdisk from Limine modules
|
||||
if (module_request.response != nullptr && module_request.response->module_count > 0) {
|
||||
Kt::KernelLogStream(OK, "Modules") << "Found " << (uint64_t)module_request.response->module_count << " module(s)";
|
||||
for (uint64_t i = 0; i < module_request.response->module_count; i++) {
|
||||
limine_file* mod = module_request.response->modules[i];
|
||||
const char* modString = mod->string;
|
||||
|
||||
// Find "ramdisk" module by its string
|
||||
if (modString != nullptr &&
|
||||
modString[0] == 'r' && modString[1] == 'a' && modString[2] == 'm' &&
|
||||
modString[3] == 'd' && modString[4] == 'i' && modString[5] == 's' &&
|
||||
modString[6] == 'k' && modString[7] == '\0') {
|
||||
Kt::KernelLogStream(OK, "Modules") << "Ramdisk module at " << kcp::hex << (uint64_t)mod->address << kcp::dec << ", size=" << mod->size;
|
||||
Fs::Ramdisk::Initialize(mod->address, mod->size);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Kt::KernelLogStream(WARNING, "Modules") << "No modules loaded (ramdisk unavailable)";
|
||||
}
|
||||
|
||||
// Initialize VFS and register ramdisk as drive 0
|
||||
Fs::Vfs::Initialize();
|
||||
|
||||
static Fs::Vfs::FsDriver ramdiskDriver = {
|
||||
Fs::Ramdisk::Open,
|
||||
Fs::Ramdisk::Read,
|
||||
Fs::Ramdisk::GetSize,
|
||||
Fs::Ramdisk::Close,
|
||||
Fs::Ramdisk::ReadDir
|
||||
};
|
||||
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
|
||||
|
||||
Graphics::Cursor::Initialize(framebuffer);
|
||||
|
||||
Hal::LoadTSS();
|
||||
Zenith::InitializeSyscalls();
|
||||
|
||||
Sched::Initialize();
|
||||
Sched::Spawn("0:/shell.elf");
|
||||
|
||||
// Enable preemptive scheduling via the APIC timer
|
||||
Timekeeping::EnableSchedulerTick();
|
||||
|
||||
// Main loop: update cursor position and halt until next interrupt
|
||||
for (;;) {
|
||||
Graphics::Cursor::Update();
|
||||
|
||||
@@ -80,19 +80,19 @@ namespace Memory
|
||||
|
||||
InsertToFreelist(rest, newBlockSize);
|
||||
}
|
||||
|
||||
|
||||
Lock.Release();
|
||||
return block;
|
||||
}
|
||||
|
||||
prev = current;
|
||||
current = current->next;
|
||||
|
||||
Lock.Release();
|
||||
}
|
||||
|
||||
// First pass allocation failed
|
||||
size_t pagesNeeded = size / 0x1000;
|
||||
Lock.Release();
|
||||
|
||||
// First pass allocation failed -- grow the heap
|
||||
size_t pagesNeeded = (sizeNeeded + 0xFFF) / 0x1000;
|
||||
InsertPagesToFreelist(pagesNeeded);
|
||||
|
||||
return Request(size);
|
||||
@@ -102,7 +102,9 @@ namespace Memory
|
||||
auto new_block = Request(size);
|
||||
|
||||
if (ptr != nullptr && new_block != nullptr) {
|
||||
memcpy(new_block, ptr, size);
|
||||
size_t oldSize = GetAllocatedBlockSize(ptr);
|
||||
size_t copySize = (oldSize < size) ? oldSize : size;
|
||||
memcpy(new_block, ptr, copySize);
|
||||
Free(ptr);
|
||||
}
|
||||
|
||||
@@ -123,7 +125,7 @@ namespace Memory
|
||||
auto actualSize = size + sizeof(Header);
|
||||
void* actualBlock = (void*)header;
|
||||
|
||||
InsertToFreelist(actualBlock, size);
|
||||
InsertToFreelist(actualBlock, actualSize);
|
||||
|
||||
Lock.Release();
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ namespace Memory::VMM {
|
||||
|
||||
for (size_t i = 0; i < memMap->entry_count; i++) {
|
||||
auto entry = memMap->entries[i];
|
||||
|
||||
|
||||
for (size_t pageAddr = entry->base; pageAddr < (entry->base + entry->length); pageAddr += 0x1000) {
|
||||
Map(pageAddr, HHDM(pageAddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadCR3(PML4);
|
||||
@@ -40,21 +40,41 @@ namespace Memory::VMM {
|
||||
|
||||
PageTable* Paging::HandleLevel(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
|
||||
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]);
|
||||
|
||||
|
||||
if (!entry->Present) {
|
||||
entry->Present = true;
|
||||
entry->Writable = true;
|
||||
|
||||
|
||||
uint64_t downLevelAddr = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
|
||||
|
||||
|
||||
entry->Address = downLevelAddr >> 12;
|
||||
|
||||
|
||||
return (PageTable*)downLevelAddr;
|
||||
} else {
|
||||
return (PageTable*)(entry->Address << 12);
|
||||
}
|
||||
}
|
||||
|
||||
PageTable* Paging::HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
|
||||
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]);
|
||||
|
||||
if (!entry->Present) {
|
||||
entry->Present = true;
|
||||
entry->Writable = true;
|
||||
entry->Supervisor = 1; // User-accessible
|
||||
|
||||
uint64_t downLevelAddr = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
|
||||
|
||||
entry->Address = downLevelAddr >> 12;
|
||||
|
||||
return (PageTable*)downLevelAddr;
|
||||
} else {
|
||||
// Ensure User bit is set on existing entries in the user path
|
||||
entry->Supervisor = 1;
|
||||
return (PageTable*)(entry->Address << 12);
|
||||
}
|
||||
}
|
||||
|
||||
void Paging::Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||
Panic("Value that isn't page-aligned passed as address to Paging::Map!", nullptr);
|
||||
@@ -95,9 +115,79 @@ namespace Memory::VMM {
|
||||
pageEntry->Address = physicalAddress >> 12;
|
||||
}
|
||||
|
||||
void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||
Panic("Value that isn't page-aligned passed as address to Paging::MapUser!", nullptr);
|
||||
}
|
||||
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
auto PML3 = HandleLevelUser(virtualAddressObj, PML4, 4);
|
||||
auto PML2 = HandleLevelUser(virtualAddressObj, PML3, 3);
|
||||
auto PML1 = HandleLevelUser(virtualAddressObj, PML2, 2);
|
||||
|
||||
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]);
|
||||
|
||||
pageEntry->Present = true;
|
||||
pageEntry->Writable = true;
|
||||
pageEntry->Supervisor = 1; // User-accessible
|
||||
|
||||
pageEntry->Address = physicalAddress >> 12;
|
||||
}
|
||||
|
||||
std::uint64_t Paging::CreateUserPML4() {
|
||||
// Allocate a new PML4
|
||||
void* newPage = Memory::g_pfa->AllocateZeroed();
|
||||
uint64_t newPml4Phys = Memory::SubHHDM((uint64_t)newPage);
|
||||
PageTable* newPml4 = (PageTable*)newPage; // HHDM virtual address
|
||||
|
||||
// Copy kernel-half entries (256-511) from the global PML4
|
||||
PageTable* kernelPml4 = (PageTable*)Memory::HHDM((uint64_t)g_paging->PML4);
|
||||
for (int i = 256; i < 512; i++) {
|
||||
newPml4->entries[i] = kernelPml4->entries[i];
|
||||
}
|
||||
|
||||
return newPml4Phys;
|
||||
}
|
||||
|
||||
void Paging::MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||
Panic("Non-aligned address in Paging::MapUserIn!", nullptr);
|
||||
}
|
||||
|
||||
VirtualAddress va(virtualAddress);
|
||||
|
||||
// Walk/create page tables from the given PML4, setting User bit at each level
|
||||
auto walkLevel = [](PageTable* table, uint64_t index) -> PageTable* {
|
||||
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[index]);
|
||||
if (!entry->Present) {
|
||||
entry->Present = true;
|
||||
entry->Writable = true;
|
||||
entry->Supervisor = 1; // User-accessible
|
||||
uint64_t newPhys = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
|
||||
entry->Address = newPhys >> 12;
|
||||
return (PageTable*)newPhys;
|
||||
} else {
|
||||
entry->Supervisor = 1;
|
||||
return (PageTable*)(entry->Address << 12);
|
||||
}
|
||||
};
|
||||
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
auto pml3 = walkLevel(pml4, va.GetL4Index());
|
||||
auto pml2 = walkLevel(pml3, va.GetL3Index());
|
||||
auto pml1 = walkLevel(pml2, va.GetL2Index());
|
||||
|
||||
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]);
|
||||
pageEntry->Present = true;
|
||||
pageEntry->Writable = true;
|
||||
pageEntry->Supervisor = 1;
|
||||
pageEntry->Address = physicalAddress >> 12;
|
||||
}
|
||||
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
|
||||
PageTable* pml4Virt = (PageTable*)HHDM(pml4);
|
||||
|
||||
PageTableEntry* pml4_entry = &pml4Virt->entries[virtualAddressObj.GetL4Index()];
|
||||
@@ -122,4 +212,4 @@ namespace Memory::VMM {
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t virtualAddress) {
|
||||
return GetPhysAddr((std::uint64_t)PML4, virtualAddress, false);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Memory::VMM {
|
||||
std::uint8_t NX : 1;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct PageTable {
|
||||
PageTableEntry entries[512];
|
||||
} __attribute__((packed)) __attribute__((aligned(0x1000)));
|
||||
@@ -79,24 +79,35 @@ namespace Memory::VMM {
|
||||
|
||||
else if (level == 1)
|
||||
return GetPageIndex();
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class Paging {
|
||||
PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
|
||||
PageTable* HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, size_t level);
|
||||
public:
|
||||
PageTable* PML4{};
|
||||
|
||||
PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
|
||||
public:
|
||||
Paging();
|
||||
void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap);
|
||||
void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||
void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||
void MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||
static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false);
|
||||
std::uint64_t GetPhysAddr(std::uint64_t virtualAddress);
|
||||
|
||||
// Create a new PML4 with kernel-half (entries 256-511) copied from g_paging.
|
||||
// Returns the physical address of the new PML4.
|
||||
static std::uint64_t CreateUserPML4();
|
||||
|
||||
// Map a page into an arbitrary PML4 (specified by physical address) with User bit set.
|
||||
static void MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||
};
|
||||
|
||||
extern Paging* g_paging;
|
||||
|
||||
extern "C" uint64_t GetCR3();
|
||||
extern "C" void LoadCR3(PageTable* PML4);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Arp.hpp"
|
||||
#include <Net/ByteOrder.hpp>
|
||||
#include <Net/Ethernet.hpp>
|
||||
#include <Net/Ipv4.hpp>
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
@@ -91,8 +92,9 @@ namespace Net::Arp {
|
||||
uint32_t senderIp = pkt->SenderIp; // Already in network byte order in struct
|
||||
uint32_t targetIp = pkt->TargetIp;
|
||||
|
||||
// Cache the sender's IP->MAC mapping
|
||||
// Cache the sender's IP->MAC mapping, then flush any packets waiting on it
|
||||
CacheInsert(senderIp, pkt->SenderMac);
|
||||
Ipv4::FlushPending();
|
||||
|
||||
uint16_t op = Ntohs(pkt->Operation);
|
||||
|
||||
|
||||
@@ -15,10 +15,40 @@ using namespace Kt;
|
||||
|
||||
namespace Net::Icmp {
|
||||
|
||||
// Reply tracking for outgoing pings
|
||||
static volatile bool g_replyReceived = false;
|
||||
static volatile uint16_t g_replyId = 0;
|
||||
static volatile uint16_t g_replySeq = 0;
|
||||
|
||||
void Initialize() {
|
||||
KernelLogStream(OK, "Net") << "ICMP initialized";
|
||||
}
|
||||
|
||||
void ResetReply() {
|
||||
g_replyReceived = false;
|
||||
}
|
||||
|
||||
bool HasReply(uint16_t identifier, uint16_t sequence) {
|
||||
return g_replyReceived
|
||||
&& g_replyId == identifier
|
||||
&& g_replySeq == sequence;
|
||||
}
|
||||
|
||||
void SendEchoRequest(uint32_t destIp, uint16_t identifier, uint16_t sequence) {
|
||||
uint8_t packet[sizeof(Header)];
|
||||
Header* hdr = (Header*)packet;
|
||||
|
||||
hdr->Type = TYPE_ECHO_REQUEST;
|
||||
hdr->Code = 0;
|
||||
hdr->Checksum = 0;
|
||||
hdr->Identifier = Htons(identifier);
|
||||
hdr->Sequence = Htons(sequence);
|
||||
|
||||
hdr->Checksum = Ipv4::Checksum(packet, sizeof(Header));
|
||||
|
||||
Ipv4::Send(destIp, Ipv4::PROTO_ICMP, packet, sizeof(Header));
|
||||
}
|
||||
|
||||
void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length) {
|
||||
if (length < sizeof(Header)) {
|
||||
return;
|
||||
@@ -54,6 +84,10 @@ namespace Net::Icmp {
|
||||
replyHdr->Checksum = Ipv4::Checksum(reply, length);
|
||||
|
||||
Ipv4::Send(srcIp, Ipv4::PROTO_ICMP, reply, length);
|
||||
} else if (hdr->Type == TYPE_ECHO_REPLY && hdr->Code == 0) {
|
||||
g_replyId = Ntohs(hdr->Identifier);
|
||||
g_replySeq = Ntohs(hdr->Sequence);
|
||||
g_replyReceived = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,4 +26,13 @@ namespace Net::Icmp {
|
||||
// Handle an incoming ICMP packet (called by IPv4 layer)
|
||||
void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length);
|
||||
|
||||
// Send an ICMP echo request to the given IP address
|
||||
void SendEchoRequest(uint32_t destIp, uint16_t identifier, uint16_t sequence);
|
||||
|
||||
// Check if a reply was received for the given identifier/sequence
|
||||
bool HasReply(uint16_t identifier, uint16_t sequence);
|
||||
|
||||
// Reset the reply tracker (call before sending a new ping)
|
||||
void ResetReply();
|
||||
|
||||
}
|
||||
|
||||
+61
-27
@@ -15,7 +15,6 @@
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -23,6 +22,18 @@ namespace Net::Ipv4 {
|
||||
|
||||
static uint16_t g_identification = 0;
|
||||
|
||||
// Deferred packet queue for packets awaiting ARP resolution
|
||||
struct PendingPacket {
|
||||
uint32_t DestIp;
|
||||
uint8_t Protocol;
|
||||
uint8_t Data[Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE];
|
||||
uint16_t Length;
|
||||
bool Active;
|
||||
};
|
||||
|
||||
static constexpr uint32_t PENDING_QUEUE_SIZE = 8;
|
||||
static PendingPacket g_pendingQueue[PENDING_QUEUE_SIZE] = {};
|
||||
|
||||
void Initialize() {
|
||||
g_identification = 0;
|
||||
KernelLogStream(OK, "Net") << "IPv4 initialized, IP: "
|
||||
@@ -138,30 +149,9 @@ namespace Net::Ipv4 {
|
||||
}
|
||||
}
|
||||
|
||||
bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen) {
|
||||
if (payloadLen > (Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine next-hop IP and resolve MAC
|
||||
uint32_t nextHop = GetNextHop(destIp);
|
||||
uint8_t destMac[6];
|
||||
|
||||
if (!Arp::Resolve(nextHop, destMac)) {
|
||||
// ARP request sent, wait briefly and retry
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
Timekeeping::Sleep(50);
|
||||
if (Arp::Resolve(nextHop, destMac)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Final check
|
||||
if (!Arp::Resolve(nextHop, destMac)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Build IP packet
|
||||
// Build and send an IP packet over Ethernet (MAC already resolved)
|
||||
static bool SendDirect(uint32_t destIp, uint8_t protocol, const uint8_t* destMac,
|
||||
const uint8_t* payload, uint16_t payloadLen) {
|
||||
uint8_t packet[Ethernet::MAX_PAYLOAD_SIZE];
|
||||
Header* hdr = (Header*)packet;
|
||||
|
||||
@@ -176,13 +166,57 @@ namespace Net::Ipv4 {
|
||||
hdr->SrcIp = GetIpAddress();
|
||||
hdr->DstIp = destIp;
|
||||
|
||||
// Calculate header checksum
|
||||
hdr->Checksum = Checksum(hdr, HEADER_SIZE);
|
||||
|
||||
// Copy payload
|
||||
memcpy(packet + HEADER_SIZE, payload, payloadLen);
|
||||
|
||||
return Ethernet::Send(destMac, Ethernet::ETHERTYPE_IPV4, packet, HEADER_SIZE + payloadLen);
|
||||
}
|
||||
|
||||
bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen) {
|
||||
if (payloadLen > (Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine next-hop IP and resolve MAC
|
||||
uint32_t nextHop = GetNextHop(destIp);
|
||||
uint8_t destMac[6];
|
||||
|
||||
if (Arp::Resolve(nextHop, destMac)) {
|
||||
return SendDirect(destIp, protocol, destMac, payload, payloadLen);
|
||||
}
|
||||
|
||||
// ARP request already sent by Resolve(), queue the packet for later
|
||||
for (uint32_t i = 0; i < PENDING_QUEUE_SIZE; i++) {
|
||||
if (!g_pendingQueue[i].Active) {
|
||||
g_pendingQueue[i].DestIp = destIp;
|
||||
g_pendingQueue[i].Protocol = protocol;
|
||||
g_pendingQueue[i].Length = payloadLen;
|
||||
memcpy(g_pendingQueue[i].Data, payload, payloadLen);
|
||||
g_pendingQueue[i].Active = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue full, drop the packet
|
||||
return false;
|
||||
}
|
||||
|
||||
void FlushPending() {
|
||||
for (uint32_t i = 0; i < PENDING_QUEUE_SIZE; i++) {
|
||||
if (!g_pendingQueue[i].Active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t nextHop = GetNextHop(g_pendingQueue[i].DestIp);
|
||||
uint8_t destMac[6];
|
||||
|
||||
if (Arp::Resolve(nextHop, destMac)) {
|
||||
SendDirect(g_pendingQueue[i].DestIp, g_pendingQueue[i].Protocol,
|
||||
destMac, g_pendingQueue[i].Data, g_pendingQueue[i].Length);
|
||||
g_pendingQueue[i].Active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,9 +36,14 @@ namespace Net::Ipv4 {
|
||||
// Handle an incoming IP packet (called by Ethernet layer)
|
||||
void OnPacketReceived(const uint8_t* data, uint16_t length);
|
||||
|
||||
// Send an IP packet with the given protocol and payload
|
||||
// Send an IP packet with the given protocol and payload.
|
||||
// If ARP resolution is pending, the packet is queued and sent when the reply arrives.
|
||||
bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen);
|
||||
|
||||
// Flush any packets that were waiting for ARP resolution.
|
||||
// Called by the ARP layer when a new cache entry is inserted.
|
||||
void FlushPending();
|
||||
|
||||
// Compute the Internet checksum over a buffer
|
||||
uint16_t Checksum(const void* data, uint16_t length);
|
||||
|
||||
|
||||
@@ -59,6 +59,15 @@ namespace {
|
||||
.response = nullptr
|
||||
};
|
||||
|
||||
__attribute__((used, section(".limine_requests")))
|
||||
volatile limine_module_request module_request = {
|
||||
.id = LIMINE_MODULE_REQUEST,
|
||||
.revision = 1,
|
||||
.response = nullptr,
|
||||
.internal_module_count = 0,
|
||||
.internal_modules = nullptr
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Finally, define the start and end markers for the Limine requests.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
;
|
||||
; Context.asm
|
||||
; Context switch: save/restore callee-saved registers, stack pointer, and CR3
|
||||
; Copyright (c) 2025 Daniel Hammer
|
||||
;
|
||||
|
||||
[bits 64]
|
||||
section .text
|
||||
|
||||
; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3)
|
||||
; rdi = pointer to save old RSP
|
||||
; rsi = new RSP to restore
|
||||
; rdx = new PML4 physical address (for CR3)
|
||||
global SchedContextSwitch
|
||||
SchedContextSwitch:
|
||||
; Save callee-saved registers on the current stack
|
||||
push rbp
|
||||
push rbx
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save current RSP into *oldRsp
|
||||
mov [rdi], rsp
|
||||
|
||||
; Load new RSP
|
||||
mov rsp, rsi
|
||||
|
||||
; Switch address space if CR3 differs (avoid unnecessary TLB flush)
|
||||
mov rax, cr3
|
||||
cmp rax, rdx
|
||||
je .skip_cr3
|
||||
mov cr3, rdx
|
||||
.skip_cr3:
|
||||
|
||||
; Restore callee-saved registers from the new stack
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop rbx
|
||||
pop rbp
|
||||
|
||||
ret
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* ElfLoader.cpp
|
||||
* ELF64 binary loader for user-mode processes
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "ElfLoader.hpp"
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
namespace Sched {
|
||||
|
||||
static bool ValidateElfHeader(const Elf64Header* hdr) {
|
||||
// Check ELF magic: 0x7f 'E' 'L' 'F'
|
||||
if (hdr->e_ident[0] != 0x7f ||
|
||||
hdr->e_ident[1] != 'E' ||
|
||||
hdr->e_ident[2] != 'L' ||
|
||||
hdr->e_ident[3] != 'F') {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Invalid ELF magic";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Class must be ELFCLASS64 (2)
|
||||
if (hdr->e_ident[4] != 2) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not a 64-bit ELF";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data encoding must be ELFDATA2LSB (1) - little endian
|
||||
if (hdr->e_ident[5] != 1) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not little-endian";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hdr->e_type != ET_EXEC) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not an executable (type=" << (uint64_t)hdr->e_type << ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hdr->e_machine != EM_X86_64) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not x86_64 (machine=" << (uint64_t)hdr->e_machine << ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "Loading " << vfsPath;
|
||||
|
||||
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
||||
if (handle < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t fileSize = Fs::Vfs::VfsGetSize(handle);
|
||||
if (fileSize < sizeof(Elf64Header)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "File too small (" << fileSize << " bytes)";
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read entire file into a heap buffer
|
||||
uint8_t* fileData = (uint8_t*)Memory::g_heap->Request(fileSize);
|
||||
if (fileData == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to allocate " << fileSize << " bytes for file";
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Fs::Vfs::VfsRead(handle, fileData, 0, fileSize);
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
|
||||
// Validate ELF header
|
||||
Elf64Header* hdr = (Elf64Header*)fileData;
|
||||
if (!ValidateElfHeader(hdr)) {
|
||||
Memory::g_heap->Free(fileData);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Entry point: " << kcp::hex << hdr->e_entry << kcp::dec
|
||||
<< ", " << (uint64_t)hdr->e_phnum << " program header(s)";
|
||||
|
||||
// Process program headers
|
||||
for (uint16_t i = 0; i < hdr->e_phnum; i++) {
|
||||
Elf64ProgramHeader* phdr = (Elf64ProgramHeader*)(fileData + hdr->e_phoff + i * hdr->e_phentsize);
|
||||
|
||||
if (phdr->p_type != PT_LOAD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phdr->p_memsz == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "PT_LOAD: vaddr=" << kcp::hex << phdr->p_vaddr
|
||||
<< " filesz=" << phdr->p_filesz << " memsz=" << phdr->p_memsz << kcp::dec;
|
||||
|
||||
// Allocate pages and map them in the process PML4 with User bit
|
||||
uint64_t segBase = phdr->p_vaddr & ~0xFFFULL;
|
||||
uint64_t segEnd = (phdr->p_vaddr + phdr->p_memsz + 0xFFF) & ~0xFFFULL;
|
||||
uint64_t numPages = (segEnd - segBase) / 0x1000;
|
||||
|
||||
for (uint64_t p = 0; p < numPages; p++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Out of physical pages";
|
||||
Memory::g_heap->Free(fileData);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
uint64_t virtAddr = segBase + p * 0x1000;
|
||||
|
||||
// Map into the process's PML4 with User bit set
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, virtAddr);
|
||||
|
||||
// Copy file data that overlaps this page (via HHDM)
|
||||
uint64_t pageStart = virtAddr;
|
||||
uint64_t pageEnd = virtAddr + 0x1000;
|
||||
|
||||
uint64_t segFileStart = phdr->p_vaddr;
|
||||
uint64_t segFileEnd = phdr->p_vaddr + phdr->p_filesz;
|
||||
|
||||
uint64_t copyStart = (pageStart > segFileStart) ? pageStart : segFileStart;
|
||||
uint64_t copyEnd = (pageEnd < segFileEnd) ? pageEnd : segFileEnd;
|
||||
|
||||
if (copyStart < copyEnd) {
|
||||
uint64_t dstOffset = copyStart - pageStart;
|
||||
uint64_t srcOffset = copyStart - phdr->p_vaddr + phdr->p_offset;
|
||||
uint64_t copySize = copyEnd - copyStart;
|
||||
|
||||
uint8_t* dst = (uint8_t*)Memory::HHDM(physAddr) + dstOffset;
|
||||
uint8_t* src = fileData + srcOffset;
|
||||
memcpy(dst, src, copySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t entryPoint = hdr->e_entry;
|
||||
Memory::g_heap->Free(fileData);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Loaded successfully, entry=" << kcp::hex << entryPoint << kcp::dec;
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* ElfLoader.hpp
|
||||
* ELF64 binary loader for user-mode processes
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Sched {
|
||||
|
||||
struct Elf64Header {
|
||||
uint8_t e_ident[16];
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint64_t e_entry;
|
||||
uint64_t e_phoff;
|
||||
uint64_t e_shoff;
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64ProgramHeader {
|
||||
uint32_t p_type;
|
||||
uint32_t p_flags;
|
||||
uint64_t p_offset;
|
||||
uint64_t p_vaddr;
|
||||
uint64_t p_paddr;
|
||||
uint64_t p_filesz;
|
||||
uint64_t p_memsz;
|
||||
uint64_t p_align;
|
||||
};
|
||||
|
||||
static constexpr uint32_t PT_LOAD = 1;
|
||||
static constexpr uint16_t ET_EXEC = 2;
|
||||
static constexpr uint16_t EM_X86_64 = 62;
|
||||
|
||||
// Load an ELF64 binary into a per-process address space.
|
||||
// pml4Phys = physical address of the process's PML4.
|
||||
// Returns the entry point address, or 0 on failure.
|
||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Scheduler.cpp
|
||||
* Preemptive process scheduler with user-mode support
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Scheduler.hpp"
|
||||
#include "ElfLoader.hpp"
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
|
||||
// Assembly: context switch with CR3 parameter
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3);
|
||||
|
||||
// Assembly: jump to user mode via IRETQ
|
||||
extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp);
|
||||
|
||||
// Global kernel RSP for SYSCALL entry (written by scheduler, read by SyscallEntry.asm)
|
||||
extern "C" uint64_t g_kernelRsp;
|
||||
uint64_t g_kernelRsp = 0;
|
||||
|
||||
namespace Sched {
|
||||
|
||||
static Process processTable[MaxProcesses];
|
||||
static int currentPid = -1; // -1 = idle (kernel main loop)
|
||||
static int nextPid = 0;
|
||||
static uint64_t idleSavedRsp = 0;
|
||||
|
||||
// The idle loop runs in the kernel PML4
|
||||
static uint64_t GetKernelCR3() {
|
||||
return (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
}
|
||||
|
||||
// Startup function for newly spawned processes.
|
||||
// SchedContextSwitch "returns" here on first schedule.
|
||||
static void ProcessStartup() {
|
||||
// Send EOI for the timer IRQ that triggered the context switch
|
||||
Hal::LocalApic::SendEOI();
|
||||
|
||||
if (currentPid >= 0) {
|
||||
Process& proc = processTable[currentPid];
|
||||
|
||||
// Set up kernel RSP for SYSCALL entry
|
||||
g_kernelRsp = proc.kernelStackTop;
|
||||
|
||||
// Set up TSS RSP0 for hardware interrupts from ring 3
|
||||
Hal::g_tss.rsp0 = proc.kernelStackTop;
|
||||
|
||||
// Jump to user mode (never returns)
|
||||
JumpToUserMode(proc.entryPoint, proc.userStackTop);
|
||||
}
|
||||
|
||||
ExitProcess();
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
processTable[i].pid = i;
|
||||
processTable[i].state = ProcessState::Free;
|
||||
processTable[i].name = nullptr;
|
||||
processTable[i].savedRsp = 0;
|
||||
processTable[i].stackBase = 0;
|
||||
processTable[i].entryPoint = 0;
|
||||
processTable[i].sliceRemaining = 0;
|
||||
processTable[i].pml4Phys = 0;
|
||||
processTable[i].kernelStackTop = 0;
|
||||
processTable[i].userStackTop = 0;
|
||||
processTable[i].heapNext = 0;
|
||||
}
|
||||
|
||||
currentPid = -1;
|
||||
nextPid = 0;
|
||||
idleSavedRsp = 0;
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Initialized (" << MaxProcesses
|
||||
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
||||
}
|
||||
|
||||
void Spawn(const char* vfsPath) {
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Free) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "No free process slots";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create per-process PML4 with kernel-half copied
|
||||
uint64_t pml4Phys = Memory::VMM::Paging::CreateUserPML4();
|
||||
|
||||
// Load ELF into the process's address space
|
||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||
if (entry == 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate kernel stack (used during syscalls and interrupts)
|
||||
void* firstPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (firstPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for kernel stack";
|
||||
return;
|
||||
}
|
||||
void* stackMem = Memory::g_pfa->ReallocConsecutive(firstPage, StackPages);
|
||||
if (stackMem == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to allocate contiguous kernel stack";
|
||||
Memory::g_pfa->Free(firstPage);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* kernelStackBase = (uint8_t*)stackMem;
|
||||
uint64_t kernelStackTop = (uint64_t)kernelStackBase + StackSize;
|
||||
|
||||
// Allocate user stack pages and map them in the process PML4
|
||||
uint64_t userStackBase = UserStackTop - UserStackSize;
|
||||
uint64_t topStackPagePhys = 0;
|
||||
for (uint64_t i = 0; i < UserStackPages; i++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for user stack";
|
||||
return;
|
||||
}
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, userStackBase + i * 0x1000);
|
||||
if (i == UserStackPages - 1) topStackPagePhys = physAddr;
|
||||
}
|
||||
|
||||
// Allocate and map a user-space exit stub page.
|
||||
// When _start() returns, it jumps here and calls SYS_EXIT(0).
|
||||
{
|
||||
void* stubPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (stubPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for exit stub";
|
||||
return;
|
||||
}
|
||||
uint64_t stubPhys = Memory::SubHHDM((uint64_t)stubPage);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, stubPhys, ExitStubAddr);
|
||||
|
||||
// Write: xor edi, edi; xor eax, eax; syscall
|
||||
uint8_t* stub = (uint8_t*)stubPage;
|
||||
stub[0] = 0x31; stub[1] = 0xFF; // xor edi, edi (exit code 0)
|
||||
stub[2] = 0x31; stub[3] = 0xC0; // xor eax, eax (SYS_EXIT = 0)
|
||||
stub[4] = 0x0F; stub[5] = 0x05; // syscall
|
||||
}
|
||||
|
||||
// Push exit stub address as the return address on the user stack.
|
||||
// UserStackTop - 8 falls at offset 0xFF8 within the top stack page.
|
||||
{
|
||||
uint8_t* topPage = (uint8_t*)Memory::HHDM(topStackPagePhys);
|
||||
*(uint64_t*)(topPage + 0xFF8) = ExitStubAddr;
|
||||
}
|
||||
|
||||
// Set up the initial kernel stack frame so that SchedContextSwitch
|
||||
// "returns" into ProcessStartup
|
||||
uint64_t* sp = (uint64_t*)kernelStackTop;
|
||||
|
||||
*(--sp) = (uint64_t)ProcessStartup; // return addr
|
||||
*(--sp) = 0; // rbp
|
||||
*(--sp) = 0; // rbx
|
||||
*(--sp) = 0; // r12
|
||||
*(--sp) = 0; // r13
|
||||
*(--sp) = 0; // r14
|
||||
*(--sp) = 0; // r15
|
||||
|
||||
Process& proc = processTable[slot];
|
||||
proc.pid = nextPid++;
|
||||
proc.state = ProcessState::Ready;
|
||||
proc.name = vfsPath;
|
||||
proc.savedRsp = (uint64_t)sp;
|
||||
proc.stackBase = (uint64_t)kernelStackBase;
|
||||
proc.entryPoint = entry;
|
||||
proc.sliceRemaining = TimeSliceMs;
|
||||
proc.pml4Phys = pml4Phys;
|
||||
proc.kernelStackTop = kernelStackTop;
|
||||
proc.userStackTop = UserStackTop - 8; // account for pushed exit stub return address
|
||||
proc.heapNext = UserHeapBase;
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Spawned process " << (uint64_t)proc.pid
|
||||
<< " (" << vfsPath << ") entry=" << kcp::hex << entry
|
||||
<< " kstack=" << (uint64_t)kernelStackBase << "-" << kernelStackTop
|
||||
<< " ustack=" << userStackBase << "-" << UserStackTop
|
||||
<< " pml4=" << pml4Phys << kcp::dec;
|
||||
}
|
||||
|
||||
void Schedule() {
|
||||
int next = -1;
|
||||
int start = (currentPid >= 0) ? currentPid + 1 : 0;
|
||||
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
int idx = (start + i) % MaxProcesses;
|
||||
if (processTable[idx].state == ProcessState::Ready) {
|
||||
next = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (next == currentPid) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t* oldRspPtr;
|
||||
uint64_t oldCR3;
|
||||
|
||||
if (currentPid >= 0) {
|
||||
processTable[currentPid].state = ProcessState::Ready;
|
||||
oldRspPtr = &processTable[currentPid].savedRsp;
|
||||
} else {
|
||||
oldRspPtr = &idleSavedRsp;
|
||||
}
|
||||
|
||||
currentPid = next;
|
||||
processTable[next].state = ProcessState::Running;
|
||||
processTable[next].sliceRemaining = TimeSliceMs;
|
||||
|
||||
uint64_t newCR3 = processTable[next].pml4Phys;
|
||||
|
||||
// Update kernel RSP for SYSCALL entry
|
||||
g_kernelRsp = processTable[next].kernelStackTop;
|
||||
|
||||
// Update TSS RSP0 for hardware interrupts from ring 3
|
||||
Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
|
||||
|
||||
SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3);
|
||||
}
|
||||
|
||||
void Tick() {
|
||||
if (currentPid < 0) {
|
||||
// Idle — check if any process became ready
|
||||
Schedule();
|
||||
return;
|
||||
}
|
||||
|
||||
if (processTable[currentPid].sliceRemaining > 0) {
|
||||
processTable[currentPid].sliceRemaining--;
|
||||
}
|
||||
|
||||
if (processTable[currentPid].sliceRemaining == 0) {
|
||||
Schedule();
|
||||
}
|
||||
}
|
||||
|
||||
int GetCurrentPid() {
|
||||
return (currentPid >= 0) ? processTable[currentPid].pid : -1;
|
||||
}
|
||||
|
||||
Process* GetCurrentProcessPtr() {
|
||||
if (currentPid < 0) return nullptr;
|
||||
return &processTable[currentPid];
|
||||
}
|
||||
|
||||
void ExitProcess() {
|
||||
if (currentPid < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Process " << (uint64_t)processTable[currentPid].pid << " terminated";
|
||||
|
||||
processTable[currentPid].state = ProcessState::Terminated;
|
||||
|
||||
int next = -1;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Ready) {
|
||||
next = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next >= 0) {
|
||||
int old = currentPid;
|
||||
currentPid = next;
|
||||
processTable[next].state = ProcessState::Running;
|
||||
processTable[next].sliceRemaining = TimeSliceMs;
|
||||
|
||||
uint64_t newCR3 = processTable[next].pml4Phys;
|
||||
g_kernelRsp = processTable[next].kernelStackTop;
|
||||
Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
|
||||
|
||||
SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3);
|
||||
} else {
|
||||
int old = currentPid;
|
||||
currentPid = -1;
|
||||
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3());
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Scheduler.hpp
|
||||
* Preemptive process scheduler with user-mode support
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Sched {
|
||||
|
||||
static constexpr int MaxProcesses = 16;
|
||||
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per process
|
||||
static constexpr uint64_t StackSize = StackPages * 0x1000;
|
||||
static constexpr uint64_t UserStackPages = 4; // 16 KiB user stack
|
||||
static constexpr uint64_t UserStackSize = UserStackPages * 0x1000;
|
||||
static constexpr uint64_t UserStackTop = 0x7FFFFFF000ULL; // User stack top VA
|
||||
static constexpr uint64_t UserHeapBase = 0x40000000ULL; // User heap start VA
|
||||
static constexpr uint64_t ExitStubAddr = 0x3FF000ULL; // User-space exit stub page
|
||||
static constexpr uint64_t TimeSliceMs = 10; // 10 ms time slice
|
||||
|
||||
enum class ProcessState {
|
||||
Free,
|
||||
Ready,
|
||||
Running,
|
||||
Terminated
|
||||
};
|
||||
|
||||
struct Process {
|
||||
int pid;
|
||||
ProcessState state;
|
||||
const char* name;
|
||||
uint64_t savedRsp;
|
||||
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
|
||||
uint64_t entryPoint;
|
||||
uint64_t sliceRemaining; // Ticks left in current time slice
|
||||
uint64_t pml4Phys; // Physical address of per-process PML4
|
||||
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
|
||||
uint64_t userStackTop; // User-space stack top
|
||||
uint64_t heapNext; // Simple bump allocator for user heap
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
void Spawn(const char* vfsPath);
|
||||
void Schedule();
|
||||
|
||||
// Called from the APIC timer handler on every tick.
|
||||
void Tick();
|
||||
|
||||
// Get the PID of the currently running process (-1 if idle)
|
||||
int GetCurrentPid();
|
||||
|
||||
// Get a pointer to the currently running process (nullptr if idle)
|
||||
Process* GetCurrentProcessPtr();
|
||||
|
||||
// Called by terminated processes to mark themselves done
|
||||
void ExitProcess();
|
||||
|
||||
}
|
||||
@@ -60,11 +60,17 @@ namespace Kt {
|
||||
}
|
||||
|
||||
void Putchar(char c) {
|
||||
if (c == '\n') {
|
||||
flanterm_write(ctx, "\r\n", 2);
|
||||
return;
|
||||
}
|
||||
flanterm_write(ctx, &c, 1);
|
||||
}
|
||||
|
||||
void Print(const char *text) {
|
||||
flanterm_write(ctx, text, Lib::strlen(text));
|
||||
for (size_t i = 0; text[i] != '\0'; i++) {
|
||||
Putchar(text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -34,9 +35,15 @@ namespace Timekeeping {
|
||||
static volatile uint64_t g_tickCount = 0;
|
||||
static uint32_t g_ticksPerMs = 0;
|
||||
|
||||
// Timer IRQ handler: increment tick count
|
||||
static bool g_schedEnabled = false;
|
||||
|
||||
// Timer IRQ handler: increment tick count and drive scheduler
|
||||
static void TimerHandler(uint8_t) {
|
||||
g_tickCount = g_tickCount + 1;
|
||||
|
||||
if (g_schedEnabled) {
|
||||
Sched::Tick();
|
||||
}
|
||||
}
|
||||
|
||||
// Use PIT channel 2 to create a precise delay for calibration.
|
||||
@@ -128,10 +135,14 @@ namespace Timekeeping {
|
||||
return g_tickCount; // 1 tick = 1 ms at 1000 Hz
|
||||
}
|
||||
|
||||
void EnableSchedulerTick() {
|
||||
g_schedEnabled = true;
|
||||
}
|
||||
|
||||
void Sleep(uint64_t ms) {
|
||||
uint64_t target = g_tickCount + ms;
|
||||
while (g_tickCount < target) {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Timekeeping {
|
||||
// Get elapsed milliseconds since timer initialization
|
||||
uint64_t GetMilliseconds();
|
||||
|
||||
// Enable scheduler tick (called after scheduler is initialized)
|
||||
void EnableSchedulerTick();
|
||||
|
||||
// Busy-wait sleep for the given number of milliseconds
|
||||
void Sleep(uint64_t ms);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user