feat: expand user mode, add DOOM game, add manpages
This commit is contained in:
@@ -7,3 +7,6 @@ qemu.log
|
|||||||
toolchain/local/
|
toolchain/local/
|
||||||
toolchain/build/
|
toolchain/build/
|
||||||
toolchain/src/
|
toolchain/src/
|
||||||
|
programs/bin/
|
||||||
|
programs/obj/
|
||||||
|
programs/src/doom/obj/
|
||||||
+1062
File diff suppressed because it is too large
Load Diff
Submodule
+1
Submodule doomgeneric added at fc60163949
@@ -20,6 +20,8 @@
|
|||||||
#include <Net/ByteOrder.hpp>
|
#include <Net/ByteOrder.hpp>
|
||||||
#include <Hal/MSR.hpp>
|
#include <Hal/MSR.hpp>
|
||||||
#include <Hal/GDT.hpp>
|
#include <Hal/GDT.hpp>
|
||||||
|
#include <Graphics/Cursor.hpp>
|
||||||
|
#include "../Libraries/flanterm/src/flanterm.h"
|
||||||
|
|
||||||
// Assembly entry point
|
// Assembly entry point
|
||||||
extern "C" void SyscallEntry();
|
extern "C" void SyscallEntry();
|
||||||
@@ -175,6 +177,47 @@ namespace Zenith {
|
|||||||
static uint16_t g_pingSeq = 0;
|
static uint16_t g_pingSeq = 0;
|
||||||
static constexpr uint16_t PING_ID = 0x2E01; // "ZE"
|
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) {
|
static int32_t Sys_Ping(uint32_t ipAddr, uint32_t timeoutMs) {
|
||||||
uint16_t seq = g_pingSeq++;
|
uint16_t seq = g_pingSeq++;
|
||||||
|
|
||||||
@@ -192,6 +235,27 @@ namespace Zenith {
|
|||||||
return (int32_t)(Timekeeping::GetMilliseconds() - start);
|
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 ----
|
// ---- Dispatch ----
|
||||||
|
|
||||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||||
@@ -248,6 +312,20 @@ namespace Zenith {
|
|||||||
return (int64_t)Sys_GetChar();
|
return (int64_t)Sys_GetChar();
|
||||||
case SYS_PING:
|
case SYS_PING:
|
||||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
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:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -274,7 +352,7 @@ namespace Zenith {
|
|||||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
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_GETKEY = 17;
|
||||||
static constexpr uint64_t SYS_GETCHAR = 18;
|
static constexpr uint64_t SYS_GETCHAR = 18;
|
||||||
static constexpr uint64_t SYS_PING = 19;
|
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 {
|
struct SysInfo {
|
||||||
char osName[32];
|
char osName[32];
|
||||||
|
|||||||
@@ -176,39 +176,66 @@ namespace Drivers::PS2::Keyboard {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle modifier keys
|
// Handle modifier keys (update state, but still push event to buffer)
|
||||||
|
bool isModifier = false;
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case ScLeftShift:
|
case ScLeftShift:
|
||||||
g_Modifiers.LeftShift = !released;
|
g_Modifiers.LeftShift = !released;
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
case ScRightShift:
|
case ScRightShift:
|
||||||
g_Modifiers.RightShift = !released;
|
g_Modifiers.RightShift = !released;
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
case ScLeftCtrl:
|
case ScLeftCtrl:
|
||||||
g_Modifiers.LeftCtrl = !released;
|
g_Modifiers.LeftCtrl = !released;
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
case ScLeftAlt:
|
case ScLeftAlt:
|
||||||
g_Modifiers.LeftAlt = !released;
|
g_Modifiers.LeftAlt = !released;
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
case ScCapsLock:
|
case ScCapsLock:
|
||||||
if (!released) {
|
if (!released) {
|
||||||
g_Modifiers.CapsLock = !g_Modifiers.CapsLock;
|
g_Modifiers.CapsLock = !g_Modifiers.CapsLock;
|
||||||
}
|
}
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
case ScNumLock:
|
case ScNumLock:
|
||||||
if (!released) {
|
if (!released) {
|
||||||
g_Modifiers.NumLock = !g_Modifiers.NumLock;
|
g_Modifiers.NumLock = !g_Modifiers.NumLock;
|
||||||
}
|
}
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
case ScScrollLock:
|
case ScScrollLock:
|
||||||
if (!released) {
|
if (!released) {
|
||||||
g_Modifiers.ScrollLock = !g_Modifiers.ScrollLock;
|
g_Modifiers.ScrollLock = !g_Modifiers.ScrollLock;
|
||||||
}
|
}
|
||||||
return;
|
isModifier = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
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
|
// Translate scancode to ASCII
|
||||||
char ascii = 0;
|
char ascii = 0;
|
||||||
if (keycode < 128) {
|
if (keycode < 128) {
|
||||||
|
|||||||
@@ -162,4 +162,9 @@ namespace Graphics::Cursor {
|
|||||||
g_OldY = newY;
|
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 Initialize(limine_framebuffer* framebuffer);
|
||||||
void Update();
|
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__)
|
#if defined (__x86_64__)
|
||||||
Hal::PrepareGDT();
|
Hal::PrepareGDT();
|
||||||
Hal::BridgeLoadGDT();
|
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
|
#endif
|
||||||
|
|
||||||
uint64_t hhdm_offset = hhdm_request.response->offset;
|
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) {
|
if (ptr != nullptr) {
|
||||||
memcpy(first, ptr, n);
|
memcpy(base, ptr, (uint64_t)n * 0x1000);
|
||||||
Free(ptr);
|
Free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return first;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageFrameAllocator::Free(void* ptr) {
|
void PageFrameAllocator::Free(void* ptr) {
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
||||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "Loading " << vfsPath;
|
|
||||||
|
|
||||||
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
||||||
if (handle < 0) {
|
if (handle < 0) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
||||||
@@ -78,6 +76,10 @@ namespace Sched {
|
|||||||
Fs::Vfs::VfsRead(handle, fileData, 0, fileSize);
|
Fs::Vfs::VfsRead(handle, fileData, 0, fileSize);
|
||||||
Fs::Vfs::VfsClose(handle);
|
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
|
// Validate ELF header
|
||||||
Elf64Header* hdr = (Elf64Header*)fileData;
|
Elf64Header* hdr = (Elf64Header*)fileData;
|
||||||
if (!ValidateElfHeader(hdr)) {
|
if (!ValidateElfHeader(hdr)) {
|
||||||
@@ -85,9 +87,6 @@ namespace Sched {
|
|||||||
return 0;
|
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
|
// Process program headers
|
||||||
for (uint16_t i = 0; i < hdr->e_phnum; i++) {
|
for (uint16_t i = 0; i < hdr->e_phnum; i++) {
|
||||||
Elf64ProgramHeader* phdr = (Elf64ProgramHeader*)(fileData + hdr->e_phoff + i * hdr->e_phentsize);
|
Elf64ProgramHeader* phdr = (Elf64ProgramHeader*)(fileData + hdr->e_phoff + i * hdr->e_phentsize);
|
||||||
@@ -100,9 +99,6 @@ namespace Sched {
|
|||||||
continue;
|
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
|
// Allocate pages and map them in the process PML4 with User bit
|
||||||
uint64_t segBase = phdr->p_vaddr & ~0xFFFULL;
|
uint64_t segBase = phdr->p_vaddr & ~0xFFFULL;
|
||||||
uint64_t segEnd = (phdr->p_vaddr + phdr->p_memsz + 0xFFF) & ~0xFFFULL;
|
uint64_t segEnd = (phdr->p_vaddr + phdr->p_memsz + 0xFFF) & ~0xFFFULL;
|
||||||
@@ -147,7 +143,6 @@ namespace Sched {
|
|||||||
uint64_t entryPoint = hdr->e_entry;
|
uint64_t entryPoint = hdr->e_entry;
|
||||||
Memory::g_heap->Free(fileData);
|
Memory::g_heap->Free(fileData);
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Loaded successfully, entry=" << kcp::hex << entryPoint << kcp::dec;
|
|
||||||
return entryPoint;
|
return entryPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ namespace Sched {
|
|||||||
processTable[i].kernelStackTop = 0;
|
processTable[i].kernelStackTop = 0;
|
||||||
processTable[i].userStackTop = 0;
|
processTable[i].userStackTop = 0;
|
||||||
processTable[i].heapNext = 0;
|
processTable[i].heapNext = 0;
|
||||||
|
processTable[i].args[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPid = -1;
|
currentPid = -1;
|
||||||
@@ -85,7 +86,7 @@ namespace Sched {
|
|||||||
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spawn(const char* vfsPath) {
|
int Spawn(const char* vfsPath, const char* args) {
|
||||||
int slot = -1;
|
int slot = -1;
|
||||||
for (int i = 0; i < MaxProcesses; i++) {
|
for (int i = 0; i < MaxProcesses; i++) {
|
||||||
if (processTable[i].state == ProcessState::Free) {
|
if (processTable[i].state == ProcessState::Free) {
|
||||||
@@ -96,7 +97,7 @@ namespace Sched {
|
|||||||
|
|
||||||
if (slot < 0) {
|
if (slot < 0) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "No free process slots";
|
Kt::KernelLogStream(Kt::ERROR, "Sched") << "No free process slots";
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create per-process PML4 with kernel-half copied
|
// Create per-process PML4 with kernel-half copied
|
||||||
@@ -106,20 +107,20 @@ namespace Sched {
|
|||||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||||
if (entry == 0) {
|
if (entry == 0) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate kernel stack (used during syscalls and interrupts)
|
// Allocate kernel stack (used during syscalls and interrupts)
|
||||||
void* firstPage = Memory::g_pfa->AllocateZeroed();
|
void* firstPage = Memory::g_pfa->AllocateZeroed();
|
||||||
if (firstPage == nullptr) {
|
if (firstPage == nullptr) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for kernel stack";
|
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for kernel stack";
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
void* stackMem = Memory::g_pfa->ReallocConsecutive(firstPage, StackPages);
|
void* stackMem = Memory::g_pfa->ReallocConsecutive(firstPage, StackPages);
|
||||||
if (stackMem == nullptr) {
|
if (stackMem == nullptr) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to allocate contiguous kernel stack";
|
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to allocate contiguous kernel stack";
|
||||||
Memory::g_pfa->Free(firstPage);
|
Memory::g_pfa->Free(firstPage);
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* kernelStackBase = (uint8_t*)stackMem;
|
uint8_t* kernelStackBase = (uint8_t*)stackMem;
|
||||||
@@ -132,7 +133,7 @@ namespace Sched {
|
|||||||
void* page = Memory::g_pfa->AllocateZeroed();
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
if (page == nullptr) {
|
if (page == nullptr) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for user stack";
|
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for user stack";
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, userStackBase + i * 0x1000);
|
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, userStackBase + i * 0x1000);
|
||||||
@@ -145,7 +146,7 @@ namespace Sched {
|
|||||||
void* stubPage = Memory::g_pfa->AllocateZeroed();
|
void* stubPage = Memory::g_pfa->AllocateZeroed();
|
||||||
if (stubPage == nullptr) {
|
if (stubPage == nullptr) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for exit stub";
|
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for exit stub";
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
uint64_t stubPhys = Memory::SubHHDM((uint64_t)stubPage);
|
uint64_t stubPhys = Memory::SubHHDM((uint64_t)stubPage);
|
||||||
Memory::VMM::Paging::MapUserIn(pml4Phys, stubPhys, ExitStubAddr);
|
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.userStackTop = UserStackTop - 8; // account for pushed exit stub return address
|
||||||
proc.heapNext = UserHeapBase;
|
proc.heapNext = UserHeapBase;
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Spawned process " << (uint64_t)proc.pid
|
// Copy arguments string into process
|
||||||
<< " (" << vfsPath << ") entry=" << kcp::hex << entry
|
proc.args[0] = '\0';
|
||||||
<< " kstack=" << (uint64_t)kernelStackBase << "-" << kernelStackTop
|
if (args != nullptr) {
|
||||||
<< " ustack=" << userStackBase << "-" << UserStackTop
|
int i = 0;
|
||||||
<< " pml4=" << pml4Phys << kcp::dec;
|
for (; i < 255 && args[i]; i++) {
|
||||||
|
proc.args[i] = args[i];
|
||||||
|
}
|
||||||
|
proc.args[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return proc.pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Schedule() {
|
void Schedule() {
|
||||||
@@ -271,8 +278,6 @@ namespace Sched {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Process " << (uint64_t)processTable[currentPid].pid << " terminated";
|
|
||||||
|
|
||||||
processTable[currentPid].state = ProcessState::Terminated;
|
processTable[currentPid].state = ProcessState::Terminated;
|
||||||
|
|
||||||
int next = -1;
|
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 kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
|
||||||
uint64_t userStackTop; // User-space stack top
|
uint64_t userStackTop; // User-space stack top
|
||||||
uint64_t heapNext; // Simple bump allocator for user heap
|
uint64_t heapNext; // Simple bump allocator for user heap
|
||||||
|
char args[256]; // Command-line arguments (set by parent via Spawn)
|
||||||
};
|
};
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void Spawn(const char* vfsPath);
|
int Spawn(const char* vfsPath, const char* args = nullptr);
|
||||||
void Schedule();
|
void Schedule();
|
||||||
|
|
||||||
// Called from the APIC timer handler on every tick.
|
// Called from the APIC timer handler on every tick.
|
||||||
@@ -56,4 +57,7 @@ namespace Sched {
|
|||||||
// Called by terminated processes to mark themselves done
|
// Called by terminated processes to mark themselves done
|
||||||
void ExitProcess();
|
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;
|
||||||
|
}
|
||||||
+11
-1
@@ -59,9 +59,19 @@ PROGRAMS := $(notdir $(wildcard src/*))
|
|||||||
# Build targets: one ELF per program.
|
# Build targets: one ELF per program.
|
||||||
TARGETS := $(addprefix $(BINDIR)/,$(addsuffix .elf,$(PROGRAMS)))
|
TARGETS := $(addprefix $(BINDIR)/,$(addsuffix .elf,$(PROGRAMS)))
|
||||||
|
|
||||||
|
# Man pages source directory.
|
||||||
|
MANDIR := man
|
||||||
|
MANSRC := $(wildcard $(MANDIR)/*.*)
|
||||||
|
MANDST := $(patsubst $(MANDIR)/%,$(BINDIR)/man/%,$(MANSRC))
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: $(TARGETS)
|
all: $(TARGETS) $(MANDST)
|
||||||
|
|
||||||
|
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
||||||
|
$(BINDIR)/man/%: $(MANDIR)/%
|
||||||
|
mkdir -p $(BINDIR)/man
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
# Build each program from its source files.
|
# Build each program from its source files.
|
||||||
# For now each program is a single .cpp file compiled and linked directly.
|
# For now each program is a single .cpp file compiled and linked directly.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -31,6 +31,20 @@ namespace Zenith {
|
|||||||
static constexpr uint64_t SYS_GETKEY = 17;
|
static constexpr uint64_t SYS_GETKEY = 17;
|
||||||
static constexpr uint64_t SYS_GETCHAR = 18;
|
static constexpr uint64_t SYS_GETCHAR = 18;
|
||||||
static constexpr uint64_t SYS_PING = 19;
|
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 {
|
struct SysInfo {
|
||||||
char osName[32];
|
char osName[32];
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef _LIBC_ASSERT_H
|
||||||
|
#define _LIBC_ASSERT_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void __assert_fail(const char *expr, const char *file, int line, const char *func);
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
#define assert(expr) ((void)0)
|
||||||
|
#else
|
||||||
|
#define assert(expr) \
|
||||||
|
((expr) ? (void)0 : __assert_fail(#expr, __FILE__, __LINE__, __func__))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_ASSERT_H */
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef _LIBC_CTYPE_H
|
||||||
|
#define _LIBC_CTYPE_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int isalpha(int c);
|
||||||
|
int isdigit(int c);
|
||||||
|
int isalnum(int c);
|
||||||
|
int isspace(int c);
|
||||||
|
int isupper(int c);
|
||||||
|
int islower(int c);
|
||||||
|
int isprint(int c);
|
||||||
|
int ispunct(int c);
|
||||||
|
int isxdigit(int c);
|
||||||
|
int iscntrl(int c);
|
||||||
|
int isgraph(int c);
|
||||||
|
int toupper(int c);
|
||||||
|
int tolower(int c);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_CTYPE_H */
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef _LIBC_ERRNO_H
|
||||||
|
#define _LIBC_ERRNO_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern int errno;
|
||||||
|
|
||||||
|
#define ENOENT 2
|
||||||
|
#define EIO 5
|
||||||
|
#define ENOMEM 12
|
||||||
|
#define EACCES 13
|
||||||
|
#define EINVAL 22
|
||||||
|
#define ERANGE 34
|
||||||
|
#define ENOSYS 38
|
||||||
|
#define EISDIR 21
|
||||||
|
#define ENOTDIR 20
|
||||||
|
#define EEXIST 17
|
||||||
|
#define EBADF 9
|
||||||
|
#define EPERM 1
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_ERRNO_H */
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef _LIBC_FCNTL_H
|
||||||
|
#define _LIBC_FCNTL_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define O_RDONLY 0
|
||||||
|
#define O_WRONLY 1
|
||||||
|
#define O_RDWR 2
|
||||||
|
#define O_CREAT 0x40
|
||||||
|
#define O_TRUNC 0x200
|
||||||
|
|
||||||
|
int open(const char *path, int flags, ...);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_FCNTL_H */
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef _LIBC_INTTYPES_H
|
||||||
|
#define _LIBC_INTTYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define PRId8 "d"
|
||||||
|
#define PRId16 "d"
|
||||||
|
#define PRId32 "d"
|
||||||
|
#define PRId64 "ld"
|
||||||
|
#define PRIi8 "i"
|
||||||
|
#define PRIi16 "i"
|
||||||
|
#define PRIi32 "i"
|
||||||
|
#define PRIi64 "li"
|
||||||
|
#define PRIu8 "u"
|
||||||
|
#define PRIu16 "u"
|
||||||
|
#define PRIu32 "u"
|
||||||
|
#define PRIu64 "lu"
|
||||||
|
#define PRIx8 "x"
|
||||||
|
#define PRIx16 "x"
|
||||||
|
#define PRIx32 "x"
|
||||||
|
#define PRIx64 "lx"
|
||||||
|
#define PRIX8 "X"
|
||||||
|
#define PRIX16 "X"
|
||||||
|
#define PRIX32 "X"
|
||||||
|
#define PRIX64 "lX"
|
||||||
|
|
||||||
|
#endif /* _LIBC_INTTYPES_H */
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef _LIBC_LIMITS_H
|
||||||
|
#define _LIBC_LIMITS_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define CHAR_BIT 8
|
||||||
|
|
||||||
|
#define SCHAR_MIN (-128)
|
||||||
|
#define SCHAR_MAX 127
|
||||||
|
#define UCHAR_MAX 255
|
||||||
|
|
||||||
|
#define SHRT_MIN (-32768)
|
||||||
|
#define SHRT_MAX 32767
|
||||||
|
#define USHRT_MAX 65535
|
||||||
|
|
||||||
|
#define INT_MIN (-2147483647 - 1)
|
||||||
|
#define INT_MAX 2147483647
|
||||||
|
#define UINT_MAX 4294967295U
|
||||||
|
|
||||||
|
#define LONG_MIN (-9223372036854775807L - 1)
|
||||||
|
#define LONG_MAX 9223372036854775807L
|
||||||
|
#define ULONG_MAX 18446744073709551615UL
|
||||||
|
|
||||||
|
#define LLONG_MIN (-9223372036854775807LL - 1)
|
||||||
|
#define LLONG_MAX 9223372036854775807LL
|
||||||
|
#define ULLONG_MAX 18446744073709551615ULL
|
||||||
|
|
||||||
|
#define PATH_MAX 4096
|
||||||
|
#define NAME_MAX 256
|
||||||
|
|
||||||
|
#endif /* _LIBC_LIMITS_H */
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef _LIBC_MATH_H
|
||||||
|
#define _LIBC_MATH_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HUGE_VAL __builtin_huge_val()
|
||||||
|
#define INFINITY __builtin_inff()
|
||||||
|
#define NAN __builtin_nanf("")
|
||||||
|
|
||||||
|
double fabs(double x);
|
||||||
|
double floor(double x);
|
||||||
|
double ceil(double x);
|
||||||
|
double sqrt(double x);
|
||||||
|
double sin(double x);
|
||||||
|
double cos(double x);
|
||||||
|
double atan2(double y, double x);
|
||||||
|
double pow(double base, double exp);
|
||||||
|
double log(double x);
|
||||||
|
double exp(double x);
|
||||||
|
double fmod(double x, double y);
|
||||||
|
double round(double x);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_MATH_H */
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#ifndef _LIBC_STDIO_H
|
||||||
|
#define _LIBC_STDIO_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EOF (-1)
|
||||||
|
#define SEEK_SET 0
|
||||||
|
#define SEEK_CUR 1
|
||||||
|
#define SEEK_END 2
|
||||||
|
#define BUFSIZ 1024
|
||||||
|
#define FILENAME_MAX 256
|
||||||
|
|
||||||
|
typedef struct _FILE {
|
||||||
|
int handle;
|
||||||
|
unsigned long pos;
|
||||||
|
unsigned long size;
|
||||||
|
int eof;
|
||||||
|
int error;
|
||||||
|
int is_std;
|
||||||
|
int ungetc_buf;
|
||||||
|
} FILE;
|
||||||
|
|
||||||
|
extern FILE *stdin;
|
||||||
|
extern FILE *stdout;
|
||||||
|
extern FILE *stderr;
|
||||||
|
|
||||||
|
int printf(const char *fmt, ...);
|
||||||
|
int fprintf(FILE *stream, const char *fmt, ...);
|
||||||
|
int sprintf(char *str, const char *fmt, ...);
|
||||||
|
int snprintf(char *str, size_t size, const char *fmt, ...);
|
||||||
|
int vprintf(const char *fmt, va_list ap);
|
||||||
|
int vfprintf(FILE *stream, const char *fmt, va_list ap);
|
||||||
|
int vsprintf(char *str, const char *fmt, va_list ap);
|
||||||
|
int vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
|
||||||
|
|
||||||
|
int puts(const char *s);
|
||||||
|
int putchar(int c);
|
||||||
|
|
||||||
|
FILE *fopen(const char *path, const char *mode);
|
||||||
|
int fclose(FILE *stream);
|
||||||
|
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||||
|
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||||
|
int fseek(FILE *stream, long offset, int whence);
|
||||||
|
long ftell(FILE *stream);
|
||||||
|
int fflush(FILE *stream);
|
||||||
|
|
||||||
|
int rename(const char *oldpath, const char *newpath);
|
||||||
|
int remove(const char *path);
|
||||||
|
|
||||||
|
int sscanf(const char *str, const char *fmt, ...);
|
||||||
|
|
||||||
|
int feof(FILE *stream);
|
||||||
|
int ferror(FILE *stream);
|
||||||
|
void clearerr(FILE *stream);
|
||||||
|
|
||||||
|
int fgetc(FILE *stream);
|
||||||
|
int getc(FILE *stream);
|
||||||
|
int ungetc(int c, FILE *stream);
|
||||||
|
char *fgets(char *s, int size, FILE *stream);
|
||||||
|
int fputs(const char *s, FILE *stream);
|
||||||
|
|
||||||
|
void perror(const char *s);
|
||||||
|
FILE *tmpfile(void);
|
||||||
|
char *tmpnam(char *s);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_STDIO_H */
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef _LIBC_STDLIB_H
|
||||||
|
#define _LIBC_STDLIB_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NULL
|
||||||
|
#define NULL ((void *)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EXIT_SUCCESS 0
|
||||||
|
#define EXIT_FAILURE 1
|
||||||
|
#define RAND_MAX 0x7fffffff
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int quot;
|
||||||
|
int rem;
|
||||||
|
} div_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long quot;
|
||||||
|
long rem;
|
||||||
|
} ldiv_t;
|
||||||
|
|
||||||
|
void *malloc(size_t size);
|
||||||
|
void free(void *ptr);
|
||||||
|
void *calloc(size_t nmemb, size_t size);
|
||||||
|
void *realloc(void *ptr, size_t size);
|
||||||
|
|
||||||
|
int atoi(const char *s);
|
||||||
|
long atol(const char *s);
|
||||||
|
double atof(const char *s);
|
||||||
|
|
||||||
|
int abs(int j);
|
||||||
|
long labs(long j);
|
||||||
|
|
||||||
|
void exit(int status);
|
||||||
|
void abort(void);
|
||||||
|
int atexit(void (*func)(void));
|
||||||
|
|
||||||
|
char *getenv(const char *name);
|
||||||
|
|
||||||
|
void qsort(void *base, size_t nmemb, size_t size,
|
||||||
|
int (*compar)(const void *, const void *));
|
||||||
|
|
||||||
|
long strtol(const char *nptr, char **endptr, int base);
|
||||||
|
unsigned long strtoul(const char *nptr, char **endptr, int base);
|
||||||
|
|
||||||
|
int rand(void);
|
||||||
|
void srand(unsigned int seed);
|
||||||
|
|
||||||
|
div_t div(int numer, int denom);
|
||||||
|
ldiv_t ldiv(long numer, long denom);
|
||||||
|
|
||||||
|
int system(const char *command);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_STDLIB_H */
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef _LIBC_STRING_H
|
||||||
|
#define _LIBC_STRING_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void *memcpy(void *dest, const void *src, size_t n);
|
||||||
|
void *memset(void *s, int c, size_t n);
|
||||||
|
void *memmove(void *dest, const void *src, size_t n);
|
||||||
|
int memcmp(const void *s1, const void *s2, size_t n);
|
||||||
|
|
||||||
|
size_t strlen(const char *s);
|
||||||
|
int strcmp(const char *s1, const char *s2);
|
||||||
|
int strncmp(const char *s1, const char *s2, size_t n);
|
||||||
|
char *strcpy(char *dest, const char *src);
|
||||||
|
char *strncpy(char *dest, const char *src, size_t n);
|
||||||
|
char *strcat(char *dest, const char *src);
|
||||||
|
char *strncat(char *dest, const char *src, size_t n);
|
||||||
|
char *strdup(const char *s);
|
||||||
|
char *strchr(const char *s, int c);
|
||||||
|
char *strrchr(const char *s, int c);
|
||||||
|
int strcasecmp(const char *s1, const char *s2);
|
||||||
|
int strncasecmp(const char *s1, const char *s2, size_t n);
|
||||||
|
char *strstr(const char *haystack, const char *needle);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_STRING_H */
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef _LIBC_STRINGS_H
|
||||||
|
#define _LIBC_STRINGS_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int strcasecmp(const char *s1, const char *s2);
|
||||||
|
int strncasecmp(const char *s1, const char *s2, size_t n);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_STRINGS_H */
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef _LIBC_SYS_STAT_H
|
||||||
|
#define _LIBC_SYS_STAT_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct stat {
|
||||||
|
unsigned long st_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
int mkdir(const char *path, unsigned int mode);
|
||||||
|
int stat(const char *path, struct stat *buf);
|
||||||
|
int fstat(int fd, struct stat *buf);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_SYS_STAT_H */
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef _LIBC_SYS_TIME_H
|
||||||
|
#define _LIBC_SYS_TIME_H
|
||||||
|
|
||||||
|
/* Stub header for ZenithOS */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct timeval {
|
||||||
|
long tv_sec;
|
||||||
|
long tv_usec;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct timezone {
|
||||||
|
int tz_minuteswest;
|
||||||
|
int tz_dsttime;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_SYS_TIME_H */
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef _LIBC_SYS_TYPES_H
|
||||||
|
#define _LIBC_SYS_TYPES_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned long size_t;
|
||||||
|
typedef long ssize_t;
|
||||||
|
typedef long off_t;
|
||||||
|
typedef int pid_t;
|
||||||
|
typedef unsigned int mode_t;
|
||||||
|
typedef unsigned int uid_t;
|
||||||
|
typedef unsigned int gid_t;
|
||||||
|
typedef long time_t;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_SYS_TYPES_H */
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef _LIBC_UNISTD_H
|
||||||
|
#define _LIBC_UNISTD_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int read(int fd, void *buf, size_t count);
|
||||||
|
int write(int fd, const void *buf, size_t count);
|
||||||
|
int close(int fd);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LIBC_UNISTD_H */
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* heap.h
|
||||||
|
* Userspace heap allocator for ZenithOS programs
|
||||||
|
* Free-list allocator backed by SYS_ALLOC page requests.
|
||||||
|
* Adapted from the kernel HeapAllocator.
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <zenith/syscall.h>
|
||||||
|
|
||||||
|
namespace zenith {
|
||||||
|
namespace heap_detail {
|
||||||
|
|
||||||
|
static constexpr uint64_t HEADER_MAGIC = 0x5A484541; // "ZHEA"
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
uint64_t magic;
|
||||||
|
uint64_t size; // user-requested size
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct FreeNode {
|
||||||
|
uint64_t size; // total size of this free block (including node)
|
||||||
|
FreeNode* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per-process heap state (single TU per program, so static is fine)
|
||||||
|
static FreeNode g_head{0, nullptr};
|
||||||
|
static bool g_initialized = false;
|
||||||
|
|
||||||
|
static inline Header* get_header(void* block) {
|
||||||
|
return (Header*)((uint8_t*)block - sizeof(Header));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void insert_free(void* ptr, uint64_t size) {
|
||||||
|
auto* node = (FreeNode*)ptr;
|
||||||
|
node->size = size;
|
||||||
|
node->next = g_head.next;
|
||||||
|
g_head.next = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void grow(uint64_t bytes) {
|
||||||
|
uint64_t pages = (bytes + 0xFFF) / 0x1000;
|
||||||
|
if (pages < 4) pages = 4; // grow at least 16 KiB at a time
|
||||||
|
|
||||||
|
void* mem = zenith::alloc(pages * 0x1000);
|
||||||
|
if (mem != nullptr)
|
||||||
|
insert_free(mem, pages * 0x1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace heap_detail
|
||||||
|
|
||||||
|
// ---- Public API ----
|
||||||
|
|
||||||
|
inline void* malloc(uint64_t size) {
|
||||||
|
using namespace heap_detail;
|
||||||
|
|
||||||
|
if (!g_initialized) {
|
||||||
|
grow(16 * 0x1000); // seed with 64 KiB
|
||||||
|
g_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t needed = size + sizeof(Header);
|
||||||
|
needed = (needed + 15) & ~15ULL; // 16-byte alignment
|
||||||
|
|
||||||
|
FreeNode* prev = &g_head;
|
||||||
|
FreeNode* current = g_head.next;
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
if (current->size >= needed) {
|
||||||
|
uint64_t blockSize = current->size;
|
||||||
|
|
||||||
|
// Unlink
|
||||||
|
prev->next = current->next;
|
||||||
|
|
||||||
|
// Split if worthwhile
|
||||||
|
if (blockSize > needed + sizeof(FreeNode) + 16) {
|
||||||
|
void* rest = (void*)((uint8_t*)current + needed);
|
||||||
|
uint64_t restSize = blockSize - needed;
|
||||||
|
insert_free(rest, restSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write allocation header
|
||||||
|
Header* header = (Header*)current;
|
||||||
|
header->magic = HEADER_MAGIC;
|
||||||
|
header->size = size;
|
||||||
|
|
||||||
|
return (void*)((uint8_t*)header + sizeof(Header));
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = current;
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fit — grow and retry
|
||||||
|
grow(needed);
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void mfree(void* ptr) {
|
||||||
|
using namespace heap_detail;
|
||||||
|
|
||||||
|
if (ptr == nullptr) return;
|
||||||
|
|
||||||
|
Header* header = get_header(ptr);
|
||||||
|
|
||||||
|
uint64_t blockSize = header->size + sizeof(Header);
|
||||||
|
blockSize = (blockSize + 15) & ~15ULL;
|
||||||
|
|
||||||
|
insert_free((void*)header, blockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void* realloc(void* ptr, uint64_t size) {
|
||||||
|
if (ptr == nullptr) return malloc(size);
|
||||||
|
|
||||||
|
auto* header = heap_detail::get_header(ptr);
|
||||||
|
uint64_t old = header->size;
|
||||||
|
|
||||||
|
void* newBlock = malloc(size);
|
||||||
|
if (newBlock == nullptr) return nullptr;
|
||||||
|
|
||||||
|
// Copy the smaller of old/new sizes
|
||||||
|
uint64_t copySize = (old < size) ? old : size;
|
||||||
|
uint8_t* dst = (uint8_t*)newBlock;
|
||||||
|
uint8_t* src = (uint8_t*)ptr;
|
||||||
|
for (uint64_t i = 0; i < copySize; i++)
|
||||||
|
dst[i] = src[i];
|
||||||
|
|
||||||
|
mfree(ptr);
|
||||||
|
return newBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace zenith
|
||||||
@@ -117,6 +117,9 @@ namespace zenith {
|
|||||||
inline void yield() { syscall0(Zenith::SYS_YIELD); }
|
inline void yield() { syscall0(Zenith::SYS_YIELD); }
|
||||||
inline void sleep_ms(uint64_t ms) { syscall1(Zenith::SYS_SLEEP_MS, ms); }
|
inline void sleep_ms(uint64_t ms) { syscall1(Zenith::SYS_SLEEP_MS, ms); }
|
||||||
inline int getpid() { return (int)syscall0(Zenith::SYS_GETPID); }
|
inline int getpid() { return (int)syscall0(Zenith::SYS_GETPID); }
|
||||||
|
inline int spawn(const char* path, const char* args = nullptr) {
|
||||||
|
return (int)syscall2(Zenith::SYS_SPAWN, (uint64_t)path, (uint64_t)args);
|
||||||
|
}
|
||||||
|
|
||||||
// Console
|
// Console
|
||||||
inline void print(const char* text) { syscall1(Zenith::SYS_PRINT, (uint64_t)text); }
|
inline void print(const char* text) { syscall1(Zenith::SYS_PRINT, (uint64_t)text); }
|
||||||
@@ -154,4 +157,23 @@ namespace zenith {
|
|||||||
return (int32_t)syscall2(Zenith::SYS_PING, (uint64_t)ip, (uint64_t)timeoutMs);
|
return (int32_t)syscall2(Zenith::SYS_PING, (uint64_t)ip, (uint64_t)timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process management
|
||||||
|
inline void waitpid(int pid) { syscall1(Zenith::SYS_WAITPID, (uint64_t)pid); }
|
||||||
|
|
||||||
|
// Framebuffer
|
||||||
|
inline void fb_info(Zenith::FbInfo* info) { syscall1(Zenith::SYS_FBINFO, (uint64_t)info); }
|
||||||
|
inline void* fb_map() { return (void*)syscall0(Zenith::SYS_FBMAP); }
|
||||||
|
|
||||||
|
// Arguments
|
||||||
|
inline int getargs(char* buf, uint64_t maxLen) {
|
||||||
|
return (int)syscall2(Zenith::SYS_GETARGS, (uint64_t)buf, maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal
|
||||||
|
inline void termsize(int* cols, int* rows) {
|
||||||
|
uint64_t r = (uint64_t)syscall0(Zenith::SYS_TERMSIZE);
|
||||||
|
if (cols) *cols = (int)(r & 0xFFFFFFFF);
|
||||||
|
if (rows) *rows = (int)(r >> 32);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
.TH FILE 2
|
||||||
|
.SH NAME
|
||||||
|
open, read, getsize, close, readdir - file I/O system calls
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.BI int zenith::open(const char* path);
|
||||||
|
.BI int zenith::read(int handle, uint8_t* buf, uint64_t offset, uint64_t size);
|
||||||
|
.BI uint64_t zenith::getsize(int handle);
|
||||||
|
.BI void zenith::close(int handle);
|
||||||
|
.BI int zenith::readdir(const char* path, const char** names, int max);
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ZenithOS provides a simple read-only Virtual File System (VFS)
|
||||||
|
backed by the boot ramdisk. Files are accessed via paths in the
|
||||||
|
format "<drive>:/<name>", where drive 0 is the ramdisk.
|
||||||
|
|
||||||
|
.SS open
|
||||||
|
Opens a file and returns a non-negative handle on success, or a
|
||||||
|
negative value on error (file not found, no free handles).
|
||||||
|
|
||||||
|
int h = zenith::open("0:/shell.elf");
|
||||||
|
|
||||||
|
.SS read
|
||||||
|
Reads up to 'size' bytes starting at 'offset' into 'buf'.
|
||||||
|
Returns the number of bytes actually read, or negative on error.
|
||||||
|
There is no implicit file position -- the offset is explicit on
|
||||||
|
every call.
|
||||||
|
|
||||||
|
uint8_t buf[512];
|
||||||
|
int n = zenith::read(h, buf, 0, 512);
|
||||||
|
|
||||||
|
.SS getsize
|
||||||
|
Returns the total size in bytes of the file.
|
||||||
|
|
||||||
|
uint64_t sz = zenith::getsize(h);
|
||||||
|
|
||||||
|
.SS close
|
||||||
|
Closes the file handle and frees kernel resources.
|
||||||
|
|
||||||
|
zenith::close(h);
|
||||||
|
|
||||||
|
.SS readdir
|
||||||
|
Lists entries in a directory. Up to 'max' entry names (max 64)
|
||||||
|
are written to the 'names' array. The kernel allocates a user-
|
||||||
|
accessible page for the string data automatically.
|
||||||
|
|
||||||
|
const char* entries[64];
|
||||||
|
int count = zenith::readdir("0:/", entries, 64);
|
||||||
|
|
||||||
|
.SH READING PATTERN
|
||||||
|
The standard pattern for reading a file:
|
||||||
|
|
||||||
|
int h = zenith::open("0:/myfile.txt");
|
||||||
|
uint64_t size = zenith::getsize(h);
|
||||||
|
uint8_t buf[512];
|
||||||
|
uint64_t off = 0;
|
||||||
|
while (off < size) {
|
||||||
|
uint64_t chunk = size - off;
|
||||||
|
if (chunk > 511) chunk = 511;
|
||||||
|
int n = zenith::read(h, buf, off, chunk);
|
||||||
|
if (n <= 0) break;
|
||||||
|
buf[n] = '\0';
|
||||||
|
zenith::print((const char*)buf);
|
||||||
|
off += n;
|
||||||
|
}
|
||||||
|
zenith::close(h);
|
||||||
|
|
||||||
|
.SH NOTES
|
||||||
|
The filesystem is read-only. There are no write or create calls.
|
||||||
|
All files live on the ramdisk which is loaded at boot from a
|
||||||
|
USTAR tar archive.
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
syscalls(2), spawn(2), malloc(3)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
.TH FRAMEBUFFER 2
|
||||||
|
.SH NAME
|
||||||
|
fb_info, fb_map - direct framebuffer access
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.BI void zenith::fb_info(Zenith::FbInfo* info);
|
||||||
|
.BI void* zenith::fb_map();
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
These syscalls allow userspace programs to access the linear
|
||||||
|
framebuffer directly for graphical output.
|
||||||
|
|
||||||
|
.SS fb_info
|
||||||
|
Fills in an FbInfo structure with the framebuffer geometry:
|
||||||
|
|
||||||
|
Zenith::FbInfo fb;
|
||||||
|
zenith::fb_info(&fb);
|
||||||
|
// fb.width, fb.height, fb.pitch, fb.bpp
|
||||||
|
|
||||||
|
The pitch is the number of bytes per scanline (may be larger
|
||||||
|
than width * 4 due to alignment). bpp is always 32.
|
||||||
|
|
||||||
|
.SS fb_map
|
||||||
|
Maps the physical framebuffer into the process address space at
|
||||||
|
a fixed virtual address (0x50000000) and returns that address.
|
||||||
|
|
||||||
|
uint32_t* pixels = (uint32_t*)zenith::fb_map();
|
||||||
|
|
||||||
|
Each pixel is a 32-bit value in 0xAARRGGBB format (blue in the
|
||||||
|
low byte). Writing to this memory directly updates the screen.
|
||||||
|
|
||||||
|
.SH PIXEL FORMAT
|
||||||
|
Bits 31-24: Alpha (unused, typically 0xFF)
|
||||||
|
Bits 23-16: Red
|
||||||
|
Bits 15-8: Green
|
||||||
|
Bits 7-0: Blue
|
||||||
|
|
||||||
|
Example: red = 0x00FF0000, green = 0x0000FF00, blue = 0x000000FF
|
||||||
|
|
||||||
|
.SH EXAMPLE
|
||||||
|
Fill the screen with blue:
|
||||||
|
|
||||||
|
Zenith::FbInfo fb;
|
||||||
|
zenith::fb_info(&fb);
|
||||||
|
uint32_t* pixels = (uint32_t*)zenith::fb_map();
|
||||||
|
|
||||||
|
for (uint64_t y = 0; y < fb.height; y++) {
|
||||||
|
uint32_t* row = (uint32_t*)((uint8_t*)pixels + y * fb.pitch);
|
||||||
|
for (uint64_t x = 0; x < fb.width; x++) {
|
||||||
|
row[x] = 0x000000FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.SH NOTES
|
||||||
|
After mapping, the cursor overlay is not composited. Programs
|
||||||
|
that use the framebuffer take full control of screen output.
|
||||||
|
|
||||||
|
Only one mapping per process is supported. Calling fb_map()
|
||||||
|
multiple times returns the same address.
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
syscalls(2), malloc(3)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
.TH INTRO 1
|
||||||
|
.SH NAME
|
||||||
|
intro - introduction to ZenithOS userspace
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ZenithOS is a hobbyist 64-bit operating system written in C++20.
|
||||||
|
Userspace programs run in Ring 3, are loaded as static ELF64
|
||||||
|
binaries, and communicate with the kernel through the x86-64
|
||||||
|
SYSCALL/SYSRET mechanism.
|
||||||
|
|
||||||
|
Programs are compiled with a freestanding cross-compiler and
|
||||||
|
linked at virtual address 0x400000. There is no standard C
|
||||||
|
library for C++ programs -- all system interaction goes through
|
||||||
|
the zenith:: syscall wrappers.
|
||||||
|
|
||||||
|
.SH GETTING STARTED
|
||||||
|
To write a new program, create a directory under programs/src/
|
||||||
|
with a main.cpp file. The entry point is:
|
||||||
|
|
||||||
|
extern "C" void _start() { ... }
|
||||||
|
|
||||||
|
There is no argc/argv. Include <zenith/syscall.h> for the full
|
||||||
|
typed syscall API. Include <zenith/heap.h> for malloc/mfree.
|
||||||
|
|
||||||
|
Build with:
|
||||||
|
|
||||||
|
cd programs && make
|
||||||
|
|
||||||
|
The resulting ELF binary appears in programs/bin/.
|
||||||
|
|
||||||
|
.SH SHELL
|
||||||
|
The built-in shell is the primary way to interact with ZenithOS.
|
||||||
|
Type 'help' at the shell prompt for a list of commands. Use
|
||||||
|
'man shell' for detailed shell documentation.
|
||||||
|
|
||||||
|
.SH MAN PAGES
|
||||||
|
The following man pages are available:
|
||||||
|
|
||||||
|
intro(1) This page
|
||||||
|
shell(1) Shell commands reference
|
||||||
|
man(1) The man command itself
|
||||||
|
syscalls(2) Overview of all syscalls
|
||||||
|
spawn(2) Process spawning
|
||||||
|
file(2) File I/O syscalls
|
||||||
|
framebuffer(2) Framebuffer access
|
||||||
|
malloc(3) Memory allocation
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
shell(1), syscalls(2), malloc(3)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
.TH MALLOC 3
|
||||||
|
.SH NAME
|
||||||
|
malloc, mfree, realloc - userspace heap allocation
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.BI void* zenith::malloc(uint64_t size);
|
||||||
|
.BI void zenith::mfree(void* ptr);
|
||||||
|
.BI void* zenith::realloc(void* ptr, uint64_t size);
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
The userspace heap provides dynamic memory allocation on top of
|
||||||
|
the kernel's page-mapping syscall (SYS_ALLOC). Include the
|
||||||
|
header <zenith/heap.h> to use these functions.
|
||||||
|
|
||||||
|
.SS malloc
|
||||||
|
Allocates 'size' bytes from the free list. Returns a 16-byte
|
||||||
|
aligned pointer, or nullptr on failure. When the free list is
|
||||||
|
empty, it requests more pages from the kernel via SYS_ALLOC
|
||||||
|
(minimum 16 KiB growth, initial seed of 64 KiB).
|
||||||
|
|
||||||
|
char* buf = (char*)zenith::malloc(1024);
|
||||||
|
|
||||||
|
.SS mfree
|
||||||
|
Returns the block to the userspace free list. No syscall is
|
||||||
|
made -- the memory stays mapped and is immediately reusable.
|
||||||
|
Passing nullptr is a safe no-op.
|
||||||
|
|
||||||
|
zenith::mfree(buf);
|
||||||
|
|
||||||
|
.SS realloc
|
||||||
|
Resizes the allocation to 'size' bytes. Allocates a new block,
|
||||||
|
copies the smaller of old/new sizes, and frees the old block.
|
||||||
|
If ptr is nullptr, behaves like malloc.
|
||||||
|
|
||||||
|
buf = (char*)zenith::realloc(buf, 2048);
|
||||||
|
|
||||||
|
.SH IMPLEMENTATION
|
||||||
|
The allocator uses a linked free-list with first-fit search.
|
||||||
|
Blocks larger than needed are split. The allocation header is
|
||||||
|
16 bytes (magic + size). All allocations are 16-byte aligned.
|
||||||
|
|
||||||
|
The heap grows by requesting pages from the kernel via
|
||||||
|
SYS_ALLOC. These pages are never returned to the kernel (since
|
||||||
|
SYS_FREE is currently a no-op), but mfree makes them available
|
||||||
|
for future malloc calls within the process.
|
||||||
|
|
||||||
|
.SH LOW-LEVEL PAGE API
|
||||||
|
For large allocations or when direct page control is needed:
|
||||||
|
|
||||||
|
void* zenith::alloc(uint64_t size); // SYS_ALLOC
|
||||||
|
void zenith::free(void* ptr); // SYS_FREE (no-op)
|
||||||
|
|
||||||
|
alloc() maps zeroed pages starting at 0x40000000 and growing
|
||||||
|
upward. Size is rounded up to 4 KiB page boundaries.
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
syscalls(2), file(2)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.TH MAN 1
|
||||||
|
.SH NAME
|
||||||
|
man - display manual pages
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.BI man topic
|
||||||
|
.BI man section topic
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
The man command displays manual pages from the ramdisk in a
|
||||||
|
fullscreen pager. Pages are stored as plain text files with
|
||||||
|
simple formatting directives.
|
||||||
|
|
||||||
|
If no section is specified, sections 1, 2, and 3 are searched
|
||||||
|
in order. If a section number is given, only that section is
|
||||||
|
checked.
|
||||||
|
|
||||||
|
.SH KEY BINDINGS
|
||||||
|
|
||||||
|
.B Navigation
|
||||||
|
j, Down Arrow Scroll down one line
|
||||||
|
k, Up Arrow Scroll up one line
|
||||||
|
Space, Page Down Scroll down one page
|
||||||
|
b, Page Up Scroll up one page
|
||||||
|
g, Home Go to top
|
||||||
|
G, End Go to bottom
|
||||||
|
q Quit
|
||||||
|
|
||||||
|
.SH SECTIONS
|
||||||
|
1 User commands (shell built-ins)
|
||||||
|
2 System calls (kernel interface)
|
||||||
|
3 Library functions (userspace libraries)
|
||||||
|
|
||||||
|
.SH FILES
|
||||||
|
Man pages are stored on the ramdisk at:
|
||||||
|
|
||||||
|
0:/man/<topic>.<section>
|
||||||
|
|
||||||
|
For example, man intro reads 0:/man/intro.1
|
||||||
|
|
||||||
|
.SH EXAMPLES
|
||||||
|
man intro View the introduction
|
||||||
|
man 2 syscalls View syscall overview (section 2)
|
||||||
|
man malloc View malloc documentation
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
intro(1), shell(1), syscalls(2)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
.TH SHELL 1
|
||||||
|
.SH NAME
|
||||||
|
shell - ZenithOS interactive command shell
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
The ZenithOS shell is a simple command interpreter that runs as
|
||||||
|
the first userspace process. It provides basic file inspection,
|
||||||
|
process management, networking, and documentation access.
|
||||||
|
|
||||||
|
.SH COMMANDS
|
||||||
|
|
||||||
|
.SS help
|
||||||
|
Display a list of available commands.
|
||||||
|
|
||||||
|
.SS info
|
||||||
|
Show the OS name, version, and syscall API version number.
|
||||||
|
|
||||||
|
.SS man <topic>
|
||||||
|
Open a manual page in the fullscreen pager. See man(1).
|
||||||
|
|
||||||
|
.SS ls
|
||||||
|
List all files on the ramdisk (drive 0:/).
|
||||||
|
|
||||||
|
.SS cat <file>
|
||||||
|
Print the contents of a ramdisk file to the terminal.
|
||||||
|
Example: cat hello.elf
|
||||||
|
|
||||||
|
.SS run <file>
|
||||||
|
Spawn a new process from an ELF binary on the ramdisk and wait
|
||||||
|
for it to exit. The shell blocks until the child process
|
||||||
|
terminates.
|
||||||
|
Example: run hello.elf
|
||||||
|
|
||||||
|
.SS ping <ip>
|
||||||
|
Send 4 ICMP echo requests to the given IP address and display
|
||||||
|
round-trip times. Timeout is 3 seconds per request.
|
||||||
|
Example: ping 10.0.2.2
|
||||||
|
|
||||||
|
.SS uptime
|
||||||
|
Display the system uptime in minutes, seconds, and milliseconds.
|
||||||
|
|
||||||
|
.SS clear
|
||||||
|
Clear the terminal screen.
|
||||||
|
|
||||||
|
.SS exit
|
||||||
|
Terminate the shell process.
|
||||||
|
|
||||||
|
.SH INPUT
|
||||||
|
The shell reads input character by character using SYS_GETCHAR.
|
||||||
|
Backspace is supported. Lines are limited to 255 characters.
|
||||||
|
There is no command history or tab completion.
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
man(1), intro(1), syscalls(2)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
.TH SPAWN 2
|
||||||
|
.SH NAME
|
||||||
|
spawn, waitpid - create and wait for processes
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.BI int zenith::spawn(const char* path, const char* args = nullptr);
|
||||||
|
.BI void zenith::waitpid(int pid);
|
||||||
|
.BI int zenith::getargs(char* buf, uint64_t maxLen);
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
|
||||||
|
.SS spawn
|
||||||
|
Loads the ELF64 binary at the given VFS path and creates a new
|
||||||
|
process. The path must include the drive prefix, for example:
|
||||||
|
|
||||||
|
int pid = zenith::spawn("0:/hello.elf");
|
||||||
|
|
||||||
|
An optional second argument passes a string to the child:
|
||||||
|
|
||||||
|
int pid = zenith::spawn("0:/man.elf", "intro");
|
||||||
|
|
||||||
|
The new process gets its own PML4 page table, a 16 KiB stack
|
||||||
|
(at 0x7FFFFEF000-0x7FFFFFF000), and begins executing at the
|
||||||
|
ELF entry point (_start).
|
||||||
|
|
||||||
|
Returns the new process's PID on success, or -1 on failure.
|
||||||
|
Failure occurs when there are no free process slots (max 16),
|
||||||
|
the file cannot be found, or the ELF is invalid.
|
||||||
|
|
||||||
|
.SS waitpid
|
||||||
|
Blocks the calling process until the process with the given PID
|
||||||
|
has exited. Internally, this yields the CPU in a loop:
|
||||||
|
|
||||||
|
zenith::waitpid(pid);
|
||||||
|
|
||||||
|
This is how the shell implements foreground process execution --
|
||||||
|
it spawns a child and waits for it to complete before showing
|
||||||
|
the next prompt.
|
||||||
|
|
||||||
|
.SH EXAMPLES
|
||||||
|
Spawn a program and wait for it:
|
||||||
|
|
||||||
|
int pid = zenith::spawn("0:/hello.elf");
|
||||||
|
if (pid < 0) {
|
||||||
|
zenith::print("spawn failed\n");
|
||||||
|
} else {
|
||||||
|
zenith::waitpid(pid);
|
||||||
|
zenith::print("child exited\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
.SS getargs
|
||||||
|
Copies the argument string into buf (up to maxLen bytes, always
|
||||||
|
null-terminated). Returns the number of characters copied, or
|
||||||
|
-1 on error.
|
||||||
|
|
||||||
|
char args[256];
|
||||||
|
zenith::getargs(args, sizeof(args));
|
||||||
|
|
||||||
|
The argument string is set by the parent when calling spawn().
|
||||||
|
If no arguments were provided, the buffer will be empty.
|
||||||
|
|
||||||
|
.SH NOTES
|
||||||
|
The _start() entry point receives no argc/argv. Use getargs()
|
||||||
|
to retrieve the argument string passed by the parent process.
|
||||||
|
|
||||||
|
Process exit codes are not yet collected by waitpid.
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
syscalls(2), file(2)
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
.TH SYSCALLS 2
|
||||||
|
.SH NAME
|
||||||
|
syscalls - overview of ZenithOS system calls
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ZenithOS provides 26 system calls (numbers 0-25) for userspace
|
||||||
|
programs. Syscalls use the x86-64 SYSCALL instruction with the
|
||||||
|
following register convention:
|
||||||
|
|
||||||
|
RAX Syscall number (in) / return value (out)
|
||||||
|
RDI Argument 1
|
||||||
|
RSI Argument 2
|
||||||
|
RDX Argument 3
|
||||||
|
R10 Argument 4
|
||||||
|
R8 Argument 5
|
||||||
|
R9 Argument 6
|
||||||
|
|
||||||
|
Include <zenith/syscall.h> for typed wrappers in the zenith::
|
||||||
|
namespace.
|
||||||
|
|
||||||
|
.SH PROCESS MANAGEMENT
|
||||||
|
.B SYS_EXIT (0)
|
||||||
|
Terminate the calling process.
|
||||||
|
void zenith::exit(int code = 0);
|
||||||
|
|
||||||
|
.B SYS_YIELD (1)
|
||||||
|
Yield the remainder of the time slice.
|
||||||
|
void zenith::yield();
|
||||||
|
|
||||||
|
.B SYS_SLEEP_MS (2)
|
||||||
|
Sleep for at least the given number of milliseconds.
|
||||||
|
void zenith::sleep_ms(uint64_t ms);
|
||||||
|
|
||||||
|
.B SYS_GETPID (3)
|
||||||
|
Return the PID of the calling process.
|
||||||
|
int zenith::getpid();
|
||||||
|
|
||||||
|
.B SYS_SPAWN (20)
|
||||||
|
Spawn a new process from an ELF binary on the VFS.
|
||||||
|
int zenith::spawn(const char* path, const char* args = nullptr);
|
||||||
|
|
||||||
|
.B SYS_WAITPID (23)
|
||||||
|
Block until the given process has exited.
|
||||||
|
void zenith::waitpid(int pid);
|
||||||
|
|
||||||
|
.SH CONSOLE I/O
|
||||||
|
.B SYS_PRINT (4)
|
||||||
|
Write a null-terminated string to the terminal.
|
||||||
|
void zenith::print(const char* text);
|
||||||
|
|
||||||
|
.B SYS_PUTCHAR (5)
|
||||||
|
Write a single character to the terminal.
|
||||||
|
void zenith::putchar(char c);
|
||||||
|
|
||||||
|
.SH FILE I/O
|
||||||
|
.B SYS_OPEN (6)
|
||||||
|
Open a file. Returns a handle or negative on error.
|
||||||
|
int zenith::open(const char* path);
|
||||||
|
|
||||||
|
.B SYS_READ (7)
|
||||||
|
Read bytes from a file at a given offset.
|
||||||
|
int zenith::read(int h, uint8_t* buf, uint64_t off, uint64_t sz);
|
||||||
|
|
||||||
|
.B SYS_GETSIZE (8)
|
||||||
|
Get the size of an open file in bytes.
|
||||||
|
uint64_t zenith::getsize(int handle);
|
||||||
|
|
||||||
|
.B SYS_CLOSE (9)
|
||||||
|
Close a file handle.
|
||||||
|
void zenith::close(int handle);
|
||||||
|
|
||||||
|
.B SYS_READDIR (10)
|
||||||
|
List directory entries (max 64 per call).
|
||||||
|
int zenith::readdir(const char* path, const char** names, int max);
|
||||||
|
|
||||||
|
.SH MEMORY
|
||||||
|
.B SYS_ALLOC (11)
|
||||||
|
Map zeroed pages into the process address space.
|
||||||
|
void* zenith::alloc(uint64_t size);
|
||||||
|
|
||||||
|
.B SYS_FREE (12)
|
||||||
|
Reserved (currently a no-op).
|
||||||
|
void zenith::free(void* ptr);
|
||||||
|
|
||||||
|
.SH TIMEKEEPING
|
||||||
|
.B SYS_GETTICKS (13)
|
||||||
|
Get APIC timer ticks since boot.
|
||||||
|
uint64_t zenith::get_ticks();
|
||||||
|
|
||||||
|
.B SYS_GETMILLISECONDS (14)
|
||||||
|
Get milliseconds elapsed since boot.
|
||||||
|
uint64_t zenith::get_milliseconds();
|
||||||
|
|
||||||
|
.SH SYSTEM
|
||||||
|
.B SYS_GETINFO (15)
|
||||||
|
Get OS name, version, and configuration.
|
||||||
|
void zenith::get_info(Zenith::SysInfo* info);
|
||||||
|
|
||||||
|
.SH KEYBOARD
|
||||||
|
.B SYS_ISKEYAVAILABLE (16)
|
||||||
|
Check if a key event is pending (non-blocking).
|
||||||
|
bool zenith::is_key_available();
|
||||||
|
|
||||||
|
.B SYS_GETKEY (17)
|
||||||
|
Get the next key event (press or release).
|
||||||
|
void zenith::getkey(Zenith::KeyEvent* out);
|
||||||
|
|
||||||
|
.B SYS_GETCHAR (18)
|
||||||
|
Block until a printable character is typed.
|
||||||
|
char zenith::getchar();
|
||||||
|
|
||||||
|
.SH NETWORKING
|
||||||
|
.B SYS_PING (19)
|
||||||
|
Send an ICMP echo request and wait for reply.
|
||||||
|
int32_t zenith::ping(uint32_t ip, uint32_t timeoutMs);
|
||||||
|
|
||||||
|
.SH FRAMEBUFFER
|
||||||
|
.B SYS_FBINFO (21)
|
||||||
|
Get framebuffer dimensions and format.
|
||||||
|
void zenith::fb_info(Zenith::FbInfo* info);
|
||||||
|
|
||||||
|
.B SYS_FBMAP (22)
|
||||||
|
Map the framebuffer into process memory at 0x50000000.
|
||||||
|
void* zenith::fb_map();
|
||||||
|
|
||||||
|
.SH TERMINAL
|
||||||
|
.B SYS_TERMSIZE (24)
|
||||||
|
Get terminal dimensions (columns and rows).
|
||||||
|
void zenith::termsize(int* cols, int* rows);
|
||||||
|
|
||||||
|
.SH ARGUMENTS
|
||||||
|
.B SYS_GETARGS (25)
|
||||||
|
Get the argument string passed to this process at spawn time.
|
||||||
|
int zenith::getargs(char* buf, uint64_t maxLen);
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
spawn(2), file(2), framebuffer(2), malloc(3)
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,187 @@
|
|||||||
|
# Makefile for DOOM (doomgeneric) on ZenithOS
|
||||||
|
# Copyright (c) 2025 Daniel Hammer
|
||||||
|
|
||||||
|
MAKEFLAGS += -rR
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
# ---- Toolchain ----
|
||||||
|
|
||||||
|
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||||
|
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||||
|
CC := $(TOOLCHAIN_PREFIX)gcc
|
||||||
|
LD := $(TOOLCHAIN_PREFIX)gcc
|
||||||
|
else
|
||||||
|
CC := gcc
|
||||||
|
LD := gcc
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ---- Paths ----
|
||||||
|
|
||||||
|
DOOM_SRC := ../../../doomgeneric/doomgeneric
|
||||||
|
LIBC_INC := ../../include/libc
|
||||||
|
PROG_INC := ../../include
|
||||||
|
LINK_LD := ../../link.ld
|
||||||
|
BINDIR := ../../bin
|
||||||
|
OBJDIR := obj
|
||||||
|
|
||||||
|
# ---- Compiler flags ----
|
||||||
|
|
||||||
|
CFLAGS := \
|
||||||
|
-std=gnu11 \
|
||||||
|
-g -O2 -pipe \
|
||||||
|
-Wall \
|
||||||
|
-Wno-unused-parameter \
|
||||||
|
-Wno-unused-variable \
|
||||||
|
-Wno-missing-field-initializers \
|
||||||
|
-Wno-sign-compare \
|
||||||
|
-Wno-pointer-sign \
|
||||||
|
-nostdinc \
|
||||||
|
-ffreestanding \
|
||||||
|
-fno-stack-protector \
|
||||||
|
-fno-stack-check \
|
||||||
|
-fno-PIC \
|
||||||
|
-ffunction-sections \
|
||||||
|
-fdata-sections \
|
||||||
|
-m64 \
|
||||||
|
-march=x86-64 \
|
||||||
|
-msse \
|
||||||
|
-msse2 \
|
||||||
|
-mno-red-zone \
|
||||||
|
-mcmodel=small \
|
||||||
|
-DNORMALUNIX \
|
||||||
|
-DLINUX \
|
||||||
|
-isystem $(LIBC_INC) \
|
||||||
|
-I $(PROG_INC) \
|
||||||
|
-I $(DOOM_SRC) \
|
||||||
|
-isystem $(shell $(CC) -print-file-name=include)
|
||||||
|
|
||||||
|
# ---- Linker flags ----
|
||||||
|
|
||||||
|
LDFLAGS := \
|
||||||
|
-nostdlib \
|
||||||
|
-static \
|
||||||
|
-Wl,--build-id=none \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-Wl,-m,elf_x86_64 \
|
||||||
|
-z max-page-size=0x1000 \
|
||||||
|
-T $(LINK_LD)
|
||||||
|
|
||||||
|
# ---- DOOM source files (excluding platform-specific ports and sound backends) ----
|
||||||
|
|
||||||
|
DOOM_SRCS := \
|
||||||
|
am_map.c \
|
||||||
|
d_event.c \
|
||||||
|
d_items.c \
|
||||||
|
d_iwad.c \
|
||||||
|
d_loop.c \
|
||||||
|
d_main.c \
|
||||||
|
d_mode.c \
|
||||||
|
d_net.c \
|
||||||
|
doomdef.c \
|
||||||
|
doomgeneric.c \
|
||||||
|
doomstat.c \
|
||||||
|
dstrings.c \
|
||||||
|
dummy.c \
|
||||||
|
f_finale.c \
|
||||||
|
f_wipe.c \
|
||||||
|
g_game.c \
|
||||||
|
gusconf.c \
|
||||||
|
hu_lib.c \
|
||||||
|
hu_stuff.c \
|
||||||
|
i_cdmus.c \
|
||||||
|
i_endoom.c \
|
||||||
|
i_input.c \
|
||||||
|
i_joystick.c \
|
||||||
|
i_scale.c \
|
||||||
|
i_sound.c \
|
||||||
|
i_system.c \
|
||||||
|
i_timer.c \
|
||||||
|
i_video.c \
|
||||||
|
info.c \
|
||||||
|
m_argv.c \
|
||||||
|
m_bbox.c \
|
||||||
|
m_cheat.c \
|
||||||
|
m_config.c \
|
||||||
|
m_controls.c \
|
||||||
|
m_fixed.c \
|
||||||
|
m_menu.c \
|
||||||
|
m_misc.c \
|
||||||
|
m_random.c \
|
||||||
|
memio.c \
|
||||||
|
mus2mid.c \
|
||||||
|
p_ceilng.c \
|
||||||
|
p_doors.c \
|
||||||
|
p_enemy.c \
|
||||||
|
p_floor.c \
|
||||||
|
p_inter.c \
|
||||||
|
p_lights.c \
|
||||||
|
p_map.c \
|
||||||
|
p_maputl.c \
|
||||||
|
p_mobj.c \
|
||||||
|
p_plats.c \
|
||||||
|
p_pspr.c \
|
||||||
|
p_saveg.c \
|
||||||
|
p_setup.c \
|
||||||
|
p_sight.c \
|
||||||
|
p_spec.c \
|
||||||
|
p_switch.c \
|
||||||
|
p_telept.c \
|
||||||
|
p_tick.c \
|
||||||
|
p_user.c \
|
||||||
|
r_bsp.c \
|
||||||
|
r_data.c \
|
||||||
|
r_draw.c \
|
||||||
|
r_main.c \
|
||||||
|
r_plane.c \
|
||||||
|
r_segs.c \
|
||||||
|
r_sky.c \
|
||||||
|
r_things.c \
|
||||||
|
s_sound.c \
|
||||||
|
sha1.c \
|
||||||
|
sounds.c \
|
||||||
|
st_lib.c \
|
||||||
|
st_stuff.c \
|
||||||
|
statdump.c \
|
||||||
|
tables.c \
|
||||||
|
v_video.c \
|
||||||
|
w_checksum.c \
|
||||||
|
w_file.c \
|
||||||
|
w_file_stdc.c \
|
||||||
|
w_main.c \
|
||||||
|
w_wad.c \
|
||||||
|
wi_stuff.c \
|
||||||
|
z_zone.c
|
||||||
|
|
||||||
|
# Local source files
|
||||||
|
LOCAL_SRCS := doomgeneric_zenith.c libc.c
|
||||||
|
|
||||||
|
# ---- Object files ----
|
||||||
|
|
||||||
|
DOOM_OBJS := $(addprefix $(OBJDIR)/,$(DOOM_SRCS:.c=.o))
|
||||||
|
LOCAL_OBJS := $(addprefix $(OBJDIR)/,$(LOCAL_SRCS:.c=.o))
|
||||||
|
ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS)
|
||||||
|
|
||||||
|
# ---- Target ----
|
||||||
|
|
||||||
|
TARGET := $(BINDIR)/doom.elf
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
|
||||||
|
|
||||||
|
# DOOM source files (from doomgeneric directory)
|
||||||
|
$(OBJDIR)/%.o: $(DOOM_SRC)/%.c Makefile
|
||||||
|
mkdir -p $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
# Local source files
|
||||||
|
$(OBJDIR)/%.o: %.c Makefile
|
||||||
|
mkdir -p $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(OBJDIR) $(TARGET)
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* doomgeneric_zenith.c
|
||||||
|
* DOOM platform implementation for ZenithOS
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "doomgeneric.h"
|
||||||
|
#include "doomkeys.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* ---- Raw syscall interface (C versions) ---- */
|
||||||
|
|
||||||
|
static inline long _zos_syscall0(long nr) {
|
||||||
|
long ret;
|
||||||
|
__asm__ volatile("syscall" : "=a"(ret) : "a"(nr)
|
||||||
|
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline long _zos_syscall1(long nr, long a1) {
|
||||||
|
long 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syscall numbers (must match kernel/src/Api/Syscall.hpp) */
|
||||||
|
#define SYS_EXIT 0
|
||||||
|
#define SYS_SLEEP_MS 2
|
||||||
|
#define SYS_PRINT 4
|
||||||
|
#define SYS_GETMILLISECONDS 14
|
||||||
|
#define SYS_ISKEYAVAILABLE 16
|
||||||
|
#define SYS_GETKEY 17
|
||||||
|
#define SYS_FBINFO 21
|
||||||
|
#define SYS_FBMAP 22
|
||||||
|
|
||||||
|
/* FbInfo struct (must match kernel definition) */
|
||||||
|
struct FbInfo {
|
||||||
|
unsigned long width;
|
||||||
|
unsigned long height;
|
||||||
|
unsigned long pitch;
|
||||||
|
unsigned long bpp;
|
||||||
|
unsigned long userAddr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* KeyEvent struct (must match kernel definition) */
|
||||||
|
struct KeyEvent {
|
||||||
|
unsigned char scancode;
|
||||||
|
char ascii;
|
||||||
|
unsigned char pressed;
|
||||||
|
unsigned char shift;
|
||||||
|
unsigned char ctrl;
|
||||||
|
unsigned char alt;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ---- Framebuffer state ---- */
|
||||||
|
|
||||||
|
static uint32_t* g_fbPtr = 0;
|
||||||
|
static uint32_t g_fbWidth = 0;
|
||||||
|
static uint32_t g_fbHeight = 0;
|
||||||
|
static uint32_t g_fbPitch = 0; /* bytes per scanline */
|
||||||
|
|
||||||
|
/* ---- Circular key queue ---- */
|
||||||
|
|
||||||
|
#define KEY_QUEUE_SIZE 64
|
||||||
|
|
||||||
|
struct KeyQueueEntry {
|
||||||
|
int pressed;
|
||||||
|
unsigned char doomkey;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct KeyQueueEntry g_keyQueue[KEY_QUEUE_SIZE];
|
||||||
|
static int g_keyQueueRead = 0;
|
||||||
|
static int g_keyQueueWrite = 0;
|
||||||
|
|
||||||
|
static void key_queue_push(int pressed, unsigned char doomkey) {
|
||||||
|
int next = (g_keyQueueWrite + 1) % KEY_QUEUE_SIZE;
|
||||||
|
if (next == g_keyQueueRead) return; /* full, drop */
|
||||||
|
g_keyQueue[g_keyQueueWrite].pressed = pressed;
|
||||||
|
g_keyQueue[g_keyQueueWrite].doomkey = doomkey;
|
||||||
|
g_keyQueueWrite = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int key_queue_pop(int* pressed, unsigned char* doomkey) {
|
||||||
|
if (g_keyQueueRead == g_keyQueueWrite) return 0;
|
||||||
|
*pressed = g_keyQueue[g_keyQueueRead].pressed;
|
||||||
|
*doomkey = g_keyQueue[g_keyQueueRead].doomkey;
|
||||||
|
g_keyQueueRead = (g_keyQueueRead + 1) % KEY_QUEUE_SIZE;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- PS/2 scancode to ASCII table (set 1, unshifted) ---- */
|
||||||
|
|
||||||
|
static const char scancode_to_ascii[128] = {
|
||||||
|
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=','\b',
|
||||||
|
'\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',
|
||||||
|
0, 'a','s','d','f','g','h','j','k','l',';','\'','`',
|
||||||
|
0, '\\','z','x','c','v','b','n','m',',','.','/', 0,
|
||||||
|
'*', 0, ' '
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ---- PS/2 scancode to DOOM key mapping ---- */
|
||||||
|
|
||||||
|
static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) {
|
||||||
|
switch (scancode) {
|
||||||
|
case 0x48: return KEY_UPARROW;
|
||||||
|
case 0x50: return KEY_DOWNARROW;
|
||||||
|
case 0x4B: return KEY_LEFTARROW;
|
||||||
|
case 0x4D: return KEY_RIGHTARROW;
|
||||||
|
case 0x1C: return KEY_ENTER;
|
||||||
|
case 0x01: return KEY_ESCAPE;
|
||||||
|
case 0x39: return ' '; /* Space = use */
|
||||||
|
case 0x1D: return KEY_RCTRL; /* LCtrl = fire */
|
||||||
|
case 0x2A: return KEY_RSHIFT; /* LShift = run */
|
||||||
|
case 0x36: return KEY_RSHIFT; /* RShift = run */
|
||||||
|
case 0x38: return KEY_RALT; /* Alt = strafe */
|
||||||
|
case 0x0E: return KEY_BACKSPACE;
|
||||||
|
case 0x0F: return KEY_TAB;
|
||||||
|
/* F1-F10 */
|
||||||
|
case 0x3B: return KEY_F1;
|
||||||
|
case 0x3C: return KEY_F2;
|
||||||
|
case 0x3D: return KEY_F3;
|
||||||
|
case 0x3E: return KEY_F4;
|
||||||
|
case 0x3F: return KEY_F5;
|
||||||
|
case 0x40: return KEY_F6;
|
||||||
|
case 0x41: return KEY_F7;
|
||||||
|
case 0x42: return KEY_F8;
|
||||||
|
case 0x43: return KEY_F9;
|
||||||
|
case 0x44: return KEY_F10;
|
||||||
|
case 0x57: return KEY_F11;
|
||||||
|
case 0x58: return KEY_F12;
|
||||||
|
/* Equals and minus for screen size */
|
||||||
|
case 0x0D: return KEY_EQUALS;
|
||||||
|
case 0x0C: return KEY_MINUS;
|
||||||
|
default:
|
||||||
|
/* Pass through printable ASCII as lowercase */
|
||||||
|
if (ascii >= 'a' && ascii <= 'z') return (unsigned char)ascii;
|
||||||
|
if (ascii >= '0' && ascii <= '9') return (unsigned char)ascii;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Poll keyboard and enqueue events ---- */
|
||||||
|
|
||||||
|
static void poll_keyboard(void) {
|
||||||
|
while (_zos_syscall0(SYS_ISKEYAVAILABLE)) {
|
||||||
|
struct KeyEvent evt;
|
||||||
|
_zos_syscall1(SYS_GETKEY, (long)&evt);
|
||||||
|
|
||||||
|
unsigned char baseSc = evt.scancode & 0x7F; /* strip break bit */
|
||||||
|
|
||||||
|
char ascii = 0;
|
||||||
|
if (baseSc < 128)
|
||||||
|
ascii = scancode_to_ascii[baseSc];
|
||||||
|
|
||||||
|
unsigned char dk = scancode_to_doomkey(baseSc, ascii);
|
||||||
|
if (dk != 0) {
|
||||||
|
key_queue_push(evt.pressed ? 1 : 0, dk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- DG platform functions ---- */
|
||||||
|
|
||||||
|
void DG_Init(void) {
|
||||||
|
struct FbInfo info;
|
||||||
|
_zos_syscall1(SYS_FBINFO, (long)&info);
|
||||||
|
|
||||||
|
g_fbWidth = (uint32_t)info.width;
|
||||||
|
g_fbHeight = (uint32_t)info.height;
|
||||||
|
g_fbPitch = (uint32_t)info.pitch;
|
||||||
|
|
||||||
|
g_fbPtr = (uint32_t*)(unsigned long)_zos_syscall0(SYS_FBMAP);
|
||||||
|
|
||||||
|
printf("DOOM: framebuffer %ux%u pitch=%u mapped at %p\n",
|
||||||
|
g_fbWidth, g_fbHeight, g_fbPitch, (void*)g_fbPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DG_DrawFrame(void) {
|
||||||
|
/* Poll keyboard first */
|
||||||
|
poll_keyboard();
|
||||||
|
|
||||||
|
/* Copy DG_ScreenBuffer (DOOMGENERIC_RESX x DOOMGENERIC_RESY) to framebuffer */
|
||||||
|
if (g_fbPtr == 0 || DG_ScreenBuffer == 0) return;
|
||||||
|
|
||||||
|
uint32_t copyW = DOOMGENERIC_RESX;
|
||||||
|
uint32_t copyH = DOOMGENERIC_RESY;
|
||||||
|
if (copyW > g_fbWidth) copyW = g_fbWidth;
|
||||||
|
if (copyH > g_fbHeight) copyH = g_fbHeight;
|
||||||
|
|
||||||
|
uint32_t fbStride = g_fbPitch / 4; /* pixels per scanline */
|
||||||
|
|
||||||
|
for (uint32_t y = 0; y < copyH; y++) {
|
||||||
|
uint32_t* dst = g_fbPtr + y * fbStride;
|
||||||
|
uint32_t* src = DG_ScreenBuffer + y * DOOMGENERIC_RESX;
|
||||||
|
memcpy(dst, src, copyW * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DG_SleepMs(uint32_t ms) {
|
||||||
|
_zos_syscall1(SYS_SLEEP_MS, (long)ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t DG_GetTicksMs(void) {
|
||||||
|
return (uint32_t)_zos_syscall0(SYS_GETMILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DG_GetKey(int* pressed, unsigned char* doomKey) {
|
||||||
|
return key_queue_pop(pressed, doomKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DG_SetWindowTitle(const char* title) {
|
||||||
|
(void)title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Entry point ---- */
|
||||||
|
|
||||||
|
void _start(void) {
|
||||||
|
char *argv[] = { "doom", "-iwad", "0:/doom1.wad", 0 };
|
||||||
|
doomgeneric_Create(3, argv);
|
||||||
|
for (;;) {
|
||||||
|
doomgeneric_Tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,374 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
* Manual page viewer for ZenithOS
|
||||||
|
* Fullscreen pager with ANSI formatting and keyboard navigation
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zenith/syscall.h>
|
||||||
|
#include <zenith/heap.h>
|
||||||
|
|
||||||
|
// ---- Utility functions ----
|
||||||
|
|
||||||
|
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 int slen(const char* s) {
|
||||||
|
int n = 0;
|
||||||
|
while (s[n]) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Pager rendering ----
|
||||||
|
|
||||||
|
static constexpr int MAN_MAX_LINES = 2048;
|
||||||
|
|
||||||
|
static int int_to_buf(char* buf, int n) {
|
||||||
|
if (n == 0) { buf[0] = '0'; return 1; }
|
||||||
|
char tmp[12];
|
||||||
|
int i = 0;
|
||||||
|
while (n > 0) { tmp[i++] = '0' + (n % 10); n /= 10; }
|
||||||
|
for (int j = 0; j < i; j++) buf[j] = tmp[i - 1 - j];
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cursor_to(int row, int col) {
|
||||||
|
char seq[24] = "\033[";
|
||||||
|
int p = 2;
|
||||||
|
p += int_to_buf(seq + p, row);
|
||||||
|
seq[p++] = ';';
|
||||||
|
p += int_to_buf(seq + p, col);
|
||||||
|
seq[p++] = 'H';
|
||||||
|
seq[p] = '\0';
|
||||||
|
zenith::print(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ManLine {
|
||||||
|
const char* text;
|
||||||
|
int len;
|
||||||
|
bool isSH;
|
||||||
|
bool isSS;
|
||||||
|
bool isBold;
|
||||||
|
bool isTH;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void man_render(ManLine* lines, int totalLines, int scroll, int rows, int cols,
|
||||||
|
const char* name, int section) {
|
||||||
|
int contentRows = rows - 1;
|
||||||
|
|
||||||
|
for (int r = 0; r < contentRows; r++) {
|
||||||
|
cursor_to(r + 1, 1);
|
||||||
|
zenith::print("\033[2K");
|
||||||
|
|
||||||
|
int idx = scroll + r;
|
||||||
|
if (idx < 0 || idx >= totalLines) continue;
|
||||||
|
|
||||||
|
ManLine& ln = lines[idx];
|
||||||
|
if (ln.isTH) continue;
|
||||||
|
|
||||||
|
if (ln.isSH || ln.isSS || ln.isBold) {
|
||||||
|
zenith::print("\033[1m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ln.isSS) {
|
||||||
|
zenith::print(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxW = cols;
|
||||||
|
if (ln.isSS) maxW -= 3;
|
||||||
|
int printLen = ln.len;
|
||||||
|
if (printLen > maxW) printLen = maxW;
|
||||||
|
for (int c = 0; c < printLen; c++) {
|
||||||
|
zenith::putchar(ln.text[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ln.isSH || ln.isSS || ln.isBold) {
|
||||||
|
zenith::print("\033[0m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status bar
|
||||||
|
cursor_to(rows, 1);
|
||||||
|
zenith::print("\033[7m");
|
||||||
|
zenith::print(" Manual page ");
|
||||||
|
zenith::print(name);
|
||||||
|
zenith::putchar('(');
|
||||||
|
print_int((uint64_t)section);
|
||||||
|
zenith::putchar(')');
|
||||||
|
zenith::print(" line ");
|
||||||
|
print_int((uint64_t)(scroll + 1));
|
||||||
|
zenith::putchar('/');
|
||||||
|
print_int((uint64_t)totalLines);
|
||||||
|
|
||||||
|
int padCount = cols - 30 - slen(name);
|
||||||
|
for (int i = 0; i < padCount; i++) zenith::putchar(' ');
|
||||||
|
|
||||||
|
zenith::print("\033[0m");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Main ----
|
||||||
|
|
||||||
|
extern "C" void _start() {
|
||||||
|
// Get arguments passed by the shell (e.g. "intro" or "2 syscalls")
|
||||||
|
char argbuf[256];
|
||||||
|
zenith::getargs(argbuf, sizeof(argbuf));
|
||||||
|
|
||||||
|
const char* arg = skip_spaces(argbuf);
|
||||||
|
if (*arg == '\0') {
|
||||||
|
zenith::print("Usage: man <topic>\n");
|
||||||
|
zenith::print(" man <section> <topic>\n");
|
||||||
|
zenith::print("Try: man intro\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse optional section number and topic name
|
||||||
|
int section = 0;
|
||||||
|
const char* topic = arg;
|
||||||
|
|
||||||
|
if (arg[0] >= '1' && arg[0] <= '9' && arg[1] == ' ') {
|
||||||
|
section = arg[0] - '0';
|
||||||
|
topic = skip_spaces(arg + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to open man page file
|
||||||
|
int handle = -1;
|
||||||
|
int foundSection = 0;
|
||||||
|
char path[128];
|
||||||
|
|
||||||
|
if (section > 0) {
|
||||||
|
const char* prefix = "0:/man/";
|
||||||
|
int p = 0;
|
||||||
|
while (prefix[p]) { path[p] = prefix[p]; p++; }
|
||||||
|
int t = 0;
|
||||||
|
while (topic[t] && p < 120) { path[p++] = topic[t++]; }
|
||||||
|
path[p++] = '.';
|
||||||
|
path[p++] = '0' + section;
|
||||||
|
path[p] = '\0';
|
||||||
|
|
||||||
|
handle = zenith::open(path);
|
||||||
|
if (handle >= 0) foundSection = section;
|
||||||
|
} else {
|
||||||
|
for (int s = 1; s <= 3; s++) {
|
||||||
|
const char* prefix = "0:/man/";
|
||||||
|
int p = 0;
|
||||||
|
while (prefix[p]) { path[p] = prefix[p]; p++; }
|
||||||
|
int t = 0;
|
||||||
|
while (topic[t] && p < 120) { path[p++] = topic[t++]; }
|
||||||
|
path[p++] = '.';
|
||||||
|
path[p++] = '0' + s;
|
||||||
|
path[p] = '\0';
|
||||||
|
|
||||||
|
handle = zenith::open(path);
|
||||||
|
if (handle >= 0) {
|
||||||
|
foundSection = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle < 0) {
|
||||||
|
zenith::print("No manual entry for ");
|
||||||
|
zenith::print(topic);
|
||||||
|
zenith::putchar('\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load entire file into heap
|
||||||
|
uint64_t fileSize = zenith::getsize(handle);
|
||||||
|
if (fileSize == 0) {
|
||||||
|
zenith::close(handle);
|
||||||
|
zenith::print("Empty manual page.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* fileData = (char*)zenith::malloc(fileSize + 1);
|
||||||
|
if (fileData == nullptr) {
|
||||||
|
zenith::close(handle);
|
||||||
|
zenith::print("Out of memory.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t offset = 0;
|
||||||
|
while (offset < fileSize) {
|
||||||
|
uint64_t chunk = fileSize - offset;
|
||||||
|
if (chunk > 4096) chunk = 4096;
|
||||||
|
int bytesRead = zenith::read(handle, (uint8_t*)fileData + offset, offset, chunk);
|
||||||
|
if (bytesRead <= 0) break;
|
||||||
|
offset += bytesRead;
|
||||||
|
}
|
||||||
|
fileData[offset] = '\0';
|
||||||
|
zenith::close(handle);
|
||||||
|
|
||||||
|
// Parse into lines
|
||||||
|
ManLine* lines = (ManLine*)zenith::malloc(MAN_MAX_LINES * sizeof(ManLine));
|
||||||
|
if (lines == nullptr) {
|
||||||
|
zenith::mfree(fileData);
|
||||||
|
zenith::print("Out of memory.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalLines = 0;
|
||||||
|
const char* p = fileData;
|
||||||
|
while (*p && totalLines < MAN_MAX_LINES) {
|
||||||
|
const char* lineStart = p;
|
||||||
|
while (*p && *p != '\n') p++;
|
||||||
|
int lineLen = (int)(p - lineStart);
|
||||||
|
if (*p == '\n') p++;
|
||||||
|
|
||||||
|
ManLine& ln = lines[totalLines];
|
||||||
|
ln.isSH = false;
|
||||||
|
ln.isSS = false;
|
||||||
|
ln.isBold = false;
|
||||||
|
ln.isTH = false;
|
||||||
|
|
||||||
|
if (starts_with(lineStart, ".TH ")) {
|
||||||
|
ln.isTH = true;
|
||||||
|
ln.text = lineStart + 4;
|
||||||
|
ln.len = lineLen - 4;
|
||||||
|
} else if (starts_with(lineStart, ".SH ")) {
|
||||||
|
ln.isSH = true;
|
||||||
|
ln.text = lineStart + 4;
|
||||||
|
ln.len = lineLen - 4;
|
||||||
|
} else if (starts_with(lineStart, ".SS ")) {
|
||||||
|
ln.isSS = true;
|
||||||
|
ln.text = lineStart + 4;
|
||||||
|
ln.len = lineLen - 4;
|
||||||
|
} else if (starts_with(lineStart, ".B ")) {
|
||||||
|
ln.isBold = true;
|
||||||
|
ln.text = lineStart + 3;
|
||||||
|
ln.len = lineLen - 3;
|
||||||
|
} else if (starts_with(lineStart, ".BI ")) {
|
||||||
|
ln.isBold = true;
|
||||||
|
ln.text = lineStart + 4;
|
||||||
|
ln.len = lineLen - 4;
|
||||||
|
} else {
|
||||||
|
ln.text = lineStart;
|
||||||
|
ln.len = lineLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalLines == 0) {
|
||||||
|
zenith::mfree(lines);
|
||||||
|
zenith::mfree(fileData);
|
||||||
|
zenith::print("Empty manual page.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get terminal dimensions
|
||||||
|
int cols = 80, rows = 25;
|
||||||
|
zenith::termsize(&cols, &rows);
|
||||||
|
|
||||||
|
// Enter alternate screen, hide cursor
|
||||||
|
zenith::print("\033[?1049h");
|
||||||
|
zenith::print("\033[?25l");
|
||||||
|
|
||||||
|
int scroll = 0;
|
||||||
|
int maxScroll = totalLines - (rows - 1);
|
||||||
|
if (maxScroll < 0) maxScroll = 0;
|
||||||
|
|
||||||
|
man_render(lines, totalLines, scroll, rows, cols, topic, foundSection);
|
||||||
|
|
||||||
|
// Event loop — yield while waiting for key input
|
||||||
|
bool running = true;
|
||||||
|
while (running) {
|
||||||
|
while (!zenith::is_key_available()) {
|
||||||
|
zenith::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
Zenith::KeyEvent ev;
|
||||||
|
zenith::getkey(&ev);
|
||||||
|
if (!ev.pressed) continue;
|
||||||
|
|
||||||
|
int contentRows = rows - 1;
|
||||||
|
|
||||||
|
switch (ev.ascii) {
|
||||||
|
case 'q':
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
if (scroll < maxScroll) scroll++;
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
if (scroll > 0) scroll--;
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
scroll += contentRows;
|
||||||
|
if (scroll > maxScroll) scroll = maxScroll;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
scroll -= contentRows;
|
||||||
|
if (scroll < 0) scroll = 0;
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
scroll = 0;
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
scroll = maxScroll;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Handle scancodes for special keys
|
||||||
|
switch (ev.scancode) {
|
||||||
|
case 0x48: // Up arrow
|
||||||
|
if (scroll > 0) scroll--;
|
||||||
|
break;
|
||||||
|
case 0x50: // Down arrow
|
||||||
|
if (scroll < maxScroll) scroll++;
|
||||||
|
break;
|
||||||
|
case 0x49: // Page Up
|
||||||
|
scroll -= contentRows;
|
||||||
|
if (scroll < 0) scroll = 0;
|
||||||
|
break;
|
||||||
|
case 0x51: // Page Down
|
||||||
|
scroll += contentRows;
|
||||||
|
if (scroll > maxScroll) scroll = maxScroll;
|
||||||
|
break;
|
||||||
|
case 0x47: // Home
|
||||||
|
scroll = 0;
|
||||||
|
break;
|
||||||
|
case 0x4F: // End
|
||||||
|
scroll = maxScroll;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
man_render(lines, totalLines, scroll, rows, cols, topic, foundSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore screen
|
||||||
|
zenith::print("\033[?25h");
|
||||||
|
zenith::print("\033[?1049l");
|
||||||
|
|
||||||
|
zenith::mfree(lines);
|
||||||
|
zenith::mfree(fileData);
|
||||||
|
}
|
||||||
@@ -44,15 +44,17 @@ static void print_int(uint64_t n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void prompt() {
|
static void prompt() {
|
||||||
zenith::print("zenith> ");
|
zenith::print("% ");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_help() {
|
static void cmd_help() {
|
||||||
zenith::print("Available commands:\n");
|
zenith::print("Available commands:\n");
|
||||||
zenith::print(" help Show this help message\n");
|
zenith::print(" help Show this help message\n");
|
||||||
zenith::print(" info Show system information\n");
|
zenith::print(" info Show system information\n");
|
||||||
|
zenith::print(" man <topic> View manual pages\n");
|
||||||
zenith::print(" ls List ramdisk files\n");
|
zenith::print(" ls List ramdisk files\n");
|
||||||
zenith::print(" cat <file> Display file contents\n");
|
zenith::print(" cat <file> Display file contents\n");
|
||||||
|
zenith::print(" run <file> Spawn a new process from an ELF file\n");
|
||||||
zenith::print(" ping <ip> Send ICMP echo requests\n");
|
zenith::print(" ping <ip> Send ICMP echo requests\n");
|
||||||
zenith::print(" uptime Show uptime in milliseconds\n");
|
zenith::print(" uptime Show uptime in milliseconds\n");
|
||||||
zenith::print(" clear Clear the screen\n");
|
zenith::print(" clear Clear the screen\n");
|
||||||
@@ -223,11 +225,52 @@ static void cmd_ping(const char* arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_clear() {
|
static void cmd_run(const char* arg) {
|
||||||
// Print enough newlines to scroll past visible content
|
arg = skip_spaces(arg);
|
||||||
for (int i = 0; i < 50; i++) {
|
if (*arg == '\0') {
|
||||||
zenith::putchar('\n');
|
zenith::print("Usage: run <filename>\n");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build path "0:/<filename>"
|
||||||
|
char path[128];
|
||||||
|
const char* prefix = "0:/";
|
||||||
|
int i = 0;
|
||||||
|
while (prefix[i]) { path[i] = prefix[i]; i++; }
|
||||||
|
int j = 0;
|
||||||
|
while (arg[j] && i < 126) { path[i++] = arg[j++]; }
|
||||||
|
path[i] = '\0';
|
||||||
|
|
||||||
|
int pid = zenith::spawn(path);
|
||||||
|
if (pid < 0) {
|
||||||
|
zenith::print("Error: failed to spawn '");
|
||||||
|
zenith::print(arg);
|
||||||
|
zenith::print("'\n");
|
||||||
|
} else {
|
||||||
|
zenith::waitpid(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_man(const char* arg) {
|
||||||
|
arg = skip_spaces(arg);
|
||||||
|
if (*arg == '\0') {
|
||||||
|
zenith::print("Usage: man <topic>\n");
|
||||||
|
zenith::print(" man <section> <topic>\n");
|
||||||
|
zenith::print("Try: man intro\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pid = zenith::spawn("0:/man.elf", arg);
|
||||||
|
if (pid < 0) {
|
||||||
|
zenith::print("Error: failed to start man viewer\n");
|
||||||
|
} else {
|
||||||
|
zenith::waitpid(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_clear() {
|
||||||
|
zenith::print("\033[2J"); // Clear entire screen
|
||||||
|
zenith::print("\033[H"); // Move cursor to top-left
|
||||||
}
|
}
|
||||||
|
|
||||||
static void process_command(const char* line) {
|
static void process_command(const char* line) {
|
||||||
@@ -241,10 +284,18 @@ static void process_command(const char* line) {
|
|||||||
cmd_info();
|
cmd_info();
|
||||||
} else if (streq(line, "ls")) {
|
} else if (streq(line, "ls")) {
|
||||||
cmd_ls();
|
cmd_ls();
|
||||||
|
} else if (starts_with(line, "man ")) {
|
||||||
|
cmd_man(line + 4);
|
||||||
|
} else if (streq(line, "man")) {
|
||||||
|
cmd_man("");
|
||||||
} else if (starts_with(line, "cat ")) {
|
} else if (starts_with(line, "cat ")) {
|
||||||
cmd_cat(line + 4);
|
cmd_cat(line + 4);
|
||||||
} else if (streq(line, "cat")) {
|
} else if (streq(line, "cat")) {
|
||||||
cmd_cat("");
|
cmd_cat("");
|
||||||
|
} else if (starts_with(line, "run ")) {
|
||||||
|
cmd_run(line + 4);
|
||||||
|
} else if (streq(line, "run")) {
|
||||||
|
cmd_run("");
|
||||||
} else if (starts_with(line, "ping ")) {
|
} else if (starts_with(line, "ping ")) {
|
||||||
cmd_ping(line + 5);
|
cmd_ping(line + 5);
|
||||||
} else if (streq(line, "ping")) {
|
} else if (streq(line, "ping")) {
|
||||||
@@ -265,7 +316,10 @@ static void process_command(const char* line) {
|
|||||||
|
|
||||||
extern "C" void _start() {
|
extern "C" void _start() {
|
||||||
zenith::print("\n");
|
zenith::print("\n");
|
||||||
zenith::print(" ZenithOS Shell v0.1\n");
|
zenith::print(" ZenithOS\n");
|
||||||
|
zenith::print(" Copyright (c) 2025-2026 Daniel Hammer\n");
|
||||||
|
zenith::print("\n");
|
||||||
|
|
||||||
zenith::print(" Type 'help' for available commands.\n");
|
zenith::print(" Type 'help' for available commands.\n");
|
||||||
zenith::print("\n");
|
zenith::print("\n");
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user