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