feat: scheduling, usermode, shell

This commit is contained in:
2026-02-17 19:17:01 +01:00
parent 20fa8a9be2
commit 605fbcbe42
46 changed files with 2622 additions and 98 deletions
+11 -3
View File
@@ -173,10 +173,16 @@ kernel-deps:
kernel: kernel-deps kernel: kernel-deps
$(MAKE) -C kernel $(MAKE) -C kernel
$(IMAGE_NAME).iso: limine/limine kernel
.PHONY: ramdisk
ramdisk: programs
./scripts/mkramdisk.sh programs/bin ramdisk.tar
$(IMAGE_NAME).iso: limine/limine kernel ramdisk
rm -rf iso_root rm -rf iso_root
mkdir -p iso_root/boot mkdir -p iso_root/boot
cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/ cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/
cp -v ramdisk.tar iso_root/boot/
mkdir -p iso_root/boot/limine mkdir -p iso_root/boot/limine
cp -v limine.conf iso_root/boot/limine/ cp -v limine.conf iso_root/boot/limine/
mkdir -p iso_root/EFI/BOOT mkdir -p iso_root/EFI/BOOT
@@ -220,7 +226,7 @@ ifeq ($(ARCH),loongarch64)
endif endif
rm -rf iso_root rm -rf iso_root
$(IMAGE_NAME).hdd: limine/limine kernel $(IMAGE_NAME).hdd: limine/limine kernel ramdisk
rm -f $(IMAGE_NAME).hdd rm -f $(IMAGE_NAME).hdd
dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd
PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00 PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00
@@ -230,6 +236,7 @@ endif
mformat -i $(IMAGE_NAME).hdd@@1M mformat -i $(IMAGE_NAME).hdd@@1M
mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine
mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot
mcopy -i $(IMAGE_NAME).hdd@@1M ramdisk.tar ::/boot
mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine
ifeq ($(ARCH),x86_64) ifeq ($(ARCH),x86_64)
mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine
@@ -249,7 +256,8 @@ endif
.PHONY: clean .PHONY: clean
clean: clean:
$(MAKE) -C kernel clean $(MAKE) -C kernel clean
rm -rf iso_root $(IMAGE_NAME).iso $(IMAGE_NAME).hdd # $(MAKE) -C programs clean
rm -rf iso_root $(IMAGE_NAME).iso $(IMAGE_NAME).hdd ramdisk.tar
.PHONY: distclean .PHONY: distclean
distclean: distclean:
+280
View File
@@ -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)";
}
}
+62
View File
@@ -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();
}
+1 -1
View File
@@ -153,7 +153,7 @@ namespace Drivers::PS2 {
SendCommand(CmdReadConfig); SendCommand(CmdReadConfig);
config = ReadData(); config = ReadData();
config |= ConfigPort1Interrupt; config |= ConfigPort1Interrupt | ConfigPort1Translation;
if (g_DualChannel) { if (g_DualChannel) {
config |= ConfigPort2Interrupt; config |= ConfigPort2Interrupt;
} }
+242
View File
@@ -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;
}
}
+33
View File
@@ -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();
}
+143
View File
@@ -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);
}
}
+33
View File
@@ -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);
}
+6
View File
@@ -8,6 +8,7 @@ section .text ; Text/code section
global ReloadSegments global ReloadSegments
global LoadGDT global LoadGDT
global LoadTR
LoadGDT: LoadGDT:
lgdt [rdi] ; Run LGDT on the contents of 1st C parameter lgdt [rdi] ; Run LGDT on the contents of 1st C parameter
@@ -29,3 +30,8 @@ ReloadSegments:
mov ss, ax mov ss, ax
ret ret
LoadTR:
mov ax, 0x28 ; TSS selector
ltr ax
ret
+41 -8
View File
@@ -1,29 +1,56 @@
/* /*
* gdt.hpp * gdt.cpp
* Intel Global Descriptor Table * Intel Global Descriptor Table
* Copyright (c) 2025 Daniel Hammer * Copyright (c) 2025 Daniel Hammer
*/ */
#include "GDT.hpp" #include "GDT.hpp"
#include "../Terminal/Terminal.hpp" #include "../Terminal/Terminal.hpp"
#include <Libraries/Memory.hpp>
namespace Hal { namespace Hal {
using namespace Kt; using namespace Kt;
GDTPointer gdtPointer{}; GDTPointer gdtPointer{};
BasicGDT kernelGDT{}; BasicGDT kernelGDT{};
TSS64 g_tss{};
void PrepareGDT() { void PrepareGDT() {
kernelGDT = { // Zero the TSS
{0xFFFF, 0, 0, 0x00, 0x00, 0}, memset(&g_tss, 0, sizeof(g_tss));
{0xFFFF, 0, 0, 0x9A, 0xA0, 0}, g_tss.iopbOffset = sizeof(TSS64);
{0xFFFF, 0, 0, 0x92, 0xA0, 0},
{0xFFFF, 0, 0, 0x9A, 0xA0, 0},
{0xFFFF, 0, 0, 0x92, 0xA0, 0},
{0, 0, 0, 0xFA, 0x00, 0x0}, kernelGDT = {
{0xFFFF, 0, 0, 0x00, 0x00, 0}, // Null
{0xFFFF, 0, 0, 0x9A, 0xA0, 0}, // KernelCode (DPL=0, code, 64-bit)
{0xFFFF, 0, 0, 0x92, 0xA0, 0}, // KernelData (DPL=0, data)
{0xFFFF, 0, 0, 0xF2, 0xA0, 0}, // UserData (DPL=3, data)
{0xFFFF, 0, 0, 0xFA, 0xA0, 0}, // UserCode (DPL=3, code, 64-bit)
{0, 0, 0, 0, 0, 0}, // TSS low (filled below)
{0, 0, 0, 0, 0, 0}, // TSS high (filled below)
}; };
// Encode 16-byte TSS descriptor
uint64_t base = (uint64_t)&g_tss;
uint32_t limit = sizeof(TSS64) - 1;
// Low 8 bytes (normal GDT entry format)
kernelGDT.TSS.LimitLow = limit & 0xFFFF;
kernelGDT.TSS.BaseLow = base & 0xFFFF;
kernelGDT.TSS.BaseMiddle = (base >> 16) & 0xFF;
kernelGDT.TSS.AccessByte = 0x89; // Present, 64-bit TSS Available
kernelGDT.TSS.GranularityByte = (limit >> 16) & 0x0F;
kernelGDT.TSS.BaseHigh = (base >> 24) & 0xFF;
// High 8 bytes (base[63:32] + reserved)
uint32_t baseUpper = (uint32_t)(base >> 32);
kernelGDT.TSSHigh.LimitLow = baseUpper & 0xFFFF;
kernelGDT.TSSHigh.BaseLow = (baseUpper >> 16) & 0xFFFF;
kernelGDT.TSSHigh.BaseMiddle = 0;
kernelGDT.TSSHigh.AccessByte = 0;
kernelGDT.TSSHigh.GranularityByte = 0;
kernelGDT.TSSHigh.BaseHigh = 0;
gdtPointer = GDTPointer{ gdtPointer = GDTPointer{
.Size = sizeof(kernelGDT) - 1, .Size = sizeof(kernelGDT) - 1,
.GDTAddress = (uint64_t)&kernelGDT .GDTAddress = (uint64_t)&kernelGDT
@@ -33,6 +60,7 @@ namespace Hal {
// Helpers implemented in gdt.asm // Helpers implemented in gdt.asm
extern "C" void LoadGDT(GDTPointer *gdtPointer); extern "C" void LoadGDT(GDTPointer *gdtPointer);
extern "C" void ReloadSegments(); extern "C" void ReloadSegments();
extern "C" void LoadTR();
void BridgeLoadGDT() { void BridgeLoadGDT() {
LoadGDT(&gdtPointer); LoadGDT(&gdtPointer);
@@ -40,4 +68,9 @@ namespace Hal {
KernelLogStream(DEBUG, "Hal") << "Set new GDT (0x" << base::hex << (uint64_t)&kernelGDT << ")"; KernelLogStream(DEBUG, "Hal") << "Set new GDT (0x" << base::hex << (uint64_t)&kernelGDT << ")";
} }
void LoadTSS() {
LoadTR();
KernelLogStream(OK, "Hal") << "Loaded TSS (selector 0x28)";
}
}; };
+28 -8
View File
@@ -21,14 +21,13 @@ namespace Hal {
}__attribute__((packed)); }__attribute__((packed));
struct BasicGDT { struct BasicGDT {
GDTEntry Null; GDTEntry Null; // 0x00
GDTEntry KernelCode; GDTEntry KernelCode; // 0x08
GDTEntry KernelData; GDTEntry KernelData; // 0x10
GDTEntry UserCode; GDTEntry UserData; // 0x18 (before UserCode for SYSRET)
GDTEntry UserData; GDTEntry UserCode; // 0x20
GDTEntry TSS; // 0x28 (low 8 bytes of 16-byte TSS descriptor)
// Task State Segment GDTEntry TSSHigh; // 0x30 (high 8 bytes of 16-byte TSS descriptor)
GDTEntry TSS;
}__attribute__((packed)); }__attribute__((packed));
struct GDTPointer { struct GDTPointer {
@@ -36,6 +35,27 @@ namespace Hal {
uint64_t GDTAddress; uint64_t GDTAddress;
}__attribute__((packed)); }__attribute__((packed));
struct TSS64 {
uint32_t reserved0;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint64_t reserved1;
uint64_t ist1;
uint64_t ist2;
uint64_t ist3;
uint64_t ist4;
uint64_t ist5;
uint64_t ist6;
uint64_t ist7;
uint64_t reserved2;
uint16_t reserved3;
uint16_t iopbOffset;
}__attribute__((packed));
extern TSS64 g_tss;
void BridgeLoadGDT(); void BridgeLoadGDT();
void PrepareGDT(); void PrepareGDT();
void LoadTSS();
}; };
+30
View File
@@ -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;
}
+89
View File
@@ -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
View File
@@ -37,7 +37,10 @@
#include <Net/Net.hpp> #include <Net/Net.hpp>
#include <CppLib/BoxUI.hpp> #include <CppLib/BoxUI.hpp>
#include <Graphics/Cursor.hpp> #include <Graphics/Cursor.hpp>
#include <Fs/Ramdisk.hpp>
#include <Fs/Vfs.hpp>
#include <Sched/Scheduler.hpp>
#include <Api/Syscall.hpp>
using namespace Kt; using namespace Kt;
namespace Memory { namespace Memory {
@@ -95,9 +98,11 @@ extern "C" void kmain() {
uint64_t hhdm_offset = hhdm_request.response->offset; uint64_t hhdm_offset = hhdm_request.response->offset;
Memory::HHDMBase = hhdm_offset; Memory::HHDMBase = hhdm_offset;
if (memmap_request.response != nullptr) { if (memmap_request.response == nullptr) {
Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator"; Panic("System memory map missing!", nullptr);
}
Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator";
Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response)); Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response));
Memory::g_pfa = &pmm; Memory::g_pfa = &pmm;
@@ -107,10 +112,6 @@ extern "C" void kmain() {
heap.Walk(); heap.Walk();
} else {
Panic("System memory map missing!", nullptr);
}
#if defined (__x86_64__) #if defined (__x86_64__)
Hal::IDTInitialize(); Hal::IDTInitialize();
@@ -142,8 +143,49 @@ extern "C" void kmain() {
Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address); Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address);
Efi::Init(ST); Efi::Init(ST);
// Initialize ramdisk from Limine modules
if (module_request.response != nullptr && module_request.response->module_count > 0) {
Kt::KernelLogStream(OK, "Modules") << "Found " << (uint64_t)module_request.response->module_count << " module(s)";
for (uint64_t i = 0; i < module_request.response->module_count; i++) {
limine_file* mod = module_request.response->modules[i];
const char* modString = mod->string;
// Find "ramdisk" module by its string
if (modString != nullptr &&
modString[0] == 'r' && modString[1] == 'a' && modString[2] == 'm' &&
modString[3] == 'd' && modString[4] == 'i' && modString[5] == 's' &&
modString[6] == 'k' && modString[7] == '\0') {
Kt::KernelLogStream(OK, "Modules") << "Ramdisk module at " << kcp::hex << (uint64_t)mod->address << kcp::dec << ", size=" << mod->size;
Fs::Ramdisk::Initialize(mod->address, mod->size);
}
}
} else {
Kt::KernelLogStream(WARNING, "Modules") << "No modules loaded (ramdisk unavailable)";
}
// Initialize VFS and register ramdisk as drive 0
Fs::Vfs::Initialize();
static Fs::Vfs::FsDriver ramdiskDriver = {
Fs::Ramdisk::Open,
Fs::Ramdisk::Read,
Fs::Ramdisk::GetSize,
Fs::Ramdisk::Close,
Fs::Ramdisk::ReadDir
};
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
Graphics::Cursor::Initialize(framebuffer); Graphics::Cursor::Initialize(framebuffer);
Hal::LoadTSS();
Zenith::InitializeSyscalls();
Sched::Initialize();
Sched::Spawn("0:/shell.elf");
// Enable preemptive scheduling via the APIC timer
Timekeeping::EnableSchedulerTick();
// Main loop: update cursor position and halt until next interrupt // Main loop: update cursor position and halt until next interrupt
for (;;) { for (;;) {
Graphics::Cursor::Update(); Graphics::Cursor::Update();
+8 -6
View File
@@ -87,12 +87,12 @@ namespace Memory
prev = current; prev = current;
current = current->next; current = current->next;
Lock.Release();
} }
// First pass allocation failed Lock.Release();
size_t pagesNeeded = size / 0x1000;
// First pass allocation failed -- grow the heap
size_t pagesNeeded = (sizeNeeded + 0xFFF) / 0x1000;
InsertPagesToFreelist(pagesNeeded); InsertPagesToFreelist(pagesNeeded);
return Request(size); return Request(size);
@@ -102,7 +102,9 @@ namespace Memory
auto new_block = Request(size); auto new_block = Request(size);
if (ptr != nullptr && new_block != nullptr) { if (ptr != nullptr && new_block != nullptr) {
memcpy(new_block, ptr, size); size_t oldSize = GetAllocatedBlockSize(ptr);
size_t copySize = (oldSize < size) ? oldSize : size;
memcpy(new_block, ptr, copySize);
Free(ptr); Free(ptr);
} }
@@ -123,7 +125,7 @@ namespace Memory
auto actualSize = size + sizeof(Header); auto actualSize = size + sizeof(Header);
void* actualBlock = (void*)header; void* actualBlock = (void*)header;
InsertToFreelist(actualBlock, size); InsertToFreelist(actualBlock, actualSize);
Lock.Release(); Lock.Release();
} }
+90
View File
@@ -55,6 +55,26 @@ namespace Memory::VMM {
} }
} }
PageTable* Paging::HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]);
if (!entry->Present) {
entry->Present = true;
entry->Writable = true;
entry->Supervisor = 1; // User-accessible
uint64_t downLevelAddr = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
entry->Address = downLevelAddr >> 12;
return (PageTable*)downLevelAddr;
} else {
// Ensure User bit is set on existing entries in the user path
entry->Supervisor = 1;
return (PageTable*)(entry->Address << 12);
}
}
void Paging::Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress) { void Paging::Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) { if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Value that isn't page-aligned passed as address to Paging::Map!", nullptr); Panic("Value that isn't page-aligned passed as address to Paging::Map!", nullptr);
@@ -95,6 +115,76 @@ namespace Memory::VMM {
pageEntry->Address = physicalAddress >> 12; pageEntry->Address = physicalAddress >> 12;
} }
void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Value that isn't page-aligned passed as address to Paging::MapUser!", nullptr);
}
VirtualAddress virtualAddressObj(virtualAddress);
auto PML3 = HandleLevelUser(virtualAddressObj, PML4, 4);
auto PML2 = HandleLevelUser(virtualAddressObj, PML3, 3);
auto PML1 = HandleLevelUser(virtualAddressObj, PML2, 2);
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]);
pageEntry->Present = true;
pageEntry->Writable = true;
pageEntry->Supervisor = 1; // User-accessible
pageEntry->Address = physicalAddress >> 12;
}
std::uint64_t Paging::CreateUserPML4() {
// Allocate a new PML4
void* newPage = Memory::g_pfa->AllocateZeroed();
uint64_t newPml4Phys = Memory::SubHHDM((uint64_t)newPage);
PageTable* newPml4 = (PageTable*)newPage; // HHDM virtual address
// Copy kernel-half entries (256-511) from the global PML4
PageTable* kernelPml4 = (PageTable*)Memory::HHDM((uint64_t)g_paging->PML4);
for (int i = 256; i < 512; i++) {
newPml4->entries[i] = kernelPml4->entries[i];
}
return newPml4Phys;
}
void Paging::MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Non-aligned address in Paging::MapUserIn!", nullptr);
}
VirtualAddress va(virtualAddress);
// Walk/create page tables from the given PML4, setting User bit at each level
auto walkLevel = [](PageTable* table, uint64_t index) -> PageTable* {
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[index]);
if (!entry->Present) {
entry->Present = true;
entry->Writable = true;
entry->Supervisor = 1; // User-accessible
uint64_t newPhys = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
entry->Address = newPhys >> 12;
return (PageTable*)newPhys;
} else {
entry->Supervisor = 1;
return (PageTable*)(entry->Address << 12);
}
};
PageTable* pml4 = (PageTable*)pml4Phys;
auto pml3 = walkLevel(pml4, va.GetL4Index());
auto pml2 = walkLevel(pml3, va.GetL3Index());
auto pml1 = walkLevel(pml2, va.GetL2Index());
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]);
pageEntry->Present = true;
pageEntry->Writable = true;
pageEntry->Supervisor = 1;
pageEntry->Address = physicalAddress >> 12;
}
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) { std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
VirtualAddress virtualAddressObj(virtualAddress); VirtualAddress virtualAddressObj(virtualAddress);
+13 -2
View File
@@ -79,20 +79,31 @@ namespace Memory::VMM {
else if (level == 1) else if (level == 1)
return GetPageIndex(); return GetPageIndex();
return 0;
} }
}; };
class Paging { class Paging {
PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
PageTable* HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, size_t level);
public:
PageTable* PML4{}; PageTable* PML4{};
PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
public:
Paging(); Paging();
void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap); void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap);
void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress); void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress); void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false); static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false);
std::uint64_t GetPhysAddr(std::uint64_t virtualAddress); std::uint64_t GetPhysAddr(std::uint64_t virtualAddress);
// Create a new PML4 with kernel-half (entries 256-511) copied from g_paging.
// Returns the physical address of the new PML4.
static std::uint64_t CreateUserPML4();
// Map a page into an arbitrary PML4 (specified by physical address) with User bit set.
static void MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
}; };
extern Paging* g_paging; extern Paging* g_paging;
+3 -1
View File
@@ -7,6 +7,7 @@
#include "Arp.hpp" #include "Arp.hpp"
#include <Net/ByteOrder.hpp> #include <Net/ByteOrder.hpp>
#include <Net/Ethernet.hpp> #include <Net/Ethernet.hpp>
#include <Net/Ipv4.hpp>
#include <Net/NetConfig.hpp> #include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp> #include <Drivers/Net/E1000.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
@@ -91,8 +92,9 @@ namespace Net::Arp {
uint32_t senderIp = pkt->SenderIp; // Already in network byte order in struct uint32_t senderIp = pkt->SenderIp; // Already in network byte order in struct
uint32_t targetIp = pkt->TargetIp; uint32_t targetIp = pkt->TargetIp;
// Cache the sender's IP->MAC mapping // Cache the sender's IP->MAC mapping, then flush any packets waiting on it
CacheInsert(senderIp, pkt->SenderMac); CacheInsert(senderIp, pkt->SenderMac);
Ipv4::FlushPending();
uint16_t op = Ntohs(pkt->Operation); uint16_t op = Ntohs(pkt->Operation);
+34
View File
@@ -15,10 +15,40 @@ using namespace Kt;
namespace Net::Icmp { namespace Net::Icmp {
// Reply tracking for outgoing pings
static volatile bool g_replyReceived = false;
static volatile uint16_t g_replyId = 0;
static volatile uint16_t g_replySeq = 0;
void Initialize() { void Initialize() {
KernelLogStream(OK, "Net") << "ICMP initialized"; KernelLogStream(OK, "Net") << "ICMP initialized";
} }
void ResetReply() {
g_replyReceived = false;
}
bool HasReply(uint16_t identifier, uint16_t sequence) {
return g_replyReceived
&& g_replyId == identifier
&& g_replySeq == sequence;
}
void SendEchoRequest(uint32_t destIp, uint16_t identifier, uint16_t sequence) {
uint8_t packet[sizeof(Header)];
Header* hdr = (Header*)packet;
hdr->Type = TYPE_ECHO_REQUEST;
hdr->Code = 0;
hdr->Checksum = 0;
hdr->Identifier = Htons(identifier);
hdr->Sequence = Htons(sequence);
hdr->Checksum = Ipv4::Checksum(packet, sizeof(Header));
Ipv4::Send(destIp, Ipv4::PROTO_ICMP, packet, sizeof(Header));
}
void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length) { void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length) {
if (length < sizeof(Header)) { if (length < sizeof(Header)) {
return; return;
@@ -54,6 +84,10 @@ namespace Net::Icmp {
replyHdr->Checksum = Ipv4::Checksum(reply, length); replyHdr->Checksum = Ipv4::Checksum(reply, length);
Ipv4::Send(srcIp, Ipv4::PROTO_ICMP, reply, length); Ipv4::Send(srcIp, Ipv4::PROTO_ICMP, reply, length);
} else if (hdr->Type == TYPE_ECHO_REPLY && hdr->Code == 0) {
g_replyId = Ntohs(hdr->Identifier);
g_replySeq = Ntohs(hdr->Sequence);
g_replyReceived = true;
} }
} }
+9
View File
@@ -26,4 +26,13 @@ namespace Net::Icmp {
// Handle an incoming ICMP packet (called by IPv4 layer) // Handle an incoming ICMP packet (called by IPv4 layer)
void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length); void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length);
// Send an ICMP echo request to the given IP address
void SendEchoRequest(uint32_t destIp, uint16_t identifier, uint16_t sequence);
// Check if a reply was received for the given identifier/sequence
bool HasReply(uint16_t identifier, uint16_t sequence);
// Reset the reply tracker (call before sending a new ping)
void ResetReply();
} }
+61 -27
View File
@@ -15,7 +15,6 @@
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
using namespace Kt; using namespace Kt;
@@ -23,6 +22,18 @@ namespace Net::Ipv4 {
static uint16_t g_identification = 0; static uint16_t g_identification = 0;
// Deferred packet queue for packets awaiting ARP resolution
struct PendingPacket {
uint32_t DestIp;
uint8_t Protocol;
uint8_t Data[Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE];
uint16_t Length;
bool Active;
};
static constexpr uint32_t PENDING_QUEUE_SIZE = 8;
static PendingPacket g_pendingQueue[PENDING_QUEUE_SIZE] = {};
void Initialize() { void Initialize() {
g_identification = 0; g_identification = 0;
KernelLogStream(OK, "Net") << "IPv4 initialized, IP: " KernelLogStream(OK, "Net") << "IPv4 initialized, IP: "
@@ -138,30 +149,9 @@ namespace Net::Ipv4 {
} }
} }
bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen) { // Build and send an IP packet over Ethernet (MAC already resolved)
if (payloadLen > (Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE)) { static bool SendDirect(uint32_t destIp, uint8_t protocol, const uint8_t* destMac,
return false; const uint8_t* payload, uint16_t payloadLen) {
}
// Determine next-hop IP and resolve MAC
uint32_t nextHop = GetNextHop(destIp);
uint8_t destMac[6];
if (!Arp::Resolve(nextHop, destMac)) {
// ARP request sent, wait briefly and retry
for (int attempt = 0; attempt < 3; attempt++) {
Timekeeping::Sleep(50);
if (Arp::Resolve(nextHop, destMac)) {
break;
}
}
// Final check
if (!Arp::Resolve(nextHop, destMac)) {
return false;
}
}
// Build IP packet
uint8_t packet[Ethernet::MAX_PAYLOAD_SIZE]; uint8_t packet[Ethernet::MAX_PAYLOAD_SIZE];
Header* hdr = (Header*)packet; Header* hdr = (Header*)packet;
@@ -176,13 +166,57 @@ namespace Net::Ipv4 {
hdr->SrcIp = GetIpAddress(); hdr->SrcIp = GetIpAddress();
hdr->DstIp = destIp; hdr->DstIp = destIp;
// Calculate header checksum
hdr->Checksum = Checksum(hdr, HEADER_SIZE); hdr->Checksum = Checksum(hdr, HEADER_SIZE);
// Copy payload
memcpy(packet + HEADER_SIZE, payload, payloadLen); memcpy(packet + HEADER_SIZE, payload, payloadLen);
return Ethernet::Send(destMac, Ethernet::ETHERTYPE_IPV4, packet, HEADER_SIZE + payloadLen); return Ethernet::Send(destMac, Ethernet::ETHERTYPE_IPV4, packet, HEADER_SIZE + payloadLen);
} }
bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen) {
if (payloadLen > (Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE)) {
return false;
}
// Determine next-hop IP and resolve MAC
uint32_t nextHop = GetNextHop(destIp);
uint8_t destMac[6];
if (Arp::Resolve(nextHop, destMac)) {
return SendDirect(destIp, protocol, destMac, payload, payloadLen);
}
// ARP request already sent by Resolve(), queue the packet for later
for (uint32_t i = 0; i < PENDING_QUEUE_SIZE; i++) {
if (!g_pendingQueue[i].Active) {
g_pendingQueue[i].DestIp = destIp;
g_pendingQueue[i].Protocol = protocol;
g_pendingQueue[i].Length = payloadLen;
memcpy(g_pendingQueue[i].Data, payload, payloadLen);
g_pendingQueue[i].Active = true;
return true;
}
}
// Queue full, drop the packet
return false;
}
void FlushPending() {
for (uint32_t i = 0; i < PENDING_QUEUE_SIZE; i++) {
if (!g_pendingQueue[i].Active) {
continue;
}
uint32_t nextHop = GetNextHop(g_pendingQueue[i].DestIp);
uint8_t destMac[6];
if (Arp::Resolve(nextHop, destMac)) {
SendDirect(g_pendingQueue[i].DestIp, g_pendingQueue[i].Protocol,
destMac, g_pendingQueue[i].Data, g_pendingQueue[i].Length);
g_pendingQueue[i].Active = false;
}
}
}
} }
+6 -1
View File
@@ -36,9 +36,14 @@ namespace Net::Ipv4 {
// Handle an incoming IP packet (called by Ethernet layer) // Handle an incoming IP packet (called by Ethernet layer)
void OnPacketReceived(const uint8_t* data, uint16_t length); void OnPacketReceived(const uint8_t* data, uint16_t length);
// Send an IP packet with the given protocol and payload // Send an IP packet with the given protocol and payload.
// If ARP resolution is pending, the packet is queued and sent when the reply arrives.
bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen); bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen);
// Flush any packets that were waiting for ARP resolution.
// Called by the ARP layer when a new cache entry is inserted.
void FlushPending();
// Compute the Internet checksum over a buffer // Compute the Internet checksum over a buffer
uint16_t Checksum(const void* data, uint16_t length); uint16_t Checksum(const void* data, uint16_t length);
+9
View File
@@ -59,6 +59,15 @@ namespace {
.response = nullptr .response = nullptr
}; };
__attribute__((used, section(".limine_requests")))
volatile limine_module_request module_request = {
.id = LIMINE_MODULE_REQUEST,
.revision = 1,
.response = nullptr,
.internal_module_count = 0,
.internal_modules = nullptr
};
} }
// Finally, define the start and end markers for the Limine requests. // Finally, define the start and end markers for the Limine requests.
+45
View File
@@ -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
+154
View File
@@ -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;
}
}
+49
View File
@@ -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);
}
+308
View File
@@ -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");
}
}
}
+59
View File
@@ -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();
}
+7 -1
View File
@@ -60,11 +60,17 @@ namespace Kt {
} }
void Putchar(char c) { void Putchar(char c) {
if (c == '\n') {
flanterm_write(ctx, "\r\n", 2);
return;
}
flanterm_write(ctx, &c, 1); flanterm_write(ctx, &c, 1);
} }
void Print(const char *text) { void Print(const char *text) {
flanterm_write(ctx, text, Lib::strlen(text)); for (size_t i = 0; text[i] != '\0'; i++) {
Putchar(text[i]);
}
} }
}; };
+12 -1
View File
@@ -10,6 +10,7 @@
#include <Io/IoPort.hpp> #include <Io/IoPort.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Sched/Scheduler.hpp>
using namespace Kt; using namespace Kt;
@@ -34,9 +35,15 @@ namespace Timekeeping {
static volatile uint64_t g_tickCount = 0; static volatile uint64_t g_tickCount = 0;
static uint32_t g_ticksPerMs = 0; static uint32_t g_ticksPerMs = 0;
// Timer IRQ handler: increment tick count static bool g_schedEnabled = false;
// Timer IRQ handler: increment tick count and drive scheduler
static void TimerHandler(uint8_t) { static void TimerHandler(uint8_t) {
g_tickCount = g_tickCount + 1; g_tickCount = g_tickCount + 1;
if (g_schedEnabled) {
Sched::Tick();
}
} }
// Use PIT channel 2 to create a precise delay for calibration. // Use PIT channel 2 to create a precise delay for calibration.
@@ -128,6 +135,10 @@ namespace Timekeeping {
return g_tickCount; // 1 tick = 1 ms at 1000 Hz return g_tickCount; // 1 tick = 1 ms at 1000 Hz
} }
void EnableSchedulerTick() {
g_schedEnabled = true;
}
void Sleep(uint64_t ms) { void Sleep(uint64_t ms) {
uint64_t target = g_tickCount + ms; uint64_t target = g_tickCount + ms;
while (g_tickCount < target) { while (g_tickCount < target) {
+3
View File
@@ -17,6 +17,9 @@ namespace Timekeeping {
// Get elapsed milliseconds since timer initialization // Get elapsed milliseconds since timer initialization
uint64_t GetMilliseconds(); uint64_t GetMilliseconds();
// Enable scheduler tick (called after scheduler is initialized)
void EnableSchedulerTick();
// Busy-wait sleep for the given number of milliseconds // Busy-wait sleep for the given number of milliseconds
void Sleep(uint64_t ms); void Sleep(uint64_t ms);
}; };
+4
View File
@@ -8,3 +8,7 @@ timeout: 0
# Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located.
path: boot():/boot/kernel path: boot():/boot/kernel
# Ramdisk module (USTAR tar archive)
module_path: boot():/boot/ramdisk.tar
module_string: ramdisk
+74
View File
@@ -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
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+51
View File
@@ -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;
};
}
+157
View File
@@ -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);
}
}
+43
View File
@@ -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.
+18
View File
@@ -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");
// }
}
+298
View File
@@ -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
View File
Binary file not shown.
View File
+20
View File
@@ -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)"
+7
View File
@@ -66,5 +66,12 @@ if command -v nmcli &>/dev/null; then
nmcli device set "$TAP" managed no 2>/dev/null || true nmcli device set "$TAP" managed no 2>/dev/null || true
fi fi
# Configure DNS on the bridge so systemd-resolved keeps working
if command -v resolvectl &>/dev/null && [ -n "$GW" ]; then
resolvectl dns "$BRIDGE" "$GW" 2>/dev/null || true
resolvectl domain "$BRIDGE" '~.' 2>/dev/null || true
echo "Configured DNS on $BRIDGE via $GW"
fi
echo "Network bridge setup complete: $PHYS -> $BRIDGE <- $TAP" echo "Network bridge setup complete: $PHYS -> $BRIDGE <- $TAP"
ip -4 addr show dev "$BRIDGE" | head -3 ip -4 addr show dev "$BRIDGE" | head -3