feat: expand user mode, add DOOM game, add manpages
This commit is contained in:
@@ -7,3 +7,6 @@ qemu.log
|
||||
toolchain/local/
|
||||
toolchain/build/
|
||||
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 <Hal/MSR.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
#include "../Libraries/flanterm/src/flanterm.h"
|
||||
|
||||
// Assembly entry point
|
||||
extern "C" void SyscallEntry();
|
||||
@@ -175,6 +177,47 @@ namespace Zenith {
|
||||
static uint16_t g_pingSeq = 0;
|
||||
static constexpr uint16_t PING_ID = 0x2E01; // "ZE"
|
||||
|
||||
static void Sys_FbInfo(FbInfo* out) {
|
||||
if (out == nullptr) return;
|
||||
out->width = Graphics::Cursor::GetFramebufferWidth();
|
||||
out->height = Graphics::Cursor::GetFramebufferHeight();
|
||||
out->pitch = Graphics::Cursor::GetFramebufferPitch();
|
||||
out->bpp = 32;
|
||||
out->userAddr = 0;
|
||||
}
|
||||
|
||||
static void Sys_WaitPid(int pid) {
|
||||
while (Sched::IsAlive(pid)) {
|
||||
Sched::Schedule(); // yield until the process exits
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t Sys_FbMap() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return 0;
|
||||
|
||||
uint32_t* fbBase = Graphics::Cursor::GetFramebufferBase();
|
||||
if (fbBase == nullptr) return 0;
|
||||
|
||||
uint64_t fbPhys = Memory::SubHHDM((uint64_t)fbBase);
|
||||
uint64_t fbSize = Graphics::Cursor::GetFramebufferHeight()
|
||||
* Graphics::Cursor::GetFramebufferPitch();
|
||||
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
|
||||
|
||||
// Map at a fixed user VA
|
||||
constexpr uint64_t userVa = 0x50000000ULL;
|
||||
|
||||
for (uint64_t i = 0; i < numPages; i++) {
|
||||
Memory::VMM::Paging::MapUserIn(
|
||||
proc->pml4Phys,
|
||||
fbPhys + i * 0x1000,
|
||||
userVa + i * 0x1000
|
||||
);
|
||||
}
|
||||
|
||||
return userVa;
|
||||
}
|
||||
|
||||
static int32_t Sys_Ping(uint32_t ipAddr, uint32_t timeoutMs) {
|
||||
uint16_t seq = g_pingSeq++;
|
||||
|
||||
@@ -192,6 +235,27 @@ namespace Zenith {
|
||||
return (int32_t)(Timekeeping::GetMilliseconds() - start);
|
||||
}
|
||||
|
||||
static int Sys_Spawn(const char* path, const char* args) {
|
||||
return Sched::Spawn(path, args);
|
||||
}
|
||||
|
||||
static int Sys_GetArgs(char* buf, uint64_t maxLen) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
||||
int i = 0;
|
||||
for (; i < (int)maxLen - 1 && proc->args[i]; i++) {
|
||||
buf[i] = proc->args[i];
|
||||
}
|
||||
buf[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
||||
static uint64_t Sys_TermSize() {
|
||||
size_t cols = 0, rows = 0;
|
||||
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
|
||||
return (rows << 32) | (cols & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
// ---- Dispatch ----
|
||||
|
||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||
@@ -248,6 +312,20 @@ namespace Zenith {
|
||||
return (int64_t)Sys_GetChar();
|
||||
case SYS_PING:
|
||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
||||
case SYS_SPAWN:
|
||||
return (int64_t)Sys_Spawn((const char*)frame->arg1, (const char*)frame->arg2);
|
||||
case SYS_WAITPID:
|
||||
Sys_WaitPid((int)frame->arg1);
|
||||
return 0;
|
||||
case SYS_FBINFO:
|
||||
Sys_FbInfo((FbInfo*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_FBMAP:
|
||||
return (int64_t)Sys_FbMap();
|
||||
case SYS_TERMSIZE:
|
||||
return (int64_t)Sys_TermSize();
|
||||
case SYS_GETARGS:
|
||||
return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -274,7 +352,7 @@ namespace Zenith {
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 20 syscalls)";
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 26 syscalls)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,20 @@ namespace Zenith {
|
||||
static constexpr uint64_t SYS_GETKEY = 17;
|
||||
static constexpr uint64_t SYS_GETCHAR = 18;
|
||||
static constexpr uint64_t SYS_PING = 19;
|
||||
static constexpr uint64_t SYS_SPAWN = 20;
|
||||
static constexpr uint64_t SYS_FBINFO = 21;
|
||||
static constexpr uint64_t SYS_FBMAP = 22;
|
||||
static constexpr uint64_t SYS_WAITPID = 23;
|
||||
static constexpr uint64_t SYS_TERMSIZE = 24;
|
||||
static constexpr uint64_t SYS_GETARGS = 25;
|
||||
|
||||
struct FbInfo {
|
||||
uint64_t width;
|
||||
uint64_t height;
|
||||
uint64_t pitch; // bytes per scanline
|
||||
uint64_t bpp; // bits per pixel (32)
|
||||
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
||||
};
|
||||
|
||||
struct SysInfo {
|
||||
char osName[32];
|
||||
|
||||
@@ -176,39 +176,66 @@ namespace Drivers::PS2::Keyboard {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle modifier keys
|
||||
// Handle modifier keys (update state, but still push event to buffer)
|
||||
bool isModifier = false;
|
||||
switch (keycode) {
|
||||
case ScLeftShift:
|
||||
g_Modifiers.LeftShift = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScRightShift:
|
||||
g_Modifiers.RightShift = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScLeftCtrl:
|
||||
g_Modifiers.LeftCtrl = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScLeftAlt:
|
||||
g_Modifiers.LeftAlt = !released;
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScCapsLock:
|
||||
if (!released) {
|
||||
g_Modifiers.CapsLock = !g_Modifiers.CapsLock;
|
||||
}
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScNumLock:
|
||||
if (!released) {
|
||||
g_Modifiers.NumLock = !g_Modifiers.NumLock;
|
||||
}
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
case ScScrollLock:
|
||||
if (!released) {
|
||||
g_Modifiers.ScrollLock = !g_Modifiers.ScrollLock;
|
||||
}
|
||||
return;
|
||||
isModifier = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Modifiers still need events in the buffer (for apps like doom)
|
||||
// but lock keys (caps/num/scroll) don't need buffer events
|
||||
if (isModifier && keycode != ScCapsLock && keycode != ScNumLock && keycode != ScScrollLock) {
|
||||
KeyEvent event = {
|
||||
.Scancode = scancode,
|
||||
.Ascii = 0,
|
||||
.Pressed = !released,
|
||||
.Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift,
|
||||
.Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl,
|
||||
.Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt,
|
||||
.CapsLock = g_Modifiers.CapsLock
|
||||
};
|
||||
g_BufferLock.Acquire();
|
||||
BufferPush(event);
|
||||
g_BufferLock.Release();
|
||||
return;
|
||||
}
|
||||
if (isModifier) return;
|
||||
|
||||
// Translate scancode to ASCII
|
||||
char ascii = 0;
|
||||
if (keycode < 128) {
|
||||
|
||||
@@ -162,4 +162,9 @@ namespace Graphics::Cursor {
|
||||
g_OldY = newY;
|
||||
}
|
||||
|
||||
uint32_t* GetFramebufferBase() { return g_FbBase; }
|
||||
uint64_t GetFramebufferWidth() { return g_FbWidth; }
|
||||
uint64_t GetFramebufferHeight() { return g_FbHeight; }
|
||||
uint64_t GetFramebufferPitch() { return g_FbPitch; }
|
||||
|
||||
};
|
||||
|
||||
@@ -13,4 +13,9 @@ namespace Graphics::Cursor {
|
||||
void Initialize(limine_framebuffer* framebuffer);
|
||||
void Update();
|
||||
|
||||
uint32_t* GetFramebufferBase();
|
||||
uint64_t GetFramebufferWidth();
|
||||
uint64_t GetFramebufferHeight();
|
||||
uint64_t GetFramebufferPitch();
|
||||
|
||||
};
|
||||
|
||||
@@ -93,6 +93,23 @@ extern "C" void kmain() {
|
||||
#if defined (__x86_64__)
|
||||
Hal::PrepareGDT();
|
||||
Hal::BridgeLoadGDT();
|
||||
|
||||
// Enable SSE/SSE2 — required for userspace programs compiled with SSE
|
||||
// CR0: clear EM (bit 2), set MP (bit 1)
|
||||
{
|
||||
uint64_t cr0;
|
||||
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||
cr0 &= ~(1ULL << 2); // Clear EM
|
||||
cr0 |= (1ULL << 1); // Set MP
|
||||
asm volatile("mov %0, %%cr0" :: "r"(cr0));
|
||||
|
||||
// CR4: set OSFXSR (bit 9) and OSXMMEXCPT (bit 10)
|
||||
uint64_t cr4;
|
||||
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
cr4 |= (1ULL << 9); // OSFXSR
|
||||
cr4 |= (1ULL << 10); // OSXMMEXCPT
|
||||
asm volatile("mov %0, %%cr4" :: "r"(cr4));
|
||||
}
|
||||
#endif
|
||||
|
||||
uint64_t hhdm_offset = hhdm_request.response->offset;
|
||||
|
||||
@@ -77,12 +77,17 @@ namespace Memory {
|
||||
};
|
||||
}
|
||||
|
||||
// Allocate() returns pages from the top of a free region in descending
|
||||
// order, so 'first' is the highest address. The contiguous block
|
||||
// actually starts (n-1) pages below 'first'.
|
||||
void* base = (void*)((uint64_t)first - (uint64_t)(n - 1) * 0x1000);
|
||||
|
||||
if (ptr != nullptr) {
|
||||
memcpy(first, ptr, n);
|
||||
memcpy(base, ptr, (uint64_t)n * 0x1000);
|
||||
Free(ptr);
|
||||
}
|
||||
|
||||
return first;
|
||||
return base;
|
||||
}
|
||||
|
||||
void PageFrameAllocator::Free(void* ptr) {
|
||||
|
||||
@@ -52,8 +52,6 @@ namespace Sched {
|
||||
}
|
||||
|
||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "Loading " << vfsPath;
|
||||
|
||||
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
||||
if (handle < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
||||
@@ -78,6 +76,10 @@ namespace Sched {
|
||||
Fs::Vfs::VfsRead(handle, fileData, 0, fileSize);
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
|
||||
// Prevent the optimizer from reordering the VfsRead store past the
|
||||
// header validation reads that follow.
|
||||
asm volatile("" ::: "memory");
|
||||
|
||||
// Validate ELF header
|
||||
Elf64Header* hdr = (Elf64Header*)fileData;
|
||||
if (!ValidateElfHeader(hdr)) {
|
||||
@@ -85,9 +87,6 @@ namespace Sched {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Entry point: " << kcp::hex << hdr->e_entry << kcp::dec
|
||||
<< ", " << (uint64_t)hdr->e_phnum << " program header(s)";
|
||||
|
||||
// Process program headers
|
||||
for (uint16_t i = 0; i < hdr->e_phnum; i++) {
|
||||
Elf64ProgramHeader* phdr = (Elf64ProgramHeader*)(fileData + hdr->e_phoff + i * hdr->e_phentsize);
|
||||
@@ -100,9 +99,6 @@ namespace Sched {
|
||||
continue;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "PT_LOAD: vaddr=" << kcp::hex << phdr->p_vaddr
|
||||
<< " filesz=" << phdr->p_filesz << " memsz=" << phdr->p_memsz << kcp::dec;
|
||||
|
||||
// Allocate pages and map them in the process PML4 with User bit
|
||||
uint64_t segBase = phdr->p_vaddr & ~0xFFFULL;
|
||||
uint64_t segEnd = (phdr->p_vaddr + phdr->p_memsz + 0xFFF) & ~0xFFFULL;
|
||||
@@ -147,7 +143,6 @@ namespace Sched {
|
||||
uint64_t entryPoint = hdr->e_entry;
|
||||
Memory::g_heap->Free(fileData);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Loaded successfully, entry=" << kcp::hex << entryPoint << kcp::dec;
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace Sched {
|
||||
processTable[i].kernelStackTop = 0;
|
||||
processTable[i].userStackTop = 0;
|
||||
processTable[i].heapNext = 0;
|
||||
processTable[i].args[0] = '\0';
|
||||
}
|
||||
|
||||
currentPid = -1;
|
||||
@@ -85,7 +86,7 @@ namespace Sched {
|
||||
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
||||
}
|
||||
|
||||
void Spawn(const char* vfsPath) {
|
||||
int Spawn(const char* vfsPath, const char* args) {
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Free) {
|
||||
@@ -96,7 +97,7 @@ namespace Sched {
|
||||
|
||||
if (slot < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "No free process slots";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create per-process PML4 with kernel-half copied
|
||||
@@ -106,20 +107,20 @@ namespace Sched {
|
||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||
if (entry == 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate kernel stack (used during syscalls and interrupts)
|
||||
void* firstPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (firstPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for kernel stack";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
void* stackMem = Memory::g_pfa->ReallocConsecutive(firstPage, StackPages);
|
||||
if (stackMem == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to allocate contiguous kernel stack";
|
||||
Memory::g_pfa->Free(firstPage);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t* kernelStackBase = (uint8_t*)stackMem;
|
||||
@@ -132,7 +133,7 @@ namespace Sched {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for user stack";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, userStackBase + i * 0x1000);
|
||||
@@ -145,7 +146,7 @@ namespace Sched {
|
||||
void* stubPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (stubPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for exit stub";
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
uint64_t stubPhys = Memory::SubHHDM((uint64_t)stubPage);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, stubPhys, ExitStubAddr);
|
||||
@@ -189,11 +190,17 @@ namespace Sched {
|
||||
proc.userStackTop = UserStackTop - 8; // account for pushed exit stub return address
|
||||
proc.heapNext = UserHeapBase;
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Spawned process " << (uint64_t)proc.pid
|
||||
<< " (" << vfsPath << ") entry=" << kcp::hex << entry
|
||||
<< " kstack=" << (uint64_t)kernelStackBase << "-" << kernelStackTop
|
||||
<< " ustack=" << userStackBase << "-" << UserStackTop
|
||||
<< " pml4=" << pml4Phys << kcp::dec;
|
||||
// Copy arguments string into process
|
||||
proc.args[0] = '\0';
|
||||
if (args != nullptr) {
|
||||
int i = 0;
|
||||
for (; i < 255 && args[i]; i++) {
|
||||
proc.args[i] = args[i];
|
||||
}
|
||||
proc.args[i] = '\0';
|
||||
}
|
||||
|
||||
return proc.pid;
|
||||
}
|
||||
|
||||
void Schedule() {
|
||||
@@ -271,8 +278,6 @@ namespace Sched {
|
||||
return;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Process " << (uint64_t)processTable[currentPid].pid << " terminated";
|
||||
|
||||
processTable[currentPid].state = ProcessState::Terminated;
|
||||
|
||||
int next = -1;
|
||||
@@ -305,4 +310,10 @@ namespace Sched {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAlive(int pid) {
|
||||
if (pid < 0 || pid >= MaxProcesses) return false;
|
||||
return processTable[pid].state == ProcessState::Ready
|
||||
|| processTable[pid].state == ProcessState::Running;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,10 +38,11 @@ namespace Sched {
|
||||
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
|
||||
uint64_t userStackTop; // User-space stack top
|
||||
uint64_t heapNext; // Simple bump allocator for user heap
|
||||
char args[256]; // Command-line arguments (set by parent via Spawn)
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
void Spawn(const char* vfsPath);
|
||||
int Spawn(const char* vfsPath, const char* args = nullptr);
|
||||
void Schedule();
|
||||
|
||||
// Called from the APIC timer handler on every tick.
|
||||
@@ -56,4 +57,7 @@ namespace Sched {
|
||||
// Called by terminated processes to mark themselves done
|
||||
void ExitProcess();
|
||||
|
||||
// Check if a process is still alive (Ready or Running)
|
||||
bool IsAlive(int pid);
|
||||
|
||||
}
|
||||
|
||||
@@ -166,3 +166,9 @@ namespace Kt
|
||||
};
|
||||
|
||||
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.
|
||||
TARGETS := $(addprefix $(BINDIR)/,$(addsuffix .elf,$(PROGRAMS)))
|
||||
|
||||
# Man pages source directory.
|
||||
MANDIR := man
|
||||
MANSRC := $(wildcard $(MANDIR)/*.*)
|
||||
MANDST := $(patsubst $(MANDIR)/%,$(BINDIR)/man/%,$(MANSRC))
|
||||
|
||||
.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.
|
||||
# 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_GETCHAR = 18;
|
||||
static constexpr uint64_t SYS_PING = 19;
|
||||
static constexpr uint64_t SYS_SPAWN = 20;
|
||||
static constexpr uint64_t SYS_FBINFO = 21;
|
||||
static constexpr uint64_t SYS_FBMAP = 22;
|
||||
static constexpr uint64_t SYS_WAITPID = 23;
|
||||
static constexpr uint64_t SYS_TERMSIZE = 24;
|
||||
static constexpr uint64_t SYS_GETARGS = 25;
|
||||
|
||||
struct FbInfo {
|
||||
uint64_t width;
|
||||
uint64_t height;
|
||||
uint64_t pitch; // bytes per scanline
|
||||
uint64_t bpp; // bits per pixel (32)
|
||||
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
||||
};
|
||||
|
||||
struct SysInfo {
|
||||
char osName[32];
|
||||
|
||||
@@ -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 sleep_ms(uint64_t ms) { syscall1(Zenith::SYS_SLEEP_MS, ms); }
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
// 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() {
|
||||
zenith::print("zenith> ");
|
||||
zenith::print("% ");
|
||||
}
|
||||
|
||||
static void cmd_help() {
|
||||
zenith::print("Available commands:\n");
|
||||
zenith::print(" help Show this help message\n");
|
||||
zenith::print(" info Show system information\n");
|
||||
zenith::print(" man <topic> View manual pages\n");
|
||||
zenith::print(" ls List ramdisk files\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(" uptime Show uptime in milliseconds\n");
|
||||
zenith::print(" clear Clear the screen\n");
|
||||
@@ -223,11 +225,52 @@ static void cmd_ping(const char* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_clear() {
|
||||
// Print enough newlines to scroll past visible content
|
||||
for (int i = 0; i < 50; i++) {
|
||||
zenith::putchar('\n');
|
||||
static void cmd_run(const char* arg) {
|
||||
arg = skip_spaces(arg);
|
||||
if (*arg == '\0') {
|
||||
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) {
|
||||
@@ -241,10 +284,18 @@ static void process_command(const char* line) {
|
||||
cmd_info();
|
||||
} else if (streq(line, "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 ")) {
|
||||
cmd_cat(line + 4);
|
||||
} else if (streq(line, "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 ")) {
|
||||
cmd_ping(line + 5);
|
||||
} else if (streq(line, "ping")) {
|
||||
@@ -265,7 +316,10 @@ static void process_command(const char* line) {
|
||||
|
||||
extern "C" void _start() {
|
||||
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("\n");
|
||||
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user