diff --git a/GNUmakefile b/GNUmakefile index 9f71b6c..b5d715b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -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: diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp new file mode 100644 index 0000000..a6522b6 --- /dev/null +++ b/kernel/src/Api/Syscall.cpp @@ -0,0 +1,280 @@ +/* + * Syscall.cpp + * SYSCALL/SYSRET setup and number-based dispatch + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Syscall.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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)"; + } + +} diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp new file mode 100644 index 0000000..59c4c96 --- /dev/null +++ b/kernel/src/Api/Syscall.hpp @@ -0,0 +1,62 @@ +/* + * Syscall.hpp + * ZenithOS syscall definitions -- shared between kernel and programs + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +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(); + +} diff --git a/kernel/src/Drivers/PS2/PS2Controller.cpp b/kernel/src/Drivers/PS2/PS2Controller.cpp index 4ab014a..34ef415 100644 --- a/kernel/src/Drivers/PS2/PS2Controller.cpp +++ b/kernel/src/Drivers/PS2/PS2Controller.cpp @@ -153,7 +153,7 @@ namespace Drivers::PS2 { SendCommand(CmdReadConfig); config = ReadData(); - config |= ConfigPort1Interrupt; + config |= ConfigPort1Interrupt | ConfigPort1Translation; if (g_DualChannel) { config |= ConfigPort2Interrupt; } diff --git a/kernel/src/Fs/Ramdisk.cpp b/kernel/src/Fs/Ramdisk.cpp new file mode 100644 index 0000000..bcff151 --- /dev/null +++ b/kernel/src/Fs/Ramdisk.cpp @@ -0,0 +1,242 @@ +/* + * Ramdisk.cpp + * USTAR tar-based ramdisk filesystem backed by Limine modules + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Ramdisk.hpp" +#include +#include +#include + +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; + } + +} diff --git a/kernel/src/Fs/Ramdisk.hpp b/kernel/src/Fs/Ramdisk.hpp new file mode 100644 index 0000000..d68de54 --- /dev/null +++ b/kernel/src/Fs/Ramdisk.hpp @@ -0,0 +1,33 @@ +/* + * Ramdisk.hpp + * USTAR tar-based ramdisk filesystem backed by Limine modules + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +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(); + +} diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp new file mode 100644 index 0000000..7fd0f07 --- /dev/null +++ b/kernel/src/Fs/Vfs.cpp @@ -0,0 +1,143 @@ +/* + * Vfs.cpp + * Virtual File System with numerical logical drive identifiers + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Vfs.hpp" +#include + +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); + } + +} diff --git a/kernel/src/Fs/Vfs.hpp b/kernel/src/Fs/Vfs.hpp new file mode 100644 index 0000000..2c6b88b --- /dev/null +++ b/kernel/src/Fs/Vfs.hpp @@ -0,0 +1,33 @@ +/* + * Vfs.hpp + * Virtual File System with numerical logical drive identifiers + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +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); + +} diff --git a/kernel/src/Hal/GDT.asm b/kernel/src/Hal/GDT.asm index 777ba62..f773b88 100644 --- a/kernel/src/Hal/GDT.asm +++ b/kernel/src/Hal/GDT.asm @@ -8,6 +8,7 @@ section .text ; Text/code section global ReloadSegments global LoadGDT +global LoadTR LoadGDT: lgdt [rdi] ; Run LGDT on the contents of 1st C parameter @@ -28,4 +29,9 @@ ReloadSegments: mov gs, ax mov ss, ax - ret \ No newline at end of file + ret + +LoadTR: + mov ax, 0x28 ; TSS selector + ltr ax + ret diff --git a/kernel/src/Hal/GDT.cpp b/kernel/src/Hal/GDT.cpp index 907bfbb..326ed51 100644 --- a/kernel/src/Hal/GDT.cpp +++ b/kernel/src/Hal/GDT.cpp @@ -1,43 +1,76 @@ /* - * gdt.hpp + * gdt.cpp * Intel Global Descriptor Table * Copyright (c) 2025 Daniel Hammer */ #include "GDT.hpp" #include "../Terminal/Terminal.hpp" +#include namespace Hal { using namespace Kt; GDTPointer gdtPointer{}; BasicGDT kernelGDT{}; - - void PrepareGDT() { - kernelGDT = { - {0xFFFF, 0, 0, 0x00, 0x00, 0}, - {0xFFFF, 0, 0, 0x9A, 0xA0, 0}, - {0xFFFF, 0, 0, 0x92, 0xA0, 0}, - {0xFFFF, 0, 0, 0x9A, 0xA0, 0}, - {0xFFFF, 0, 0, 0x92, 0xA0, 0}, + TSS64 g_tss{}; - {0, 0, 0, 0xFA, 0x00, 0x0}, + void PrepareGDT() { + // Zero the TSS + memset(&g_tss, 0, sizeof(g_tss)); + g_tss.iopbOffset = sizeof(TSS64); + + kernelGDT = { + {0xFFFF, 0, 0, 0x00, 0x00, 0}, // Null + {0xFFFF, 0, 0, 0x9A, 0xA0, 0}, // KernelCode (DPL=0, code, 64-bit) + {0xFFFF, 0, 0, 0x92, 0xA0, 0}, // KernelData (DPL=0, data) + {0xFFFF, 0, 0, 0xF2, 0xA0, 0}, // UserData (DPL=3, data) + {0xFFFF, 0, 0, 0xFA, 0xA0, 0}, // UserCode (DPL=3, code, 64-bit) + {0, 0, 0, 0, 0, 0}, // TSS low (filled below) + {0, 0, 0, 0, 0, 0}, // TSS high (filled below) }; - + + // Encode 16-byte TSS descriptor + uint64_t base = (uint64_t)&g_tss; + uint32_t limit = sizeof(TSS64) - 1; + + // Low 8 bytes (normal GDT entry format) + kernelGDT.TSS.LimitLow = limit & 0xFFFF; + kernelGDT.TSS.BaseLow = base & 0xFFFF; + kernelGDT.TSS.BaseMiddle = (base >> 16) & 0xFF; + kernelGDT.TSS.AccessByte = 0x89; // Present, 64-bit TSS Available + kernelGDT.TSS.GranularityByte = (limit >> 16) & 0x0F; + kernelGDT.TSS.BaseHigh = (base >> 24) & 0xFF; + + // High 8 bytes (base[63:32] + reserved) + uint32_t baseUpper = (uint32_t)(base >> 32); + kernelGDT.TSSHigh.LimitLow = baseUpper & 0xFFFF; + kernelGDT.TSSHigh.BaseLow = (baseUpper >> 16) & 0xFFFF; + kernelGDT.TSSHigh.BaseMiddle = 0; + kernelGDT.TSSHigh.AccessByte = 0; + kernelGDT.TSSHigh.GranularityByte = 0; + kernelGDT.TSSHigh.BaseHigh = 0; + gdtPointer = GDTPointer{ .Size = sizeof(kernelGDT) - 1, .GDTAddress = (uint64_t)&kernelGDT }; } - + // Helpers implemented in gdt.asm extern "C" void LoadGDT(GDTPointer *gdtPointer); extern "C" void ReloadSegments(); - + extern "C" void LoadTR(); + void BridgeLoadGDT() { LoadGDT(&gdtPointer); ReloadSegments(); KernelLogStream(DEBUG, "Hal") << "Set new GDT (0x" << base::hex << (uint64_t)&kernelGDT << ")"; } -}; \ No newline at end of file + + void LoadTSS() { + LoadTR(); + KernelLogStream(OK, "Hal") << "Loaded TSS (selector 0x28)"; + } +}; diff --git a/kernel/src/Hal/GDT.hpp b/kernel/src/Hal/GDT.hpp index cdbd300..daf1236 100644 --- a/kernel/src/Hal/GDT.hpp +++ b/kernel/src/Hal/GDT.hpp @@ -17,25 +17,45 @@ namespace Hal { uint8_t BaseMiddle; uint8_t AccessByte; uint8_t GranularityByte; - uint8_t BaseHigh; + uint8_t BaseHigh; }__attribute__((packed)); - + struct BasicGDT { - GDTEntry Null; - GDTEntry KernelCode; - GDTEntry KernelData; - GDTEntry UserCode; - GDTEntry UserData; - - // Task State Segment - GDTEntry TSS; + GDTEntry Null; // 0x00 + GDTEntry KernelCode; // 0x08 + GDTEntry KernelData; // 0x10 + GDTEntry UserData; // 0x18 (before UserCode for SYSRET) + GDTEntry UserCode; // 0x20 + GDTEntry TSS; // 0x28 (low 8 bytes of 16-byte TSS descriptor) + GDTEntry TSSHigh; // 0x30 (high 8 bytes of 16-byte TSS descriptor) }__attribute__((packed)); - + struct GDTPointer { uint16_t Size; uint64_t GDTAddress; }__attribute__((packed)); - + + struct TSS64 { + uint32_t reserved0; + uint64_t rsp0; + uint64_t rsp1; + uint64_t rsp2; + uint64_t reserved1; + uint64_t ist1; + uint64_t ist2; + uint64_t ist3; + uint64_t ist4; + uint64_t ist5; + uint64_t ist6; + uint64_t ist7; + uint64_t reserved2; + uint16_t reserved3; + uint16_t iopbOffset; + }__attribute__((packed)); + + extern TSS64 g_tss; + void BridgeLoadGDT(); void PrepareGDT(); -}; \ No newline at end of file + void LoadTSS(); +}; diff --git a/kernel/src/Hal/MSR.hpp b/kernel/src/Hal/MSR.hpp new file mode 100644 index 0000000..6d0ddf6 --- /dev/null +++ b/kernel/src/Hal/MSR.hpp @@ -0,0 +1,30 @@ +/* + * MSR.hpp + * Model-Specific Register read/write helpers + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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; + +} diff --git a/kernel/src/Hal/SyscallEntry.asm b/kernel/src/Hal/SyscallEntry.asm new file mode 100644 index 0000000..d18eaa1 --- /dev/null +++ b/kernel/src/Hal/SyscallEntry.asm @@ -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 diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 0257a0d..e56ac4f 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -37,7 +37,10 @@ #include #include #include - +#include +#include +#include +#include using namespace Kt; namespace Memory { @@ -95,22 +98,20 @@ extern "C" void kmain() { uint64_t hhdm_offset = hhdm_request.response->offset; Memory::HHDMBase = hhdm_offset; - if (memmap_request.response != nullptr) { - Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator"; - - Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response)); - Memory::g_pfa = &pmm; - - Kt::KernelLogStream(OK, "Mem") << "Creating HeapAllocator"; - Memory::HeapAllocator heap{}; - Memory::g_heap = &heap; - - heap.Walk(); - - } else { + if (memmap_request.response == nullptr) { Panic("System memory map missing!", nullptr); } + Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator"; + Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response)); + Memory::g_pfa = &pmm; + + Kt::KernelLogStream(OK, "Mem") << "Creating HeapAllocator"; + Memory::HeapAllocator heap{}; + Memory::g_heap = &heap; + + heap.Walk(); + #if defined (__x86_64__) Hal::IDTInitialize(); @@ -142,8 +143,49 @@ extern "C" void kmain() { Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address); Efi::Init(ST); + // Initialize ramdisk from Limine modules + if (module_request.response != nullptr && module_request.response->module_count > 0) { + Kt::KernelLogStream(OK, "Modules") << "Found " << (uint64_t)module_request.response->module_count << " module(s)"; + for (uint64_t i = 0; i < module_request.response->module_count; i++) { + limine_file* mod = module_request.response->modules[i]; + const char* modString = mod->string; + + // Find "ramdisk" module by its string + if (modString != nullptr && + modString[0] == 'r' && modString[1] == 'a' && modString[2] == 'm' && + modString[3] == 'd' && modString[4] == 'i' && modString[5] == 's' && + modString[6] == 'k' && modString[7] == '\0') { + Kt::KernelLogStream(OK, "Modules") << "Ramdisk module at " << kcp::hex << (uint64_t)mod->address << kcp::dec << ", size=" << mod->size; + Fs::Ramdisk::Initialize(mod->address, mod->size); + } + } + } else { + Kt::KernelLogStream(WARNING, "Modules") << "No modules loaded (ramdisk unavailable)"; + } + + // Initialize VFS and register ramdisk as drive 0 + Fs::Vfs::Initialize(); + + static Fs::Vfs::FsDriver ramdiskDriver = { + Fs::Ramdisk::Open, + Fs::Ramdisk::Read, + Fs::Ramdisk::GetSize, + Fs::Ramdisk::Close, + Fs::Ramdisk::ReadDir + }; + Fs::Vfs::RegisterDrive(0, &ramdiskDriver); + Graphics::Cursor::Initialize(framebuffer); + Hal::LoadTSS(); + Zenith::InitializeSyscalls(); + + Sched::Initialize(); + Sched::Spawn("0:/shell.elf"); + + // Enable preemptive scheduling via the APIC timer + Timekeeping::EnableSchedulerTick(); + // Main loop: update cursor position and halt until next interrupt for (;;) { Graphics::Cursor::Update(); diff --git a/kernel/src/Memory/Heap.cpp b/kernel/src/Memory/Heap.cpp index 64eb03a..3c8345a 100644 --- a/kernel/src/Memory/Heap.cpp +++ b/kernel/src/Memory/Heap.cpp @@ -80,19 +80,19 @@ namespace Memory InsertToFreelist(rest, newBlockSize); } - + Lock.Release(); return block; } prev = current; current = current->next; - - Lock.Release(); } - // First pass allocation failed - size_t pagesNeeded = size / 0x1000; + Lock.Release(); + + // First pass allocation failed -- grow the heap + size_t pagesNeeded = (sizeNeeded + 0xFFF) / 0x1000; InsertPagesToFreelist(pagesNeeded); return Request(size); @@ -102,7 +102,9 @@ namespace Memory auto new_block = Request(size); if (ptr != nullptr && new_block != nullptr) { - memcpy(new_block, ptr, size); + size_t oldSize = GetAllocatedBlockSize(ptr); + size_t copySize = (oldSize < size) ? oldSize : size; + memcpy(new_block, ptr, copySize); Free(ptr); } @@ -123,7 +125,7 @@ namespace Memory auto actualSize = size + sizeof(Header); void* actualBlock = (void*)header; - InsertToFreelist(actualBlock, size); + InsertToFreelist(actualBlock, actualSize); Lock.Release(); } diff --git a/kernel/src/Memory/Paging.cpp b/kernel/src/Memory/Paging.cpp index 5de9d77..635b0ea 100644 --- a/kernel/src/Memory/Paging.cpp +++ b/kernel/src/Memory/Paging.cpp @@ -28,10 +28,10 @@ namespace Memory::VMM { for (size_t i = 0; i < memMap->entry_count; i++) { auto entry = memMap->entries[i]; - + for (size_t pageAddr = entry->base; pageAddr < (entry->base + entry->length); pageAddr += 0x1000) { Map(pageAddr, HHDM(pageAddr)); - } + } } LoadCR3(PML4); @@ -40,21 +40,41 @@ namespace Memory::VMM { PageTable* Paging::HandleLevel(VirtualAddress virtualAddress, PageTable* table, const size_t level) { PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]); - + if (!entry->Present) { entry->Present = true; entry->Writable = true; - + uint64_t downLevelAddr = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed()); - + entry->Address = downLevelAddr >> 12; - + return (PageTable*)downLevelAddr; } else { return (PageTable*)(entry->Address << 12); } } + PageTable* Paging::HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, const size_t level) { + PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]); + + if (!entry->Present) { + entry->Present = true; + entry->Writable = true; + entry->Supervisor = 1; // User-accessible + + uint64_t downLevelAddr = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed()); + + entry->Address = downLevelAddr >> 12; + + return (PageTable*)downLevelAddr; + } else { + // Ensure User bit is set on existing entries in the user path + entry->Supervisor = 1; + return (PageTable*)(entry->Address << 12); + } + } + void Paging::Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress) { if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) { Panic("Value that isn't page-aligned passed as address to Paging::Map!", nullptr); @@ -95,9 +115,79 @@ namespace Memory::VMM { pageEntry->Address = physicalAddress >> 12; } + void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) { + if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) { + Panic("Value that isn't page-aligned passed as address to Paging::MapUser!", nullptr); + } + + VirtualAddress virtualAddressObj(virtualAddress); + + auto PML3 = HandleLevelUser(virtualAddressObj, PML4, 4); + auto PML2 = HandleLevelUser(virtualAddressObj, PML3, 3); + auto PML1 = HandleLevelUser(virtualAddressObj, PML2, 2); + + PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]); + + pageEntry->Present = true; + pageEntry->Writable = true; + pageEntry->Supervisor = 1; // User-accessible + + pageEntry->Address = physicalAddress >> 12; + } + + std::uint64_t Paging::CreateUserPML4() { + // Allocate a new PML4 + void* newPage = Memory::g_pfa->AllocateZeroed(); + uint64_t newPml4Phys = Memory::SubHHDM((uint64_t)newPage); + PageTable* newPml4 = (PageTable*)newPage; // HHDM virtual address + + // Copy kernel-half entries (256-511) from the global PML4 + PageTable* kernelPml4 = (PageTable*)Memory::HHDM((uint64_t)g_paging->PML4); + for (int i = 256; i < 512; i++) { + newPml4->entries[i] = kernelPml4->entries[i]; + } + + return newPml4Phys; + } + + void Paging::MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) { + if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) { + Panic("Non-aligned address in Paging::MapUserIn!", nullptr); + } + + VirtualAddress va(virtualAddress); + + // Walk/create page tables from the given PML4, setting User bit at each level + auto walkLevel = [](PageTable* table, uint64_t index) -> PageTable* { + PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[index]); + if (!entry->Present) { + entry->Present = true; + entry->Writable = true; + entry->Supervisor = 1; // User-accessible + uint64_t newPhys = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed()); + entry->Address = newPhys >> 12; + return (PageTable*)newPhys; + } else { + entry->Supervisor = 1; + return (PageTable*)(entry->Address << 12); + } + }; + + PageTable* pml4 = (PageTable*)pml4Phys; + auto pml3 = walkLevel(pml4, va.GetL4Index()); + auto pml2 = walkLevel(pml3, va.GetL3Index()); + auto pml1 = walkLevel(pml2, va.GetL2Index()); + + PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]); + pageEntry->Present = true; + pageEntry->Writable = true; + pageEntry->Supervisor = 1; + pageEntry->Address = physicalAddress >> 12; + } + std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) { VirtualAddress virtualAddressObj(virtualAddress); - + PageTable* pml4Virt = (PageTable*)HHDM(pml4); PageTableEntry* pml4_entry = &pml4Virt->entries[virtualAddressObj.GetL4Index()]; @@ -122,4 +212,4 @@ namespace Memory::VMM { std::uint64_t Paging::GetPhysAddr(std::uint64_t virtualAddress) { return GetPhysAddr((std::uint64_t)PML4, virtualAddress, false); } -}; \ No newline at end of file +}; diff --git a/kernel/src/Memory/Paging.hpp b/kernel/src/Memory/Paging.hpp index 7b8a3d7..52b68f3 100644 --- a/kernel/src/Memory/Paging.hpp +++ b/kernel/src/Memory/Paging.hpp @@ -35,7 +35,7 @@ namespace Memory::VMM { std::uint8_t NX : 1; }; - + struct PageTable { PageTableEntry entries[512]; } __attribute__((packed)) __attribute__((aligned(0x1000))); @@ -79,24 +79,35 @@ namespace Memory::VMM { else if (level == 1) return GetPageIndex(); + + return 0; } }; class Paging { + PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level); + PageTable* HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, size_t level); +public: PageTable* PML4{}; - PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level); -public: Paging(); void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap); void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress); void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress); + void MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress); static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false); std::uint64_t GetPhysAddr(std::uint64_t virtualAddress); + + // Create a new PML4 with kernel-half (entries 256-511) copied from g_paging. + // Returns the physical address of the new PML4. + static std::uint64_t CreateUserPML4(); + + // Map a page into an arbitrary PML4 (specified by physical address) with User bit set. + static void MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress); }; extern Paging* g_paging; extern "C" uint64_t GetCR3(); extern "C" void LoadCR3(PageTable* PML4); -}; \ No newline at end of file +}; diff --git a/kernel/src/Net/Arp.cpp b/kernel/src/Net/Arp.cpp index 415ce8d..f98e0da 100644 --- a/kernel/src/Net/Arp.cpp +++ b/kernel/src/Net/Arp.cpp @@ -7,6 +7,7 @@ #include "Arp.hpp" #include #include +#include #include #include #include @@ -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); diff --git a/kernel/src/Net/Icmp.cpp b/kernel/src/Net/Icmp.cpp index d5c7a12..737a6dc 100644 --- a/kernel/src/Net/Icmp.cpp +++ b/kernel/src/Net/Icmp.cpp @@ -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; } } diff --git a/kernel/src/Net/Icmp.hpp b/kernel/src/Net/Icmp.hpp index 12d9841..616f2ce 100644 --- a/kernel/src/Net/Icmp.hpp +++ b/kernel/src/Net/Icmp.hpp @@ -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(); + } diff --git a/kernel/src/Net/Ipv4.cpp b/kernel/src/Net/Ipv4.cpp index 07981af..d010a2c 100644 --- a/kernel/src/Net/Ipv4.cpp +++ b/kernel/src/Net/Ipv4.cpp @@ -15,7 +15,6 @@ #include #include #include -#include 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; + } + } + } + } diff --git a/kernel/src/Net/Ipv4.hpp b/kernel/src/Net/Ipv4.hpp index f77fe41..87620ef 100644 --- a/kernel/src/Net/Ipv4.hpp +++ b/kernel/src/Net/Ipv4.hpp @@ -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); diff --git a/kernel/src/Platform/Limine.hpp b/kernel/src/Platform/Limine.hpp index cd0dad4..1497c38 100644 --- a/kernel/src/Platform/Limine.hpp +++ b/kernel/src/Platform/Limine.hpp @@ -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. diff --git a/kernel/src/Sched/Context.asm b/kernel/src/Sched/Context.asm new file mode 100644 index 0000000..56bdfe7 --- /dev/null +++ b/kernel/src/Sched/Context.asm @@ -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 diff --git a/kernel/src/Sched/ElfLoader.cpp b/kernel/src/Sched/ElfLoader.cpp new file mode 100644 index 0000000..97521fb --- /dev/null +++ b/kernel/src/Sched/ElfLoader.cpp @@ -0,0 +1,154 @@ +/* + * ElfLoader.cpp + * ELF64 binary loader for user-mode processes + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "ElfLoader.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +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; + } + +} diff --git a/kernel/src/Sched/ElfLoader.hpp b/kernel/src/Sched/ElfLoader.hpp new file mode 100644 index 0000000..9d4ff96 --- /dev/null +++ b/kernel/src/Sched/ElfLoader.hpp @@ -0,0 +1,49 @@ +/* + * ElfLoader.hpp + * ELF64 binary loader for user-mode processes + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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); + +} diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp new file mode 100644 index 0000000..7aa0e93 --- /dev/null +++ b/kernel/src/Sched/Scheduler.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +// 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"); + } + } + +} diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp new file mode 100644 index 0000000..05cabbd --- /dev/null +++ b/kernel/src/Sched/Scheduler.hpp @@ -0,0 +1,59 @@ +/* + * Scheduler.hpp + * Preemptive process scheduler with user-mode support + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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(); + +} diff --git a/kernel/src/Terminal/Terminal.cpp b/kernel/src/Terminal/Terminal.cpp index 68f1eb7..959ce25 100644 --- a/kernel/src/Terminal/Terminal.cpp +++ b/kernel/src/Terminal/Terminal.cpp @@ -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]); + } } }; \ No newline at end of file diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index 9d39161..8883680 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using namespace Kt; @@ -34,9 +35,15 @@ namespace Timekeeping { static volatile uint64_t g_tickCount = 0; static uint32_t g_ticksPerMs = 0; - // Timer IRQ handler: increment tick count + static bool g_schedEnabled = false; + + // Timer IRQ handler: increment tick count and drive scheduler static void TimerHandler(uint8_t) { g_tickCount = g_tickCount + 1; + + if (g_schedEnabled) { + Sched::Tick(); + } } // Use PIT channel 2 to create a precise delay for calibration. @@ -128,10 +135,14 @@ namespace Timekeeping { return g_tickCount; // 1 tick = 1 ms at 1000 Hz } + void EnableSchedulerTick() { + g_schedEnabled = true; + } + void Sleep(uint64_t ms) { uint64_t target = g_tickCount + ms; while (g_tickCount < target) { asm volatile("hlt"); } } -}; \ No newline at end of file +}; diff --git a/kernel/src/Timekeeping/ApicTimer.hpp b/kernel/src/Timekeeping/ApicTimer.hpp index d73a2b6..94242fd 100644 --- a/kernel/src/Timekeeping/ApicTimer.hpp +++ b/kernel/src/Timekeeping/ApicTimer.hpp @@ -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); -}; \ No newline at end of file +}; diff --git a/limine.conf b/limine.conf index b57f6f9..2086f9c 100644 --- a/limine.conf +++ b/limine.conf @@ -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 diff --git a/programs/GNUmakefile b/programs/GNUmakefile new file mode 100644 index 0000000..f9edb1c --- /dev/null +++ b/programs/GNUmakefile @@ -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 diff --git a/programs/bin/hello.elf b/programs/bin/hello.elf new file mode 100755 index 0000000..844af02 Binary files /dev/null and b/programs/bin/hello.elf differ diff --git a/programs/bin/shell.elf b/programs/bin/shell.elf new file mode 100755 index 0000000..6a5f982 Binary files /dev/null and b/programs/bin/shell.elf differ diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp new file mode 100644 index 0000000..a66e7a9 --- /dev/null +++ b/programs/include/Api/Syscall.hpp @@ -0,0 +1,51 @@ +/* + * Syscall.hpp + * ZenithOS syscall definitions for userspace programs + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +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; + }; + +} diff --git a/programs/include/zenith/syscall.h b/programs/include/zenith/syscall.h new file mode 100644 index 0000000..1ba0705 --- /dev/null +++ b/programs/include/zenith/syscall.h @@ -0,0 +1,157 @@ +/* + * syscall.h + * ZenithOS program-side syscall wrappers using SYSCALL instruction + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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); + } + +} diff --git a/programs/link.ld b/programs/link.ld new file mode 100644 index 0000000..8b4af41 --- /dev/null +++ b/programs/link.ld @@ -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*) + } +} diff --git a/programs/obj/hello/main.o b/programs/obj/hello/main.o new file mode 100644 index 0000000..781ee69 Binary files /dev/null and b/programs/obj/hello/main.o differ diff --git a/programs/obj/shell/main.o b/programs/obj/shell/main.o new file mode 100644 index 0000000..86b80f6 Binary files /dev/null and b/programs/obj/shell/main.o differ diff --git a/programs/src/hello/main.cpp b/programs/src/hello/main.cpp new file mode 100644 index 0000000..3cb35d2 --- /dev/null +++ b/programs/src/hello/main.cpp @@ -0,0 +1,18 @@ +/* + * main.cpp + * Hello world program for ZenithOS + * Copyright (c) 2025 Daniel Hammer +*/ + +#include + +extern "C" void _start() { + + + zenith::print("Hello from userspace!\n"); + + // while(true) { + // zenith::print("ab"); + // } + +} diff --git a/programs/src/shell/main.cpp b/programs/src/shell/main.cpp new file mode 100644 index 0000000..bf5079e --- /dev/null +++ b/programs/src/shell/main.cpp @@ -0,0 +1,298 @@ +/* + * main.cpp + * Interactive shell for ZenithOS + * Copyright (c) 2025 Daniel Hammer +*/ + +#include + +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 Display file contents\n"); + zenith::print(" ping 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 \n"); + return; + } + + // Build path "0:/" + 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 \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); + } + } +} diff --git a/ramdisk.tar b/ramdisk.tar new file mode 100644 index 0000000..c7658e0 Binary files /dev/null and b/ramdisk.tar differ diff --git a/ramdisk/test.txt b/ramdisk/test.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/mkramdisk.sh b/scripts/mkramdisk.sh new file mode 100755 index 0000000..681cece --- /dev/null +++ b/scripts/mkramdisk.sh @@ -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)" diff --git a/scripts/net-setup.sh b/scripts/net-setup.sh index 6993132..bf3fd92 100755 --- a/scripts/net-setup.sh +++ b/scripts/net-setup.sh @@ -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