feat: expand user mode, add DOOM game, add manpages
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
#include <Net/ByteOrder.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
#include "../Libraries/flanterm/src/flanterm.h"
|
||||
|
||||
// Assembly entry point
|
||||
extern "C" void SyscallEntry();
|
||||
@@ -175,6 +177,47 @@ namespace Zenith {
|
||||
static uint16_t g_pingSeq = 0;
|
||||
static constexpr uint16_t PING_ID = 0x2E01; // "ZE"
|
||||
|
||||
static void Sys_FbInfo(FbInfo* out) {
|
||||
if (out == nullptr) return;
|
||||
out->width = Graphics::Cursor::GetFramebufferWidth();
|
||||
out->height = Graphics::Cursor::GetFramebufferHeight();
|
||||
out->pitch = Graphics::Cursor::GetFramebufferPitch();
|
||||
out->bpp = 32;
|
||||
out->userAddr = 0;
|
||||
}
|
||||
|
||||
static void Sys_WaitPid(int pid) {
|
||||
while (Sched::IsAlive(pid)) {
|
||||
Sched::Schedule(); // yield until the process exits
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t Sys_FbMap() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return 0;
|
||||
|
||||
uint32_t* fbBase = Graphics::Cursor::GetFramebufferBase();
|
||||
if (fbBase == nullptr) return 0;
|
||||
|
||||
uint64_t fbPhys = Memory::SubHHDM((uint64_t)fbBase);
|
||||
uint64_t fbSize = Graphics::Cursor::GetFramebufferHeight()
|
||||
* Graphics::Cursor::GetFramebufferPitch();
|
||||
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
|
||||
|
||||
// Map at a fixed user VA
|
||||
constexpr uint64_t userVa = 0x50000000ULL;
|
||||
|
||||
for (uint64_t i = 0; i < numPages; i++) {
|
||||
Memory::VMM::Paging::MapUserIn(
|
||||
proc->pml4Phys,
|
||||
fbPhys + i * 0x1000,
|
||||
userVa + i * 0x1000
|
||||
);
|
||||
}
|
||||
|
||||
return userVa;
|
||||
}
|
||||
|
||||
static int32_t Sys_Ping(uint32_t ipAddr, uint32_t timeoutMs) {
|
||||
uint16_t seq = g_pingSeq++;
|
||||
|
||||
@@ -192,6 +235,27 @@ namespace Zenith {
|
||||
return (int32_t)(Timekeeping::GetMilliseconds() - start);
|
||||
}
|
||||
|
||||
static int Sys_Spawn(const char* path, const char* args) {
|
||||
return Sched::Spawn(path, args);
|
||||
}
|
||||
|
||||
static int Sys_GetArgs(char* buf, uint64_t maxLen) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
||||
int i = 0;
|
||||
for (; i < (int)maxLen - 1 && proc->args[i]; i++) {
|
||||
buf[i] = proc->args[i];
|
||||
}
|
||||
buf[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
||||
static uint64_t Sys_TermSize() {
|
||||
size_t cols = 0, rows = 0;
|
||||
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
|
||||
return (rows << 32) | (cols & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
// ---- Dispatch ----
|
||||
|
||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||
@@ -248,6 +312,20 @@ namespace Zenith {
|
||||
return (int64_t)Sys_GetChar();
|
||||
case SYS_PING:
|
||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
||||
case SYS_SPAWN:
|
||||
return (int64_t)Sys_Spawn((const char*)frame->arg1, (const char*)frame->arg2);
|
||||
case SYS_WAITPID:
|
||||
Sys_WaitPid((int)frame->arg1);
|
||||
return 0;
|
||||
case SYS_FBINFO:
|
||||
Sys_FbInfo((FbInfo*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_FBMAP:
|
||||
return (int64_t)Sys_FbMap();
|
||||
case SYS_TERMSIZE:
|
||||
return (int64_t)Sys_TermSize();
|
||||
case SYS_GETARGS:
|
||||
return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -274,7 +352,7 @@ namespace Zenith {
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 20 syscalls)";
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 26 syscalls)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,20 @@ namespace Zenith {
|
||||
static constexpr uint64_t SYS_GETKEY = 17;
|
||||
static constexpr uint64_t SYS_GETCHAR = 18;
|
||||
static constexpr uint64_t SYS_PING = 19;
|
||||
static constexpr uint64_t SYS_SPAWN = 20;
|
||||
static constexpr uint64_t SYS_FBINFO = 21;
|
||||
static constexpr uint64_t SYS_FBMAP = 22;
|
||||
static constexpr uint64_t SYS_WAITPID = 23;
|
||||
static constexpr uint64_t SYS_TERMSIZE = 24;
|
||||
static constexpr uint64_t SYS_GETARGS = 25;
|
||||
|
||||
struct FbInfo {
|
||||
uint64_t width;
|
||||
uint64_t height;
|
||||
uint64_t pitch; // bytes per scanline
|
||||
uint64_t bpp; // bits per pixel (32)
|
||||
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
||||
};
|
||||
|
||||
struct SysInfo {
|
||||
char osName[32];
|
||||
|
||||
@@ -176,39 +176,66 @@ namespace Drivers::PS2::Keyboard {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle modifier keys
|
||||
// Handle modifier keys (update state, but still push event to buffer)
|
||||
bool isModifier = false;
|
||||
switch (keycode) {
|
||||
case ScLeftShift:
|
||||
g_Modifiers.LeftShift = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScRightShift:
|
||||
g_Modifiers.RightShift = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScLeftCtrl:
|
||||
g_Modifiers.LeftCtrl = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScLeftAlt:
|
||||
g_Modifiers.LeftAlt = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScCapsLock:
|
||||
if (!released) {
|
||||
g_Modifiers.CapsLock = !g_Modifiers.CapsLock;
|
||||
}
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScNumLock:
|
||||
if (!released) {
|
||||
g_Modifiers.NumLock = !g_Modifiers.NumLock;
|
||||
}
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScScrollLock:
|
||||
if (!released) {
|
||||
g_Modifiers.ScrollLock = !g_Modifiers.ScrollLock;
|
||||
}
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Modifiers still need events in the buffer (for apps like doom)
|
||||
// but lock keys (caps/num/scroll) don't need buffer events
|
||||
if (isModifier && keycode != ScCapsLock && keycode != ScNumLock && keycode != ScScrollLock) {
|
||||
KeyEvent event = {
|
||||
.Scancode = scancode,
|
||||
.Ascii = 0,
|
||||
.Pressed = !released,
|
||||
.Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift,
|
||||
.Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl,
|
||||
.Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt,
|
||||
.CapsLock = g_Modifiers.CapsLock
|
||||
};
|
||||
g_BufferLock.Acquire();
|
||||
BufferPush(event);
|
||||
g_BufferLock.Release();
|
||||
return;
|
||||
}
|
||||
if (isModifier) return;
|
||||
|
||||
// Translate scancode to ASCII
|
||||
char ascii = 0;
|
||||
if (keycode < 128) {
|
||||
|
||||
@@ -162,4 +162,9 @@ namespace Graphics::Cursor {
|
||||
g_OldY = newY;
|
||||
}
|
||||
|
||||
uint32_t* GetFramebufferBase() { return g_FbBase; }
|
||||
uint64_t GetFramebufferWidth() { return g_FbWidth; }
|
||||
uint64_t GetFramebufferHeight() { return g_FbHeight; }
|
||||
uint64_t GetFramebufferPitch() { return g_FbPitch; }
|
||||
|
||||
};
|
||||
|
||||
@@ -13,4 +13,9 @@ namespace Graphics::Cursor {
|
||||
void Initialize(limine_framebuffer* framebuffer);
|
||||
void Update();
|
||||
|
||||
uint32_t* GetFramebufferBase();
|
||||
uint64_t GetFramebufferWidth();
|
||||
uint64_t GetFramebufferHeight();
|
||||
uint64_t GetFramebufferPitch();
|
||||
|
||||
};
|
||||
|
||||
@@ -93,6 +93,23 @@ extern "C" void kmain() {
|
||||
#if defined (__x86_64__)
|
||||
Hal::PrepareGDT();
|
||||
Hal::BridgeLoadGDT();
|
||||
|
||||
// Enable SSE/SSE2 — required for userspace programs compiled with SSE
|
||||
// CR0: clear EM (bit 2), set MP (bit 1)
|
||||
{
|
||||
uint64_t cr0;
|
||||
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||
cr0 &= ~(1ULL << 2); // Clear EM
|
||||
cr0 |= (1ULL << 1); // Set MP
|
||||
asm volatile("mov %0, %%cr0" :: "r"(cr0));
|
||||
|
||||
// CR4: set OSFXSR (bit 9) and OSXMMEXCPT (bit 10)
|
||||
uint64_t cr4;
|
||||
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
cr4 |= (1ULL << 9); // OSFXSR
|
||||
cr4 |= (1ULL << 10); // OSXMMEXCPT
|
||||
asm volatile("mov %0, %%cr4" :: "r"(cr4));
|
||||
}
|
||||
#endif
|
||||
|
||||
uint64_t hhdm_offset = hhdm_request.response->offset;
|
||||
|
||||
@@ -77,12 +77,17 @@ namespace Memory {
|
||||
};
|
||||
}
|
||||
|
||||
// Allocate() returns pages from the top of a free region in descending
|
||||
// order, so 'first' is the highest address. The contiguous block
|
||||
// actually starts (n-1) pages below 'first'.
|
||||
void* base = (void*)((uint64_t)first - (uint64_t)(n - 1) * 0x1000);
|
||||
|
||||
if (ptr != nullptr) {
|
||||
memcpy(first, ptr, n);
|
||||
memcpy(base, ptr, (uint64_t)n * 0x1000);
|
||||
Free(ptr);
|
||||
}
|
||||
|
||||
return first;
|
||||
return base;
|
||||
}
|
||||
|
||||
void PageFrameAllocator::Free(void* ptr) {
|
||||
|
||||
@@ -52,8 +52,6 @@ namespace Sched {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -78,6 +76,10 @@ namespace Sched {
|
||||
Fs::Vfs::VfsRead(handle, fileData, 0, fileSize);
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
|
||||
// Prevent the optimizer from reordering the VfsRead store past the
|
||||
// header validation reads that follow.
|
||||
asm volatile("" ::: "memory");
|
||||
|
||||
// Validate ELF header
|
||||
Elf64Header* hdr = (Elf64Header*)fileData;
|
||||
if (!ValidateElfHeader(hdr)) {
|
||||
@@ -85,9 +87,6 @@ namespace Sched {
|
||||
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);
|
||||
@@ -100,9 +99,6 @@ namespace Sched {
|
||||
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;
|
||||
@@ -147,7 +143,6 @@ namespace Sched {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace Sched {
|
||||
processTable[i].kernelStackTop = 0;
|
||||
processTable[i].userStackTop = 0;
|
||||
processTable[i].heapNext = 0;
|
||||
processTable[i].args[0] = '\0';
|
||||
}
|
||||
|
||||
currentPid = -1;
|
||||
@@ -85,7 +86,7 @@ namespace Sched {
|
||||
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
||||
}
|
||||
|
||||
void Spawn(const char* vfsPath) {
|
||||
int Spawn(const char* vfsPath, const char* args) {
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Free) {
|
||||
@@ -96,7 +97,7 @@ namespace Sched {
|
||||
|
||||
if (slot < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "No free process slots";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create per-process PML4 with kernel-half copied
|
||||
@@ -106,20 +107,20 @@ namespace Sched {
|
||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||
if (entry == 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t* kernelStackBase = (uint8_t*)stackMem;
|
||||
@@ -132,7 +133,7 @@ namespace Sched {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for user stack";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, userStackBase + i * 0x1000);
|
||||
@@ -145,7 +146,7 @@ namespace Sched {
|
||||
void* stubPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (stubPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for exit stub";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
uint64_t stubPhys = Memory::SubHHDM((uint64_t)stubPage);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, stubPhys, ExitStubAddr);
|
||||
@@ -189,11 +190,17 @@ namespace Sched {
|
||||
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;
|
||||
// Copy arguments string into process
|
||||
proc.args[0] = '\0';
|
||||
if (args != nullptr) {
|
||||
int i = 0;
|
||||
for (; i < 255 && args[i]; i++) {
|
||||
proc.args[i] = args[i];
|
||||
}
|
||||
proc.args[i] = '\0';
|
||||
}
|
||||
|
||||
return proc.pid;
|
||||
}
|
||||
|
||||
void Schedule() {
|
||||
@@ -271,8 +278,6 @@ namespace Sched {
|
||||
return;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Process " << (uint64_t)processTable[currentPid].pid << " terminated";
|
||||
|
||||
processTable[currentPid].state = ProcessState::Terminated;
|
||||
|
||||
int next = -1;
|
||||
@@ -305,4 +310,10 @@ namespace Sched {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAlive(int pid) {
|
||||
if (pid < 0 || pid >= MaxProcesses) return false;
|
||||
return processTable[pid].state == ProcessState::Ready
|
||||
|| processTable[pid].state == ProcessState::Running;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,10 +38,11 @@ namespace Sched {
|
||||
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
|
||||
char args[256]; // Command-line arguments (set by parent via Spawn)
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
void Spawn(const char* vfsPath);
|
||||
int Spawn(const char* vfsPath, const char* args = nullptr);
|
||||
void Schedule();
|
||||
|
||||
// Called from the APIC timer handler on every tick.
|
||||
@@ -56,4 +57,7 @@ namespace Sched {
|
||||
// Called by terminated processes to mark themselves done
|
||||
void ExitProcess();
|
||||
|
||||
// Check if a process is still alive (Ready or Running)
|
||||
bool IsAlive(int pid);
|
||||
|
||||
}
|
||||
|
||||
@@ -165,4 +165,10 @@ namespace Kt
|
||||
};
|
||||
};
|
||||
|
||||
extern Kt::KernelErrorStream kerr;
|
||||
extern Kt::KernelErrorStream kerr;
|
||||
|
||||
// Forward-declare flanterm context for syscall access
|
||||
struct flanterm_context;
|
||||
namespace Kt {
|
||||
extern flanterm_context *ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user