feat: scheduling, usermode, shell
This commit is contained in:
+11
-3
@@ -173,10 +173,16 @@ kernel-deps:
|
|||||||
kernel: kernel-deps
|
kernel: kernel-deps
|
||||||
$(MAKE) -C kernel
|
$(MAKE) -C kernel
|
||||||
|
|
||||||
$(IMAGE_NAME).iso: limine/limine kernel
|
|
||||||
|
.PHONY: ramdisk
|
||||||
|
ramdisk: programs
|
||||||
|
./scripts/mkramdisk.sh programs/bin ramdisk.tar
|
||||||
|
|
||||||
|
$(IMAGE_NAME).iso: limine/limine kernel ramdisk
|
||||||
rm -rf iso_root
|
rm -rf iso_root
|
||||||
mkdir -p iso_root/boot
|
mkdir -p iso_root/boot
|
||||||
cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/
|
cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/
|
||||||
|
cp -v ramdisk.tar iso_root/boot/
|
||||||
mkdir -p iso_root/boot/limine
|
mkdir -p iso_root/boot/limine
|
||||||
cp -v limine.conf iso_root/boot/limine/
|
cp -v limine.conf iso_root/boot/limine/
|
||||||
mkdir -p iso_root/EFI/BOOT
|
mkdir -p iso_root/EFI/BOOT
|
||||||
@@ -220,7 +226,7 @@ ifeq ($(ARCH),loongarch64)
|
|||||||
endif
|
endif
|
||||||
rm -rf iso_root
|
rm -rf iso_root
|
||||||
|
|
||||||
$(IMAGE_NAME).hdd: limine/limine kernel
|
$(IMAGE_NAME).hdd: limine/limine kernel ramdisk
|
||||||
rm -f $(IMAGE_NAME).hdd
|
rm -f $(IMAGE_NAME).hdd
|
||||||
dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd
|
dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd
|
||||||
PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00
|
PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00
|
||||||
@@ -230,6 +236,7 @@ endif
|
|||||||
mformat -i $(IMAGE_NAME).hdd@@1M
|
mformat -i $(IMAGE_NAME).hdd@@1M
|
||||||
mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine
|
mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine
|
||||||
mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot
|
mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot
|
||||||
|
mcopy -i $(IMAGE_NAME).hdd@@1M ramdisk.tar ::/boot
|
||||||
mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine
|
mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine
|
||||||
ifeq ($(ARCH),x86_64)
|
ifeq ($(ARCH),x86_64)
|
||||||
mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine
|
mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine
|
||||||
@@ -249,7 +256,8 @@ endif
|
|||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
$(MAKE) -C kernel clean
|
$(MAKE) -C kernel clean
|
||||||
rm -rf iso_root $(IMAGE_NAME).iso $(IMAGE_NAME).hdd
|
# $(MAKE) -C programs clean
|
||||||
|
rm -rf iso_root $(IMAGE_NAME).iso $(IMAGE_NAME).hdd ramdisk.tar
|
||||||
|
|
||||||
.PHONY: distclean
|
.PHONY: distclean
|
||||||
distclean:
|
distclean:
|
||||||
|
|||||||
@@ -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);
|
SendCommand(CmdReadConfig);
|
||||||
config = ReadData();
|
config = ReadData();
|
||||||
|
|
||||||
config |= ConfigPort1Interrupt;
|
config |= ConfigPort1Interrupt | ConfigPort1Translation;
|
||||||
if (g_DualChannel) {
|
if (g_DualChannel) {
|
||||||
config |= ConfigPort2Interrupt;
|
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 ReloadSegments
|
||||||
global LoadGDT
|
global LoadGDT
|
||||||
|
global LoadTR
|
||||||
|
|
||||||
LoadGDT:
|
LoadGDT:
|
||||||
lgdt [rdi] ; Run LGDT on the contents of 1st C parameter
|
lgdt [rdi] ; Run LGDT on the contents of 1st C parameter
|
||||||
@@ -29,3 +30,8 @@ ReloadSegments:
|
|||||||
mov ss, ax
|
mov ss, ax
|
||||||
|
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
LoadTR:
|
||||||
|
mov ax, 0x28 ; TSS selector
|
||||||
|
ltr ax
|
||||||
|
ret
|
||||||
|
|||||||
+41
-8
@@ -1,29 +1,56 @@
|
|||||||
/*
|
/*
|
||||||
* gdt.hpp
|
* gdt.cpp
|
||||||
* Intel Global Descriptor Table
|
* Intel Global Descriptor Table
|
||||||
* Copyright (c) 2025 Daniel Hammer
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "GDT.hpp"
|
#include "GDT.hpp"
|
||||||
#include "../Terminal/Terminal.hpp"
|
#include "../Terminal/Terminal.hpp"
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
|
||||||
namespace Hal {
|
namespace Hal {
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
GDTPointer gdtPointer{};
|
GDTPointer gdtPointer{};
|
||||||
BasicGDT kernelGDT{};
|
BasicGDT kernelGDT{};
|
||||||
|
TSS64 g_tss{};
|
||||||
|
|
||||||
void PrepareGDT() {
|
void PrepareGDT() {
|
||||||
kernelGDT = {
|
// Zero the TSS
|
||||||
{0xFFFF, 0, 0, 0x00, 0x00, 0},
|
memset(&g_tss, 0, sizeof(g_tss));
|
||||||
{0xFFFF, 0, 0, 0x9A, 0xA0, 0},
|
g_tss.iopbOffset = sizeof(TSS64);
|
||||||
{0xFFFF, 0, 0, 0x92, 0xA0, 0},
|
|
||||||
{0xFFFF, 0, 0, 0x9A, 0xA0, 0},
|
|
||||||
{0xFFFF, 0, 0, 0x92, 0xA0, 0},
|
|
||||||
|
|
||||||
{0, 0, 0, 0xFA, 0x00, 0x0},
|
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{
|
gdtPointer = GDTPointer{
|
||||||
.Size = sizeof(kernelGDT) - 1,
|
.Size = sizeof(kernelGDT) - 1,
|
||||||
.GDTAddress = (uint64_t)&kernelGDT
|
.GDTAddress = (uint64_t)&kernelGDT
|
||||||
@@ -33,6 +60,7 @@ namespace Hal {
|
|||||||
// Helpers implemented in gdt.asm
|
// Helpers implemented in gdt.asm
|
||||||
extern "C" void LoadGDT(GDTPointer *gdtPointer);
|
extern "C" void LoadGDT(GDTPointer *gdtPointer);
|
||||||
extern "C" void ReloadSegments();
|
extern "C" void ReloadSegments();
|
||||||
|
extern "C" void LoadTR();
|
||||||
|
|
||||||
void BridgeLoadGDT() {
|
void BridgeLoadGDT() {
|
||||||
LoadGDT(&gdtPointer);
|
LoadGDT(&gdtPointer);
|
||||||
@@ -40,4 +68,9 @@ namespace Hal {
|
|||||||
|
|
||||||
KernelLogStream(DEBUG, "Hal") << "Set new GDT (0x" << base::hex << (uint64_t)&kernelGDT << ")";
|
KernelLogStream(DEBUG, "Hal") << "Set new GDT (0x" << base::hex << (uint64_t)&kernelGDT << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoadTSS() {
|
||||||
|
LoadTR();
|
||||||
|
KernelLogStream(OK, "Hal") << "Loaded TSS (selector 0x28)";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
+28
-8
@@ -21,14 +21,13 @@ namespace Hal {
|
|||||||
}__attribute__((packed));
|
}__attribute__((packed));
|
||||||
|
|
||||||
struct BasicGDT {
|
struct BasicGDT {
|
||||||
GDTEntry Null;
|
GDTEntry Null; // 0x00
|
||||||
GDTEntry KernelCode;
|
GDTEntry KernelCode; // 0x08
|
||||||
GDTEntry KernelData;
|
GDTEntry KernelData; // 0x10
|
||||||
GDTEntry UserCode;
|
GDTEntry UserData; // 0x18 (before UserCode for SYSRET)
|
||||||
GDTEntry UserData;
|
GDTEntry UserCode; // 0x20
|
||||||
|
GDTEntry TSS; // 0x28 (low 8 bytes of 16-byte TSS descriptor)
|
||||||
// Task State Segment
|
GDTEntry TSSHigh; // 0x30 (high 8 bytes of 16-byte TSS descriptor)
|
||||||
GDTEntry TSS;
|
|
||||||
}__attribute__((packed));
|
}__attribute__((packed));
|
||||||
|
|
||||||
struct GDTPointer {
|
struct GDTPointer {
|
||||||
@@ -36,6 +35,27 @@ namespace Hal {
|
|||||||
uint64_t GDTAddress;
|
uint64_t GDTAddress;
|
||||||
}__attribute__((packed));
|
}__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 BridgeLoadGDT();
|
||||||
void PrepareGDT();
|
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 <Net/Net.hpp>
|
||||||
#include <CppLib/BoxUI.hpp>
|
#include <CppLib/BoxUI.hpp>
|
||||||
#include <Graphics/Cursor.hpp>
|
#include <Graphics/Cursor.hpp>
|
||||||
|
#include <Fs/Ramdisk.hpp>
|
||||||
|
#include <Fs/Vfs.hpp>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Api/Syscall.hpp>
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
namespace Memory {
|
namespace Memory {
|
||||||
@@ -95,22 +98,20 @@ extern "C" void kmain() {
|
|||||||
uint64_t hhdm_offset = hhdm_request.response->offset;
|
uint64_t hhdm_offset = hhdm_request.response->offset;
|
||||||
Memory::HHDMBase = hhdm_offset;
|
Memory::HHDMBase = hhdm_offset;
|
||||||
|
|
||||||
if (memmap_request.response != nullptr) {
|
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 {
|
|
||||||
Panic("System memory map missing!", 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__)
|
#if defined (__x86_64__)
|
||||||
Hal::IDTInitialize();
|
Hal::IDTInitialize();
|
||||||
@@ -142,8 +143,49 @@ extern "C" void kmain() {
|
|||||||
Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address);
|
Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address);
|
||||||
Efi::Init(ST);
|
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);
|
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
|
// Main loop: update cursor position and halt until next interrupt
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Graphics::Cursor::Update();
|
Graphics::Cursor::Update();
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ namespace Memory
|
|||||||
|
|
||||||
prev = current;
|
prev = current;
|
||||||
current = current->next;
|
current = current->next;
|
||||||
|
|
||||||
Lock.Release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// First pass allocation failed
|
Lock.Release();
|
||||||
size_t pagesNeeded = size / 0x1000;
|
|
||||||
|
// First pass allocation failed -- grow the heap
|
||||||
|
size_t pagesNeeded = (sizeNeeded + 0xFFF) / 0x1000;
|
||||||
InsertPagesToFreelist(pagesNeeded);
|
InsertPagesToFreelist(pagesNeeded);
|
||||||
|
|
||||||
return Request(size);
|
return Request(size);
|
||||||
@@ -102,7 +102,9 @@ namespace Memory
|
|||||||
auto new_block = Request(size);
|
auto new_block = Request(size);
|
||||||
|
|
||||||
if (ptr != nullptr && new_block != nullptr) {
|
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);
|
Free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +125,7 @@ namespace Memory
|
|||||||
auto actualSize = size + sizeof(Header);
|
auto actualSize = size + sizeof(Header);
|
||||||
void* actualBlock = (void*)header;
|
void* actualBlock = (void*)header;
|
||||||
|
|
||||||
InsertToFreelist(actualBlock, size);
|
InsertToFreelist(actualBlock, actualSize);
|
||||||
|
|
||||||
Lock.Release();
|
Lock.Release();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,26 @@ namespace Memory::VMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
void Paging::Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||||
Panic("Value that isn't page-aligned passed as address to Paging::Map!", nullptr);
|
Panic("Value that isn't page-aligned passed as address to Paging::Map!", nullptr);
|
||||||
@@ -95,6 +115,76 @@ namespace Memory::VMM {
|
|||||||
pageEntry->Address = physicalAddress >> 12;
|
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) {
|
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
|
||||||
VirtualAddress virtualAddressObj(virtualAddress);
|
VirtualAddress virtualAddressObj(virtualAddress);
|
||||||
|
|
||||||
|
|||||||
@@ -79,20 +79,31 @@ namespace Memory::VMM {
|
|||||||
|
|
||||||
else if (level == 1)
|
else if (level == 1)
|
||||||
return GetPageIndex();
|
return GetPageIndex();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Paging {
|
class Paging {
|
||||||
|
PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
|
||||||
|
PageTable* HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, size_t level);
|
||||||
|
public:
|
||||||
PageTable* PML4{};
|
PageTable* PML4{};
|
||||||
|
|
||||||
PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
|
|
||||||
public:
|
|
||||||
Paging();
|
Paging();
|
||||||
void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap);
|
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 Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||||
void MapMMIO(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);
|
static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false);
|
||||||
std::uint64_t GetPhysAddr(std::uint64_t virtualAddress);
|
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 Paging* g_paging;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Arp.hpp"
|
#include "Arp.hpp"
|
||||||
#include <Net/ByteOrder.hpp>
|
#include <Net/ByteOrder.hpp>
|
||||||
#include <Net/Ethernet.hpp>
|
#include <Net/Ethernet.hpp>
|
||||||
|
#include <Net/Ipv4.hpp>
|
||||||
#include <Net/NetConfig.hpp>
|
#include <Net/NetConfig.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
#include <Libraries/Memory.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 senderIp = pkt->SenderIp; // Already in network byte order in struct
|
||||||
uint32_t targetIp = pkt->TargetIp;
|
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);
|
CacheInsert(senderIp, pkt->SenderMac);
|
||||||
|
Ipv4::FlushPending();
|
||||||
|
|
||||||
uint16_t op = Ntohs(pkt->Operation);
|
uint16_t op = Ntohs(pkt->Operation);
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,40 @@ using namespace Kt;
|
|||||||
|
|
||||||
namespace Net::Icmp {
|
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() {
|
void Initialize() {
|
||||||
KernelLogStream(OK, "Net") << "ICMP initialized";
|
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) {
|
void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length) {
|
||||||
if (length < sizeof(Header)) {
|
if (length < sizeof(Header)) {
|
||||||
return;
|
return;
|
||||||
@@ -54,6 +84,10 @@ namespace Net::Icmp {
|
|||||||
replyHdr->Checksum = Ipv4::Checksum(reply, length);
|
replyHdr->Checksum = Ipv4::Checksum(reply, length);
|
||||||
|
|
||||||
Ipv4::Send(srcIp, Ipv4::PROTO_ICMP, 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)
|
// Handle an incoming ICMP packet (called by IPv4 layer)
|
||||||
void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length);
|
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 <Libraries/Memory.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
@@ -23,6 +22,18 @@ namespace Net::Ipv4 {
|
|||||||
|
|
||||||
static uint16_t g_identification = 0;
|
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() {
|
void Initialize() {
|
||||||
g_identification = 0;
|
g_identification = 0;
|
||||||
KernelLogStream(OK, "Net") << "IPv4 initialized, IP: "
|
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) {
|
// Build and send an IP packet over Ethernet (MAC already resolved)
|
||||||
if (payloadLen > (Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE)) {
|
static bool SendDirect(uint32_t destIp, uint8_t protocol, const uint8_t* destMac,
|
||||||
return false;
|
const uint8_t* payload, uint16_t payloadLen) {
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
uint8_t packet[Ethernet::MAX_PAYLOAD_SIZE];
|
uint8_t packet[Ethernet::MAX_PAYLOAD_SIZE];
|
||||||
Header* hdr = (Header*)packet;
|
Header* hdr = (Header*)packet;
|
||||||
|
|
||||||
@@ -176,13 +166,57 @@ namespace Net::Ipv4 {
|
|||||||
hdr->SrcIp = GetIpAddress();
|
hdr->SrcIp = GetIpAddress();
|
||||||
hdr->DstIp = destIp;
|
hdr->DstIp = destIp;
|
||||||
|
|
||||||
// Calculate header checksum
|
|
||||||
hdr->Checksum = Checksum(hdr, HEADER_SIZE);
|
hdr->Checksum = Checksum(hdr, HEADER_SIZE);
|
||||||
|
|
||||||
// Copy payload
|
|
||||||
memcpy(packet + HEADER_SIZE, payload, payloadLen);
|
memcpy(packet + HEADER_SIZE, payload, payloadLen);
|
||||||
|
|
||||||
return Ethernet::Send(destMac, Ethernet::ETHERTYPE_IPV4, packet, HEADER_SIZE + 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)
|
// Handle an incoming IP packet (called by Ethernet layer)
|
||||||
void OnPacketReceived(const uint8_t* data, uint16_t length);
|
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);
|
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
|
// Compute the Internet checksum over a buffer
|
||||||
uint16_t Checksum(const void* data, uint16_t length);
|
uint16_t Checksum(const void* data, uint16_t length);
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ namespace {
|
|||||||
.response = nullptr
|
.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.
|
// 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) {
|
void Putchar(char c) {
|
||||||
|
if (c == '\n') {
|
||||||
|
flanterm_write(ctx, "\r\n", 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
flanterm_write(ctx, &c, 1);
|
flanterm_write(ctx, &c, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Print(const char *text) {
|
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 <Io/IoPort.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
@@ -34,9 +35,15 @@ namespace Timekeeping {
|
|||||||
static volatile uint64_t g_tickCount = 0;
|
static volatile uint64_t g_tickCount = 0;
|
||||||
static uint32_t g_ticksPerMs = 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) {
|
static void TimerHandler(uint8_t) {
|
||||||
g_tickCount = g_tickCount + 1;
|
g_tickCount = g_tickCount + 1;
|
||||||
|
|
||||||
|
if (g_schedEnabled) {
|
||||||
|
Sched::Tick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use PIT channel 2 to create a precise delay for calibration.
|
// Use PIT channel 2 to create a precise delay for calibration.
|
||||||
@@ -128,6 +135,10 @@ namespace Timekeeping {
|
|||||||
return g_tickCount; // 1 tick = 1 ms at 1000 Hz
|
return g_tickCount; // 1 tick = 1 ms at 1000 Hz
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnableSchedulerTick() {
|
||||||
|
g_schedEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Sleep(uint64_t ms) {
|
void Sleep(uint64_t ms) {
|
||||||
uint64_t target = g_tickCount + ms;
|
uint64_t target = g_tickCount + ms;
|
||||||
while (g_tickCount < target) {
|
while (g_tickCount < target) {
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ namespace Timekeeping {
|
|||||||
// Get elapsed milliseconds since timer initialization
|
// Get elapsed milliseconds since timer initialization
|
||||||
uint64_t GetMilliseconds();
|
uint64_t GetMilliseconds();
|
||||||
|
|
||||||
|
// Enable scheduler tick (called after scheduler is initialized)
|
||||||
|
void EnableSchedulerTick();
|
||||||
|
|
||||||
// Busy-wait sleep for the given number of milliseconds
|
// Busy-wait sleep for the given number of milliseconds
|
||||||
void Sleep(uint64_t ms);
|
void Sleep(uint64_t ms);
|
||||||
};
|
};
|
||||||
@@ -8,3 +8,7 @@ timeout: 0
|
|||||||
|
|
||||||
# Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located.
|
# Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located.
|
||||||
path: boot():/boot/kernel
|
path: boot():/boot/kernel
|
||||||
|
|
||||||
|
# Ramdisk module (USTAR tar archive)
|
||||||
|
module_path: boot():/boot/ramdisk.tar
|
||||||
|
module_string: ramdisk
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Nuke built-in rules and variables.
|
||||||
|
MAKEFLAGS += -rR
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
# Target architecture.
|
||||||
|
ARCH := x86_64
|
||||||
|
|
||||||
|
# Auto-detect cross compiler from toolchain/local/.
|
||||||
|
TOOLCHAIN_PREFIX := $(shell cd .. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||||
|
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||||
|
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||||
|
else
|
||||||
|
CXX := g++
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Compiler flags: freestanding, no stdlib, kernel-mode compatible.
|
||||||
|
override CXXFLAGS := \
|
||||||
|
-std=gnu++20 \
|
||||||
|
-g -O2 -pipe \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-nostdinc \
|
||||||
|
-ffreestanding \
|
||||||
|
-fno-stack-protector \
|
||||||
|
-fno-stack-check \
|
||||||
|
-fno-PIC \
|
||||||
|
-fno-rtti \
|
||||||
|
-fno-exceptions \
|
||||||
|
-ffunction-sections \
|
||||||
|
-fdata-sections \
|
||||||
|
-m64 \
|
||||||
|
-march=x86-64 \
|
||||||
|
-mno-80387 \
|
||||||
|
-mno-mmx \
|
||||||
|
-mno-sse \
|
||||||
|
-mno-sse2 \
|
||||||
|
-mno-red-zone \
|
||||||
|
-mcmodel=small \
|
||||||
|
-I include \
|
||||||
|
-isystem ../kernel/freestnd-c-hdrs/x86_64/include \
|
||||||
|
-isystem ../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||||
|
|
||||||
|
# Linker flags: freestanding static ELF.
|
||||||
|
override LDFLAGS := \
|
||||||
|
-nostdlib \
|
||||||
|
-static \
|
||||||
|
-Wl,--build-id=none \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-Wl,-m,elf_x86_64 \
|
||||||
|
-z max-page-size=0x1000 \
|
||||||
|
-T link.ld
|
||||||
|
|
||||||
|
# Output directory.
|
||||||
|
BINDIR := bin
|
||||||
|
|
||||||
|
# Discover all programs (each subdirectory under src/ is a program).
|
||||||
|
PROGRAMS := $(notdir $(wildcard src/*))
|
||||||
|
|
||||||
|
# Build targets: one ELF per program.
|
||||||
|
TARGETS := $(addprefix $(BINDIR)/,$(addsuffix .elf,$(PROGRAMS)))
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGETS)
|
||||||
|
|
||||||
|
# Build each program from its source files.
|
||||||
|
# For now each program is a single .cpp file compiled and linked directly.
|
||||||
|
$(BINDIR)/%.elf: src/%/main.cpp link.ld GNUmakefile
|
||||||
|
mkdir -p $(BINDIR) obj/$*
|
||||||
|
$(CXX) $(CXXFLAGS) -c src/$*/main.cpp -o obj/$*/main.o
|
||||||
|
$(CXX) $(CXXFLAGS) $(LDFLAGS) obj/$*/main.o -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BINDIR) obj
|
||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Syscall.hpp
|
||||||
|
* ZenithOS syscall definitions for userspace 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* syscall.h
|
||||||
|
* ZenithOS program-side syscall wrappers using SYSCALL instruction
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Api/Syscall.hpp>
|
||||||
|
|
||||||
|
namespace zenith {
|
||||||
|
|
||||||
|
// ---- Raw SYSCALL wrappers ----
|
||||||
|
|
||||||
|
// The SYSCALL handler does not restore RDI, RSI, RDX, R10, R8, R9
|
||||||
|
// (they are skipped on the return path). We move arguments into the
|
||||||
|
// correct registers inside the asm block and list ALL argument
|
||||||
|
// registers in the clobber list. This guarantees the compiler
|
||||||
|
// reloads every argument on each call — GCC cannot optimise away
|
||||||
|
// clobbers, unlike "+r" outputs whose dead values it may discard.
|
||||||
|
|
||||||
|
inline int64_t syscall0(uint64_t nr) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile("syscall" : "=a"(ret) : "a"(nr)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t syscall1(uint64_t nr, uint64_t a1) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile(
|
||||||
|
"mov %[a1], %%rdi\n\t"
|
||||||
|
"syscall"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(nr), [a1] "r"(a1)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t syscall2(uint64_t nr, uint64_t a1, uint64_t a2) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile(
|
||||||
|
"mov %[a1], %%rdi\n\t"
|
||||||
|
"mov %[a2], %%rsi\n\t"
|
||||||
|
"syscall"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t syscall3(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile(
|
||||||
|
"mov %[a1], %%rdi\n\t"
|
||||||
|
"mov %[a2], %%rsi\n\t"
|
||||||
|
"mov %[a3], %%rdx\n\t"
|
||||||
|
"syscall"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t syscall4(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile(
|
||||||
|
"mov %[a1], %%rdi\n\t"
|
||||||
|
"mov %[a2], %%rsi\n\t"
|
||||||
|
"mov %[a3], %%rdx\n\t"
|
||||||
|
"mov %[a4], %%r10\n\t"
|
||||||
|
"syscall"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t syscall5(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile(
|
||||||
|
"mov %[a1], %%rdi\n\t"
|
||||||
|
"mov %[a2], %%rsi\n\t"
|
||||||
|
"mov %[a3], %%rdx\n\t"
|
||||||
|
"mov %[a4], %%r10\n\t"
|
||||||
|
"mov %[a5], %%r8\n\t"
|
||||||
|
"syscall"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4), [a5] "r"(a5)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t syscall6(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) {
|
||||||
|
int64_t ret;
|
||||||
|
asm volatile(
|
||||||
|
"mov %[a1], %%rdi\n\t"
|
||||||
|
"mov %[a2], %%rsi\n\t"
|
||||||
|
"mov %[a3], %%rdx\n\t"
|
||||||
|
"mov %[a4], %%r10\n\t"
|
||||||
|
"mov %[a5], %%r8\n\t"
|
||||||
|
"mov %[a6], %%r9\n\t"
|
||||||
|
"syscall"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4), [a5] "r"(a5), [a6] "r"(a6)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Typed wrappers ----
|
||||||
|
|
||||||
|
// Process
|
||||||
|
[[noreturn]] inline void exit(int code = 0) {
|
||||||
|
syscall1(Zenith::SYS_EXIT, (uint64_t)code);
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void yield() { syscall0(Zenith::SYS_YIELD); }
|
||||||
|
inline void sleep_ms(uint64_t ms) { syscall1(Zenith::SYS_SLEEP_MS, ms); }
|
||||||
|
inline int getpid() { return (int)syscall0(Zenith::SYS_GETPID); }
|
||||||
|
|
||||||
|
// Console
|
||||||
|
inline void print(const char* text) { syscall1(Zenith::SYS_PRINT, (uint64_t)text); }
|
||||||
|
inline void putchar(char c) { syscall1(Zenith::SYS_PUTCHAR, (uint64_t)c); }
|
||||||
|
|
||||||
|
// File I/O
|
||||||
|
inline int open(const char* path) { return (int)syscall1(Zenith::SYS_OPEN, (uint64_t)path); }
|
||||||
|
inline int read(int handle, uint8_t* buf, uint64_t off, uint64_t size) {
|
||||||
|
return (int)syscall4(Zenith::SYS_READ, (uint64_t)handle, (uint64_t)buf, off, size);
|
||||||
|
}
|
||||||
|
inline uint64_t getsize(int handle) { return (uint64_t)syscall1(Zenith::SYS_GETSIZE, (uint64_t)handle); }
|
||||||
|
inline void close(int handle) { syscall1(Zenith::SYS_CLOSE, (uint64_t)handle); }
|
||||||
|
inline int readdir(const char* path, const char** names, int max) {
|
||||||
|
return (int)syscall3(Zenith::SYS_READDIR, (uint64_t)path, (uint64_t)names, (uint64_t)max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
inline void* alloc(uint64_t size) { return (void*)syscall1(Zenith::SYS_ALLOC, size); }
|
||||||
|
inline void free(void* ptr) { syscall1(Zenith::SYS_FREE, (uint64_t)ptr); }
|
||||||
|
|
||||||
|
// Timekeeping
|
||||||
|
inline uint64_t get_ticks() { return (uint64_t)syscall0(Zenith::SYS_GETTICKS); }
|
||||||
|
inline uint64_t get_milliseconds() { return (uint64_t)syscall0(Zenith::SYS_GETMILLISECONDS); }
|
||||||
|
|
||||||
|
// System
|
||||||
|
inline void get_info(Zenith::SysInfo* info) { syscall1(Zenith::SYS_GETINFO, (uint64_t)info); }
|
||||||
|
|
||||||
|
// Keyboard
|
||||||
|
inline bool is_key_available() { return (bool)syscall0(Zenith::SYS_ISKEYAVAILABLE); }
|
||||||
|
inline void getkey(Zenith::KeyEvent* out) { syscall1(Zenith::SYS_GETKEY, (uint64_t)out); }
|
||||||
|
inline char getchar() { return (char)syscall0(Zenith::SYS_GETCHAR); }
|
||||||
|
|
||||||
|
// Networking
|
||||||
|
inline int32_t ping(uint32_t ip, uint32_t timeoutMs = 3000) {
|
||||||
|
return (int32_t)syscall2(Zenith::SYS_PING, (uint64_t)ip, (uint64_t)timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* link.ld
|
||||||
|
* Linker script for ZenithOS userspace programs
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*
|
||||||
|
* Programs are loaded at a standard user-space address.
|
||||||
|
*/
|
||||||
|
|
||||||
|
OUTPUT_FORMAT(elf64-x86-64)
|
||||||
|
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 0x400000;
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
*(.text .text.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
. = ALIGN(4096);
|
||||||
|
|
||||||
|
.rodata : {
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
. = ALIGN(4096);
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
}
|
||||||
|
|
||||||
|
/DISCARD/ : {
|
||||||
|
*(.eh_frame*)
|
||||||
|
*(.note .note.*)
|
||||||
|
*(.comment*)
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
* Hello world program for ZenithOS
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zenith/syscall.h>
|
||||||
|
|
||||||
|
extern "C" void _start() {
|
||||||
|
|
||||||
|
|
||||||
|
zenith::print("Hello from userspace!\n");
|
||||||
|
|
||||||
|
// while(true) {
|
||||||
|
// zenith::print("ab");
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
* Interactive shell for ZenithOS
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zenith/syscall.h>
|
||||||
|
|
||||||
|
static bool streq(const char* a, const char* b) {
|
||||||
|
while (*a && *b) {
|
||||||
|
if (*a != *b) return false;
|
||||||
|
a++; b++;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool starts_with(const char* str, const char* prefix) {
|
||||||
|
while (*prefix) {
|
||||||
|
if (*str != *prefix) return false;
|
||||||
|
str++; prefix++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* skip_spaces(const char* s) {
|
||||||
|
while (*s == ' ') s++;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_int(uint64_t n) {
|
||||||
|
if (n == 0) {
|
||||||
|
zenith::putchar('0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char buf[20];
|
||||||
|
int i = 0;
|
||||||
|
while (n > 0) {
|
||||||
|
buf[i++] = '0' + (n % 10);
|
||||||
|
n /= 10;
|
||||||
|
}
|
||||||
|
for (int j = i - 1; j >= 0; j--) {
|
||||||
|
zenith::putchar(buf[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prompt() {
|
||||||
|
zenith::print("zenith> ");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_help() {
|
||||||
|
zenith::print("Available commands:\n");
|
||||||
|
zenith::print(" help Show this help message\n");
|
||||||
|
zenith::print(" info Show system information\n");
|
||||||
|
zenith::print(" ls List ramdisk files\n");
|
||||||
|
zenith::print(" cat <file> Display file contents\n");
|
||||||
|
zenith::print(" ping <ip> Send ICMP echo requests\n");
|
||||||
|
zenith::print(" uptime Show uptime in milliseconds\n");
|
||||||
|
zenith::print(" clear Clear the screen\n");
|
||||||
|
zenith::print(" exit Exit the shell\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_info() {
|
||||||
|
Zenith::SysInfo info;
|
||||||
|
zenith::get_info(&info);
|
||||||
|
zenith::print(info.osName);
|
||||||
|
zenith::print(" v");
|
||||||
|
zenith::print(info.osVersion);
|
||||||
|
zenith::print("\n");
|
||||||
|
zenith::print("Syscall API version: ");
|
||||||
|
print_int(info.apiVersion);
|
||||||
|
zenith::putchar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_ls() {
|
||||||
|
const char* entries[64];
|
||||||
|
int count = zenith::readdir("0:/", entries, 64);
|
||||||
|
if (count <= 0) {
|
||||||
|
zenith::print("(empty)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
zenith::print(" ");
|
||||||
|
zenith::print(entries[i]);
|
||||||
|
zenith::putchar('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_cat(const char* arg) {
|
||||||
|
arg = skip_spaces(arg);
|
||||||
|
if (*arg == '\0') {
|
||||||
|
zenith::print("Usage: cat <filename>\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build path "0:/<filename>"
|
||||||
|
char path[128];
|
||||||
|
const char* prefix = "0:/";
|
||||||
|
int i = 0;
|
||||||
|
while (prefix[i]) { path[i] = prefix[i]; i++; }
|
||||||
|
int j = 0;
|
||||||
|
while (arg[j] && i < 126) { path[i++] = arg[j++]; }
|
||||||
|
path[i] = '\0';
|
||||||
|
|
||||||
|
int handle = zenith::open(path);
|
||||||
|
if (handle < 0) {
|
||||||
|
zenith::print("Error: cannot open '");
|
||||||
|
zenith::print(arg);
|
||||||
|
zenith::print("'\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t size = zenith::getsize(handle);
|
||||||
|
if (size == 0) {
|
||||||
|
zenith::close(handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in chunks
|
||||||
|
uint8_t buf[512];
|
||||||
|
uint64_t offset = 0;
|
||||||
|
while (offset < size) {
|
||||||
|
uint64_t chunk = size - offset;
|
||||||
|
if (chunk > sizeof(buf) - 1) chunk = sizeof(buf) - 1;
|
||||||
|
int bytesRead = zenith::read(handle, buf, offset, chunk);
|
||||||
|
if (bytesRead <= 0) break;
|
||||||
|
buf[bytesRead] = '\0';
|
||||||
|
zenith::print((const char*)buf);
|
||||||
|
offset += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
zenith::close(handle);
|
||||||
|
zenith::putchar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_uptime() {
|
||||||
|
uint64_t ms = zenith::get_milliseconds();
|
||||||
|
uint64_t secs = ms / 1000;
|
||||||
|
uint64_t mins = secs / 60;
|
||||||
|
secs %= 60;
|
||||||
|
ms %= 1000;
|
||||||
|
|
||||||
|
zenith::print("Uptime: ");
|
||||||
|
print_int(mins);
|
||||||
|
zenith::print("m ");
|
||||||
|
print_int(secs);
|
||||||
|
zenith::print("s ");
|
||||||
|
print_int(ms);
|
||||||
|
zenith::print("ms\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_ip(const char* s, uint32_t* out) {
|
||||||
|
// Parse "a.b.c.d" into a uint32_t in network byte order (little-endian stored)
|
||||||
|
uint32_t octets[4];
|
||||||
|
int idx = 0;
|
||||||
|
uint32_t val = 0;
|
||||||
|
bool hasDigit = false;
|
||||||
|
|
||||||
|
for (int i = 0; ; i++) {
|
||||||
|
char c = s[i];
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
val = val * 10 + (c - '0');
|
||||||
|
if (val > 255) return false;
|
||||||
|
hasDigit = true;
|
||||||
|
} else if (c == '.' || c == '\0') {
|
||||||
|
if (!hasDigit || idx >= 4) return false;
|
||||||
|
octets[idx++] = val;
|
||||||
|
val = 0;
|
||||||
|
hasDigit = false;
|
||||||
|
if (c == '\0') break;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx != 4) return false;
|
||||||
|
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_ip(uint32_t ip) {
|
||||||
|
print_int(ip & 0xFF);
|
||||||
|
zenith::putchar('.');
|
||||||
|
print_int((ip >> 8) & 0xFF);
|
||||||
|
zenith::putchar('.');
|
||||||
|
print_int((ip >> 16) & 0xFF);
|
||||||
|
zenith::putchar('.');
|
||||||
|
print_int((ip >> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_ping(const char* arg) {
|
||||||
|
arg = skip_spaces(arg);
|
||||||
|
if (*arg == '\0') {
|
||||||
|
zenith::print("Usage: ping <ip address>\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ip;
|
||||||
|
if (!parse_ip(arg, &ip)) {
|
||||||
|
zenith::print("Invalid IP address: ");
|
||||||
|
zenith::print(arg);
|
||||||
|
zenith::putchar('\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
zenith::print("PING ");
|
||||||
|
print_ip(ip);
|
||||||
|
zenith::putchar('\n');
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int32_t rtt = zenith::ping(ip, 3000);
|
||||||
|
if (rtt < 0) {
|
||||||
|
zenith::print(" Request timed out\n");
|
||||||
|
} else {
|
||||||
|
zenith::print(" Reply from ");
|
||||||
|
print_ip(ip);
|
||||||
|
zenith::print(": time=");
|
||||||
|
print_int((uint64_t)rtt);
|
||||||
|
zenith::print("ms\n");
|
||||||
|
}
|
||||||
|
if (i < 3) {
|
||||||
|
zenith::sleep_ms(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_clear() {
|
||||||
|
// Print enough newlines to scroll past visible content
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
zenith::putchar('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_command(const char* line) {
|
||||||
|
// Skip leading spaces
|
||||||
|
line = skip_spaces(line);
|
||||||
|
if (*line == '\0') return;
|
||||||
|
|
||||||
|
if (streq(line, "help")) {
|
||||||
|
cmd_help();
|
||||||
|
} else if (streq(line, "info")) {
|
||||||
|
cmd_info();
|
||||||
|
} else if (streq(line, "ls")) {
|
||||||
|
cmd_ls();
|
||||||
|
} else if (starts_with(line, "cat ")) {
|
||||||
|
cmd_cat(line + 4);
|
||||||
|
} else if (streq(line, "cat")) {
|
||||||
|
cmd_cat("");
|
||||||
|
} else if (starts_with(line, "ping ")) {
|
||||||
|
cmd_ping(line + 5);
|
||||||
|
} else if (streq(line, "ping")) {
|
||||||
|
cmd_ping("");
|
||||||
|
} else if (streq(line, "uptime")) {
|
||||||
|
cmd_uptime();
|
||||||
|
} else if (streq(line, "clear")) {
|
||||||
|
cmd_clear();
|
||||||
|
} else if (streq(line, "exit")) {
|
||||||
|
zenith::print("Goodbye.\n");
|
||||||
|
zenith::exit(0);
|
||||||
|
} else {
|
||||||
|
zenith::print("Unknown command: ");
|
||||||
|
zenith::print(line);
|
||||||
|
zenith::print("\nType 'help' for available commands.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void _start() {
|
||||||
|
zenith::print("\n");
|
||||||
|
zenith::print(" ZenithOS Shell v0.1\n");
|
||||||
|
zenith::print(" Type 'help' for available commands.\n");
|
||||||
|
zenith::print("\n");
|
||||||
|
|
||||||
|
char line[256];
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
prompt();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
char c = zenith::getchar();
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
zenith::putchar('\n');
|
||||||
|
line[pos] = '\0';
|
||||||
|
process_command(line);
|
||||||
|
pos = 0;
|
||||||
|
prompt();
|
||||||
|
} else if (c == '\b') {
|
||||||
|
if (pos > 0) {
|
||||||
|
pos--;
|
||||||
|
zenith::putchar('\b');
|
||||||
|
zenith::putchar(' ');
|
||||||
|
zenith::putchar('\b');
|
||||||
|
}
|
||||||
|
} else if (c >= ' ' && pos < 255) {
|
||||||
|
line[pos++] = c;
|
||||||
|
zenith::putchar(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# mkramdisk.sh - Create a USTAR tar archive for the ZenithOS ramdisk
|
||||||
|
# Usage: ./scripts/mkramdisk.sh [input_dir] [output_path]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
INPUT_DIR="${1:-programs/bin}"
|
||||||
|
OUTPUT_PATH="${2:-ramdisk.tar}"
|
||||||
|
|
||||||
|
if [ ! -d "$INPUT_DIR" ]; then
|
||||||
|
echo "mkramdisk: input directory '$INPUT_DIR' does not exist, creating empty ramdisk"
|
||||||
|
mkdir -p "$INPUT_DIR"
|
||||||
|
# Create a placeholder file so the tar isn't completely empty
|
||||||
|
echo "ZenithOS ramdisk" > "$INPUT_DIR/readme.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create USTAR tar archive
|
||||||
|
tar --format=ustar -cf "$OUTPUT_PATH" -C "$INPUT_DIR" .
|
||||||
|
|
||||||
|
echo "mkramdisk: created $OUTPUT_PATH from $INPUT_DIR ($(wc -c < "$OUTPUT_PATH") bytes)"
|
||||||
@@ -66,5 +66,12 @@ if command -v nmcli &>/dev/null; then
|
|||||||
nmcli device set "$TAP" managed no 2>/dev/null || true
|
nmcli device set "$TAP" managed no 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Configure DNS on the bridge so systemd-resolved keeps working
|
||||||
|
if command -v resolvectl &>/dev/null && [ -n "$GW" ]; then
|
||||||
|
resolvectl dns "$BRIDGE" "$GW" 2>/dev/null || true
|
||||||
|
resolvectl domain "$BRIDGE" '~.' 2>/dev/null || true
|
||||||
|
echo "Configured DNS on $BRIDGE via $GW"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Network bridge setup complete: $PHYS -> $BRIDGE <- $TAP"
|
echo "Network bridge setup complete: $PHYS -> $BRIDGE <- $TAP"
|
||||||
ip -4 addr show dev "$BRIDGE" | head -3
|
ip -4 addr show dev "$BRIDGE" | head -3
|
||||||
|
|||||||
Reference in New Issue
Block a user