feat: userspace overhaul, Intel GPU driver, and more

This commit is contained in:
2026-02-19 15:46:49 +01:00
parent d355d376f9
commit cae7dd352e
55 changed files with 9080 additions and 270 deletions
+65 -2
View File
@@ -18,6 +18,7 @@
#include <Libraries/String.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Net/Icmp.hpp>
#include <Net/Dns.hpp>
#include <Net/Socket.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
@@ -209,11 +210,18 @@ namespace Zenith {
* Graphics::Cursor::GetFramebufferPitch();
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
Kt::KernelLogStream(Kt::INFO, "FbMap") << "fbPhys=" << kcp::hex << fbPhys
<< " size=" << kcp::dec << fbSize
<< " pages=" << numPages
<< " (" << Graphics::Cursor::GetFramebufferWidth()
<< "x" << Graphics::Cursor::GetFramebufferHeight()
<< " pitch=" << Graphics::Cursor::GetFramebufferPitch() << ")";
// Map at a fixed user VA
constexpr uint64_t userVa = 0x50000000ULL;
for (uint64_t i = 0; i < numPages; i++) {
Memory::VMM::Paging::MapUserIn(
Memory::VMM::Paging::MapUserInWC(
proc->pml4Phys,
fbPhys + i * 0x1000,
userVa + i * 0x1000
@@ -335,6 +343,7 @@ namespace Zenith {
}
out->_pad[0] = 0;
out->_pad[1] = 0;
out->dnsServer = Net::GetDnsServer();
}
static int Sys_SetNetCfg(const NetCfg* in) {
@@ -342,6 +351,7 @@ namespace Zenith {
Net::SetIpAddress(in->ipAddress);
Net::SetSubnetMask(in->subnetMask);
Net::SetGateway(in->gateway);
Net::SetDnsServer(in->dnsServer);
return 0;
}
@@ -372,6 +382,53 @@ namespace Zenith {
return Fs::Vfs::VfsCreate(path);
}
// ---- Terminal scaling ----
static int64_t Sys_TermScale(uint64_t scale_x, uint64_t scale_y) {
if (scale_x == 0) {
return (int64_t)((Kt::GetFontScaleY() << 32) | (Kt::GetFontScaleX() & 0xFFFFFFFF));
}
Kt::Rescale((size_t)scale_x, (size_t)scale_y);
size_t cols = 0, rows = 0;
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
return (int64_t)((rows << 32) | (cols & 0xFFFFFFFF));
}
// ---- DNS resolve ----
static int64_t Sys_Resolve(const char* hostname) {
uint32_t ip = Net::Dns::Resolve(hostname);
return (int64_t)ip;
}
// ---- Random number generation ----
// Uses RDTSC mixed with xorshift64* PRNG for entropy.
// RDRAND is intentionally avoided: some firmware disables the RDRAND
// hardware unit while CPUID still advertises support (bit 30 of ECX),
// causing #UD on real hardware. RDTSC-based entropy is sufficient for
// seeding BearSSL's PRNG for TLS session keys.
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
uint64_t tsc;
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
uint64_t state = tsc;
for (uint64_t i = 0; i < len; i += 8) {
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
state ^= tsc;
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
uint64_t val = state * 0x2545F4914F6CDD1DULL;
uint64_t remaining = len - i;
uint64_t toCopy = remaining < 8 ? remaining : 8;
for (uint64_t j = 0; j < toCopy; j++)
buf[i + j] = (uint8_t)(val >> (j * 8));
}
return (int64_t)len;
}
// ---- Dispatch ----
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
@@ -486,6 +543,12 @@ namespace Zenith {
frame->arg3, frame->arg4);
case SYS_FCREATE:
return (int64_t)Sys_FCreate((const char*)frame->arg1);
case SYS_TERMSCALE:
return Sys_TermScale(frame->arg1, frame->arg2);
case SYS_RESOLVE:
return Sys_Resolve((const char*)frame->arg1);
case SYS_GETRANDOM:
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2);
default:
return -1;
}
@@ -512,7 +575,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 << ", 43 syscalls)";
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 46 syscalls)";
}
}
+4
View File
@@ -54,6 +54,9 @@ namespace Zenith {
static constexpr uint64_t SYS_RECVFROM = 40;
static constexpr uint64_t SYS_FWRITE = 41;
static constexpr uint64_t SYS_FCREATE = 42;
static constexpr uint64_t SYS_TERMSCALE = 43;
static constexpr uint64_t SYS_RESOLVE = 44;
static constexpr uint64_t SYS_GETRANDOM = 45;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
@@ -88,6 +91,7 @@ namespace Zenith {
uint32_t gateway; // network byte order
uint8_t macAddress[6];
uint8_t _pad[2];
uint32_t dnsServer; // network byte order
};
struct KeyEvent {
+6
View File
@@ -32,6 +32,12 @@ void Panic(const char *meditationString, System::PanicFrame* frame) {
if (frame->InterruptVector == 0xE) {
auto pf_frame = (System::PageFaultPanicFrame*)frame;
frame = (System::PanicFrame*)&pf_frame->IP;
// CR2 holds the faulting virtual address for page faults
uint64_t cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2));
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, boxWidth);
PrintBoxedLine(kerr, "Page Fault Error:", boxWidth, true);
PrintBoxedDec(kerr, "Present", pf_frame->PageFaultError.Present, boxWidth);
PrintBoxedDec(kerr, "Write", pf_frame->PageFaultError.Write, boxWidth);
+564
View File
@@ -0,0 +1,564 @@
/*
* IntelGPU.cpp
* Intel integrated graphics (i915) modesetting driver
* Scans PCI for Intel display controllers, maps MMIO, initializes GTT,
* and sets up a framebuffer using the firmware's existing display timings.
* Copyright (c) 2025 Daniel Hammer
*/
#include "IntelGPU.hpp"
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Io/IoPort.hpp>
#include <Graphics/Cursor.hpp>
using namespace Kt;
namespace Drivers::Graphics::IntelGPU {
// =========================================================================
// Driver state
// =========================================================================
static bool g_initialized = false;
static GpuInfo g_gpuInfo = {};
static volatile uint8_t* g_mmioBase = nullptr;
// GTT state
static volatile void* g_gttBase = nullptr; // Virtual address of GTT entries
static uint64_t g_gttEntryCount = 0; // Number of GTT entries
static uint64_t g_scratchPagePhys = 0; // Physical address of scratch page
static uint8_t g_gpuGen = 0; // Cached generation number
// Framebuffer state
static uint32_t* g_fbBase = nullptr; // HHDM virtual address of first FB page
static uint64_t g_fbPhysBase = 0; // Physical address of first FB page
static uint64_t g_fbWidth = 0;
static uint64_t g_fbHeight = 0;
static uint64_t g_fbPitch = 0; // Stride in bytes
static uint64_t g_fbSize = 0; // Total framebuffer size in bytes
static uint64_t g_fbGttOffset = 0; // GTT offset where FB starts (in bytes)
// =========================================================================
// Register access helpers
// =========================================================================
static void WriteReg(uint32_t reg, uint32_t val) {
*(volatile uint32_t*)(g_mmioBase + reg) = val;
}
static uint32_t ReadReg(uint32_t reg) {
return *(volatile uint32_t*)(g_mmioBase + reg);
}
// =========================================================================
// PCI Detection
// =========================================================================
static bool DetectGpu() {
auto& devices = Pci::GetDevices();
const Pci::PciDevice* found = nullptr;
const DeviceInfo* matchedInfo = nullptr;
for (uint64_t i = 0; i < devices.size(); i++) {
if (devices[i].VendorId != VendorIntel) {
continue;
}
if (devices[i].ClassCode != ClassDisplay) {
continue;
}
// Found an Intel display controller; try to match device ID
found = &devices[i];
for (int j = 0; j < SupportedDeviceCount; j++) {
if (devices[i].DeviceId == SupportedDevices[j].deviceId) {
matchedInfo = &SupportedDevices[j];
break;
}
}
// Stop at first Intel display controller
break;
}
if (found == nullptr) {
KernelLogStream(WARNING, "IntelGPU") << "No Intel display controller found";
return false;
}
g_gpuInfo.pciBus = found->Bus;
g_gpuInfo.pciDevice = found->Device;
g_gpuInfo.pciFunction = found->Function;
g_gpuInfo.deviceId = found->DeviceId;
if (matchedInfo != nullptr) {
g_gpuInfo.gen = matchedInfo->gen;
g_gpuInfo.name = matchedInfo->name;
KernelLogStream(OK, "IntelGPU") << "Found " << matchedInfo->name
<< " (device " << base::hex << (uint64_t)found->DeviceId << ")"
<< " at PCI " << (uint64_t)found->Bus << ":"
<< (uint64_t)found->Device << "." << (uint64_t)found->Function;
} else {
// Unknown device ID - accept generically but warn
g_gpuInfo.gen = 7; // Assume gen 7 as a safe default
g_gpuInfo.name = "Unknown Intel GPU";
KernelLogStream(WARNING, "IntelGPU") << "Unknown Intel display controller "
<< "(device " << base::hex << (uint64_t)found->DeviceId << ")"
<< " at PCI " << (uint64_t)found->Bus << ":"
<< (uint64_t)found->Device << "." << (uint64_t)found->Function
<< " - attempting generic initialization";
}
g_gpuGen = g_gpuInfo.gen;
return true;
}
// =========================================================================
// BAR0 MMIO Mapping
// =========================================================================
static bool MapMmio() {
uint8_t bus = g_gpuInfo.pciBus;
uint8_t dev = g_gpuInfo.pciDevice;
uint8_t func = g_gpuInfo.pciFunction;
// Read BAR0
uint32_t bar0Low = Pci::LegacyRead32(bus, dev, func, (uint8_t)PCI_REG_BAR0);
uint64_t mmioPhys = bar0Low & 0xFFFFFFF0u; // Mask off type/prefetchable bits
// Check for 64-bit BAR (bit 2 of BAR type field)
if (bar0Low & 0x04) {
uint32_t bar0High = Pci::LegacyRead32(bus, dev, func, (uint8_t)(PCI_REG_BAR0 + 4));
mmioPhys |= ((uint64_t)bar0High << 32);
}
g_gpuInfo.mmioPhys = mmioPhys;
g_gpuInfo.mmioSize = 0x200000; // Map 2MB of MMIO space
KernelLogStream(INFO, "IntelGPU") << "BAR0 physical: " << base::hex << mmioPhys;
// Map 2MB of MMIO space (0x200 pages)
for (uint64_t offset = 0; offset < 0x200000; offset += 0x1000) {
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
}
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
KernelLogStream(OK, "IntelGPU") << "MMIO mapped at virtual " << base::hex << (uint64_t)g_mmioBase;
// Enable memory space and bus master in PCI command register
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_COMMAND);
pciCmd |= PCI_CMD_MEM_SPACE | PCI_CMD_BUS_MASTER;
Pci::LegacyWrite16(bus, dev, func, (uint8_t)PCI_REG_COMMAND, pciCmd);
KernelLogStream(OK, "IntelGPU") << "PCI memory space and bus mastering enabled";
return true;
}
// =========================================================================
// VGA Disable
// =========================================================================
static void DisableVga() {
// Check if VGA plane is already disabled (bit 31 of VGACNTRL).
// On modern Intel iGPUs with eDP/LVDS panels, the firmware typically
// disables VGA when it sets up GOP. Skip if already done.
uint32_t vgaCtrl = ReadReg(VGACNTRL);
if (vgaCtrl & VGACNTRL_DISABLE) {
KernelLogStream(INFO, "IntelGPU") << "VGA plane already disabled by firmware";
return;
}
// Step 1: Disable VGA screen via the VGA sequencer I/O ports
// SR01 bit 5 must be set BEFORE disabling the VGA plane register
Io::Out8(0x01, 0x3C4);
uint8_t sr01 = Io::In8(0x3C5);
sr01 |= (1 << 5);
Io::Out8(0x01, 0x3C4);
Io::Out8(sr01, 0x3C5);
// Step 2: Set bit 31 of VGACNTRL MMIO register to disable VGA display plane
vgaCtrl |= VGACNTRL_DISABLE;
WriteReg(VGACNTRL, vgaCtrl);
// Step 3: Read back to flush the write
(void)ReadReg(VGACNTRL);
KernelLogStream(OK, "IntelGPU") << "VGA plane disabled";
}
// =========================================================================
// Read Current Display State
// =========================================================================
static bool ReadDisplayState() {
// Read pipe A configuration
uint32_t pipeConf = ReadReg(PIPEACONF);
bool pipeEnabled = (pipeConf & PIPECONF_ENABLE) != 0;
// Read plane A control
uint32_t dspaCntr = ReadReg(DSPACNTR);
bool planeEnabled = (dspaCntr & DISP_ENABLE) != 0;
// Read current surface address and stride
uint32_t dspaSurf = ReadReg(DSPASURF);
uint32_t dspaStride = ReadReg(DSPASTRIDE);
// Read timing registers
uint32_t htotal = ReadReg(HTOTAL_A);
uint32_t vtotal = ReadReg(VTOTAL_A);
uint32_t pipeSrc = ReadReg(PIPEASRC);
KernelLogStream(INFO, "IntelGPU") << "Pipe A: "
<< (pipeEnabled ? "ENABLED" : "DISABLED")
<< ", Plane A: " << (planeEnabled ? "ENABLED" : "DISABLED");
KernelLogStream(INFO, "IntelGPU") << "DSPASURF: " << base::hex << (uint64_t)dspaSurf
<< ", DSPASTRIDE: " << (uint64_t)dspaStride;
KernelLogStream(INFO, "IntelGPU") << "HTOTAL_A: " << base::hex << (uint64_t)htotal
<< ", VTOTAL_A: " << (uint64_t)vtotal
<< ", PIPEASRC: " << (uint64_t)pipeSrc;
// Extract resolution from PIPEASRC (preferred) or timing registers
if (pipeSrc != 0) {
// PIPEASRC: bits [31:16] = horizontal size - 1, bits [15:0] = vertical size - 1
g_fbWidth = ((pipeSrc >> 16) & 0xFFFF) + 1;
g_fbHeight = (pipeSrc & 0xFFFF) + 1;
} else if (pipeEnabled) {
// Fallback to timing registers
g_fbWidth = (htotal & 0xFFF) + 1;
g_fbHeight = (vtotal & 0xFFF) + 1;
}
// Read stride from hardware.
// On Gen 9+ (Skylake and later), PLANE_STRIDE (same offset as DSPASTRIDE)
// stores the stride in 64-byte units, not bytes. Detect this by checking
// whether the raw value is too small to be a byte stride.
if (dspaStride != 0) {
g_fbPitch = dspaStride;
if (g_fbWidth > 0 && g_fbPitch < g_fbWidth * 4) {
KernelLogStream(INFO, "IntelGPU") << "DSPASTRIDE=" << base::dec << g_fbPitch
<< " is in 64-byte units (Gen 9+), converting to bytes";
g_fbPitch *= 64;
}
}
// If we still don't have valid dimensions, fall back to the firmware framebuffer
if (g_fbWidth == 0 || g_fbHeight == 0 || g_fbPitch == 0) {
g_fbWidth = ::Graphics::Cursor::GetFramebufferWidth();
g_fbHeight = ::Graphics::Cursor::GetFramebufferHeight();
g_fbPitch = ::Graphics::Cursor::GetFramebufferPitch();
KernelLogStream(INFO, "IntelGPU") << "Using firmware framebuffer dimensions: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " pitch=" << g_fbPitch;
} else {
KernelLogStream(INFO, "IntelGPU") << "Detected resolution: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " pitch=" << g_fbPitch;
}
if (g_fbWidth == 0 || g_fbHeight == 0) {
KernelLogStream(ERROR, "IntelGPU") << "Could not determine display resolution";
return false;
}
// Ensure pitch is at least width * 4 (BGRX8888)
if (g_fbPitch == 0) {
g_fbPitch = g_fbWidth * 4;
KernelLogStream(WARNING, "IntelGPU") << "Stride not available, assuming "
<< base::dec << g_fbPitch << " bytes";
}
g_fbSize = g_fbHeight * g_fbPitch;
KernelLogStream(OK, "IntelGPU") << "Display state: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< ", stride=" << g_fbPitch
<< ", FB size=" << g_fbSize << " bytes";
return true;
}
// =========================================================================
// GTT Initialization
// =========================================================================
static bool InitializeGtt() {
uint8_t bus = g_gpuInfo.pciBus;
uint8_t dev = g_gpuInfo.pciDevice;
uint8_t func = g_gpuInfo.pciFunction;
// Read GMCH_CTL to determine GTT size
uint16_t gmchCtl = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_GMCH_CTL);
uint8_t gttSizeBits = (gmchCtl >> 8) & 0x3;
uint64_t gttSizeBytes = 0;
if (g_gpuGen >= 8) {
// Gen 8+ has a different encoding for GTT size
switch (gttSizeBits) {
case 0: gttSizeBytes = 0; break; // No GTT
case 1: gttSizeBytes = 2 * 1024 * 1024; break; // 2MB
case 2: gttSizeBytes = 4 * 1024 * 1024; break; // 4MB
case 3: gttSizeBytes = 8 * 1024 * 1024; break; // 8MB
default: gttSizeBytes = 2 * 1024 * 1024; break;
}
} else {
// Gen 5-7
switch (gttSizeBits) {
case 0: gttSizeBytes = 0; break; // No GTT
case 1: gttSizeBytes = 1024 * 1024; break; // 1MB
case 2: gttSizeBytes = 2 * 1024 * 1024; break; // 2MB
case 3: gttSizeBytes = 2 * 1024 * 1024; break; // Depends on gen, default 2MB
default: gttSizeBytes = 1024 * 1024; break;
}
}
if (gttSizeBytes == 0) {
// If hardware reports no GTT, assume 1MB as a safe fallback
gttSizeBytes = 1024 * 1024;
KernelLogStream(WARNING, "IntelGPU") << "GMCH_CTL reports no GTT, assuming 1MB";
}
g_gpuInfo.gttSize = gttSizeBytes;
KernelLogStream(INFO, "IntelGPU") << "GMCH_CTL: " << base::hex << (uint64_t)gmchCtl
<< ", GTT size: " << base::dec << (gttSizeBytes / 1024) << " KB";
// The GTT entries reside at BAR0 + 2MB (offset 0x200000)
// This is correct for most Intel generations
uint64_t gttPhys = g_gpuInfo.mmioPhys + 0x200000;
// Map the GTT region (it may overlap with already-mapped MMIO, but we map
// additional pages beyond the initial 2MB MMIO mapping)
uint64_t gttMapSize = gttSizeBytes;
// Ensure we map at least up to the GTT region end
for (uint64_t offset = 0; offset < gttMapSize; offset += 0x1000) {
Memory::VMM::g_paging->MapMMIO(gttPhys + offset, Memory::HHDM(gttPhys + offset));
}
g_gttBase = (volatile void*)Memory::HHDM(gttPhys);
// Calculate number of GTT entries
if (g_gpuGen >= 8) {
// 64-bit PTEs
g_gttEntryCount = gttSizeBytes / sizeof(uint64_t);
} else {
// 32-bit PTEs
g_gttEntryCount = gttSizeBytes / sizeof(uint32_t);
}
KernelLogStream(INFO, "IntelGPU") << "GTT at physical " << base::hex << gttPhys
<< ", " << base::dec << g_gttEntryCount << " entries"
<< (g_gpuGen >= 8 ? " (64-bit PTEs)" : " (32-bit PTEs)");
// Allocate a scratch page (zeroed) for future use
void* scratchPageVirt = Memory::g_pfa->AllocateZeroed();
g_scratchPagePhys = Memory::SubHHDM(scratchPageVirt);
// Do NOT clear the entire GTT here. The firmware has active GTT mappings
// that the display engine is currently scanning out from. Clearing them
// would cause the display to go black (or worse) before we remap.
// Instead, we only write the entries we need in SetupFramebuffer().
KernelLogStream(OK, "IntelGPU") << "GTT ready: " << base::dec << g_gttEntryCount
<< " entries, scratch page at " << base::hex << g_scratchPagePhys;
return true;
}
// =========================================================================
// Framebuffer Allocation and Setup
// =========================================================================
static bool SetupFramebuffer() {
// Map the firmware framebuffer's contiguous physical pages through our GTT.
// This keeps the same physical memory that the firmware set up (contiguous
// pages, already HHDM-mapped), so both kernel and userspace access continue
// to work via the original virtual/physical addresses. No copy is needed.
uint32_t* fwFb = ::Graphics::Cursor::GetFramebufferBase();
if (fwFb == nullptr) {
KernelLogStream(ERROR, "IntelGPU") << "No firmware framebuffer available";
return false;
}
uint64_t fwFbPhys = Memory::SubHHDM((uint64_t)fwFb);
uint64_t pageCount = (g_fbSize + 0xFFF) / 0x1000;
if (pageCount > g_gttEntryCount) {
KernelLogStream(ERROR, "IntelGPU") << "Framebuffer requires " << base::dec << pageCount
<< " pages but GTT only has " << g_gttEntryCount << " entries";
return false;
}
KernelLogStream(INFO, "IntelGPU") << "Mapping " << base::dec << pageCount
<< " firmware FB pages through GTT (phys base " << base::hex << fwFbPhys << ")";
// Program GTT entries to point to the firmware FB's contiguous physical pages
if (g_gpuGen >= 8) {
volatile uint64_t* gtt64 = (volatile uint64_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt64[i] = MakeGttPte64(fwFbPhys + i * 0x1000);
}
// Flush GTT writes
(void)gtt64[pageCount - 1];
} else {
volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt32[i] = MakeGttPte32(fwFbPhys + i * 0x1000);
}
// Flush GTT writes
(void)gtt32[pageCount - 1];
}
// Keep using the same framebuffer memory
g_fbBase = fwFb;
g_fbPhysBase = fwFbPhys;
g_fbGttOffset = 0; // Starting at GTT entry 0 => offset 0
KernelLogStream(OK, "IntelGPU") << "Framebuffer mapped through GTT: " << base::dec << pageCount
<< " pages, phys=" << base::hex << fwFbPhys;
return true;
}
static void ProgramDisplayPlane() {
// Preserve the firmware's DSPACNTR value entirely. The firmware already
// configured the correct pixel format, pipe assignment, and tiling mode.
// We only need to point DSPASURF to our GTT-mapped framebuffer.
uint32_t dspaCntr = ReadReg(DSPACNTR);
uint32_t fmtBits = (dspaCntr & DISP_FORMAT_MASK) >> DISP_FORMAT_SHIFT;
uint32_t oldDspaSurf = ReadReg(DSPASURF);
KernelLogStream(INFO, "IntelGPU") << "Preserving firmware DSPACNTR: " << base::hex
<< (uint64_t)dspaCntr << " (format=" << (uint64_t)fmtBits << ")";
KernelLogStream(INFO, "IntelGPU") << "Firmware DSPASURF was: " << base::hex
<< (uint64_t)oldDspaSurf;
// Ensure the plane is enabled (it should already be)
if (!(dspaCntr & DISP_ENABLE)) {
dspaCntr |= DISP_ENABLE;
WriteReg(DSPACNTR, dspaCntr);
}
// Do NOT write DSPASTRIDE — the firmware already set the correct value
// in the hardware's native format (bytes on Gen <9, 64-byte units on Gen 9+).
// Writing our byte-converted g_fbPitch would corrupt it on Gen 9+.
// Write the GTT base offset to DSPASURF - this triggers the plane update
// Since we mapped at GTT entry 0, the offset is 0
uint32_t surfAddr = (uint32_t)g_fbGttOffset;
WriteReg(DSPASURF, surfAddr);
// Read back to flush
(void)ReadReg(DSPASURF);
KernelLogStream(OK, "IntelGPU") << "Display plane A: DSPASURF="
<< base::hex << (uint64_t)surfAddr
<< " (was " << (uint64_t)oldDspaSurf << ")"
<< ", stride=" << base::dec << g_fbPitch;
}
// =========================================================================
// Public API
// =========================================================================
void Initialize() {
KernelLogStream(INFO, "IntelGPU") << "Scanning for Intel integrated graphics...";
// Step 1: Detect GPU on PCI bus
if (!DetectGpu()) {
return;
}
// Step 2: Map BAR0 MMIO region
if (!MapMmio()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to map MMIO region";
return;
}
// Step 3: Disable VGA plane
DisableVga();
// Step 4: Read current display state from firmware
if (!ReadDisplayState()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
return;
}
// Step 5: Initialize GTT
if (!InitializeGtt()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT";
return;
}
// Step 6: Map firmware framebuffer pages through GTT
if (!SetupFramebuffer()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to set up framebuffer";
return;
}
// Step 7: Program the display plane to use our GTT-mapped framebuffer
ProgramDisplayPlane();
g_initialized = true;
// Diagnostic: compare GPU-detected values with firmware/Limine values
uint64_t fwWidth = ::Graphics::Cursor::GetFramebufferWidth();
uint64_t fwHeight = ::Graphics::Cursor::GetFramebufferHeight();
uint64_t fwPitch = ::Graphics::Cursor::GetFramebufferPitch();
if (g_fbWidth != fwWidth || g_fbHeight != fwHeight || g_fbPitch != fwPitch) {
KernelLogStream(WARNING, "IntelGPU") << "GPU dimensions differ from firmware!";
KernelLogStream(WARNING, "IntelGPU") << " GPU: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " pitch=" << g_fbPitch;
KernelLogStream(WARNING, "IntelGPU") << " Firmware: "
<< base::dec << fwWidth << "x" << fwHeight
<< " pitch=" << fwPitch;
}
KernelLogStream(OK, "IntelGPU") << "Initialization complete: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " @ " << base::hex << (uint64_t)g_fbBase;
}
bool IsInitialized() {
return g_initialized;
}
const GpuInfo* GetGpuInfo() {
return &g_gpuInfo;
}
uint32_t* GetFramebufferBase() {
return g_fbBase;
}
uint64_t GetFramebufferPhysBase() {
return g_fbPhysBase;
}
uint64_t GetWidth() {
return g_fbWidth;
}
uint64_t GetHeight() {
return g_fbHeight;
}
uint64_t GetPitch() {
return g_fbPitch;
}
};
+420
View File
@@ -0,0 +1,420 @@
/*
* IntelGPU.hpp
* Intel integrated graphics (i915) modesetting driver
* Supports Gen 5 (Ironlake) through Gen 12 (Tiger Lake / Alder Lake)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Graphics::IntelGPU {
// =========================================================================
// PCI identification
// =========================================================================
static constexpr uint16_t VendorIntel = 0x8086;
static constexpr uint8_t ClassDisplay = 0x03;
static constexpr uint8_t SubclassVGA = 0x00;
// PCI config space offsets
static constexpr uint16_t PCI_REG_BAR0 = 0x10;
static constexpr uint16_t PCI_REG_BAR2 = 0x18;
static constexpr uint16_t PCI_REG_COMMAND = 0x04;
static constexpr uint16_t PCI_CMD_MEM_SPACE = (1 << 1);
static constexpr uint16_t PCI_CMD_BUS_MASTER = (1 << 2);
// Graphics control register (PCI config offset 0x50 on SNB+)
static constexpr uint16_t PCI_REG_GMCH_CTL = 0x50;
// Supported Intel GPU device IDs (representative subset)
struct DeviceInfo {
uint16_t deviceId;
uint8_t gen; // Intel graphics generation (5-12)
const char* name;
};
static constexpr DeviceInfo SupportedDevices[] = {
// Gen 5 - Ironlake
{0x0042, 5, "Ironlake Desktop"},
{0x0046, 5, "Ironlake Mobile"},
// Gen 6 - Sandy Bridge
{0x0102, 6, "Sandy Bridge GT1 Desktop"},
{0x0112, 6, "Sandy Bridge GT2 Desktop"},
{0x0122, 6, "Sandy Bridge GT2 Desktop"},
{0x0106, 6, "Sandy Bridge GT1 Mobile"},
{0x0116, 6, "Sandy Bridge GT2 Mobile"},
{0x0126, 6, "Sandy Bridge GT2 Mobile"},
{0x010A, 6, "Sandy Bridge GT1 Server"},
// Gen 7 - Ivy Bridge
{0x0152, 7, "Ivy Bridge GT1 Desktop"},
{0x0162, 7, "Ivy Bridge GT2 Desktop"},
{0x0156, 7, "Ivy Bridge GT1 Mobile"},
{0x0166, 7, "Ivy Bridge GT2 Mobile"},
{0x015A, 7, "Ivy Bridge GT1 Server"},
{0x016A, 7, "Ivy Bridge GT2 Server"},
// Gen 7.5 - Haswell
{0x0402, 7, "Haswell GT1 Desktop"},
{0x0412, 7, "Haswell GT2 Desktop"},
{0x0422, 7, "Haswell GT3 Desktop"},
{0x0406, 7, "Haswell GT1 Mobile"},
{0x0416, 7, "Haswell GT2 Mobile"},
{0x0426, 7, "Haswell GT3 Mobile"},
{0x0A06, 7, "Haswell ULT GT1"},
{0x0A16, 7, "Haswell ULT GT2"},
{0x0A26, 7, "Haswell ULT GT3"},
{0x0D12, 7, "Haswell CRW GT2"},
{0x0D22, 7, "Haswell CRW GT3"},
// Gen 8 - Broadwell
{0x1602, 8, "Broadwell GT1"},
{0x1612, 8, "Broadwell GT2"},
{0x1616, 8, "Broadwell GT2 Mobile"},
{0x1622, 8, "Broadwell GT3"},
{0x1626, 8, "Broadwell GT3 Mobile"},
{0x162A, 8, "Broadwell GT3 Server"},
// Gen 9 - Skylake
{0x1902, 9, "Skylake GT1 Desktop"},
{0x1906, 9, "Skylake GT1 Mobile"},
{0x1912, 9, "Skylake GT2 Desktop"},
{0x1916, 9, "Skylake GT2 Mobile"},
{0x191E, 9, "Skylake GT2 Mobile"},
{0x1926, 9, "Skylake GT3 Mobile"},
{0x1932, 9, "Skylake GT4 Desktop"},
// Gen 9.5 - Kaby Lake / Coffee Lake
{0x5902, 9, "Kaby Lake GT1 Desktop"},
{0x5912, 9, "Kaby Lake GT2 Desktop"},
{0x5916, 9, "Kaby Lake GT2 Mobile"},
{0x5926, 9, "Kaby Lake GT3 Mobile"},
{0x3E90, 9, "Coffee Lake GT1 Desktop"},
{0x3E92, 9, "Coffee Lake GT2 Desktop"},
{0x3EA0, 9, "Coffee Lake GT3"},
{0x3E91, 9, "Coffee Lake GT2 Desktop"},
{0x3E98, 9, "Coffee Lake GT2 Desktop"},
{0x9B41, 9, "Comet Lake GT2"},
{0x9BA5, 9, "Comet Lake GT2 Mobile"},
// Gen 11 - Ice Lake
{0x8A52, 11, "Ice Lake GT2"},
{0x8A56, 11, "Ice Lake GT2 Mobile"},
{0x8A5A, 11, "Ice Lake GT1.5"},
{0x8A5C, 11, "Ice Lake GT1"},
// Gen 12 - Tiger Lake
{0x9A49, 12, "Tiger Lake GT2"},
{0x9A78, 12, "Tiger Lake GT2"},
{0x9A40, 12, "Tiger Lake GT2"},
// Gen 12 - Alder Lake
{0x4626, 12, "Alder Lake GT2"},
{0x4680, 12, "Alder Lake-S GT1"},
{0x4692, 12, "Alder Lake-S GT1"},
{0x46A6, 12, "Alder Lake-P GT2"},
};
static constexpr int SupportedDeviceCount = sizeof(SupportedDevices) / sizeof(SupportedDevices[0]);
// =========================================================================
// MMIO register offsets (relative to BAR0)
// =========================================================================
// --- VGA control ---
static constexpr uint32_t VGACNTRL = 0x71400;
static constexpr uint32_t VGACNTRL_DISABLE = (1u << 31);
// --- DPLL (Display PLL) ---
static constexpr uint32_t DPLL_A = 0x06014;
static constexpr uint32_t DPLL_B = 0x06018;
static constexpr uint32_t FPA0 = 0x06040;
static constexpr uint32_t FPA1 = 0x06044;
static constexpr uint32_t FPB0 = 0x06048;
static constexpr uint32_t FPB1 = 0x0604C;
// DPLL control bits
static constexpr uint32_t DPLL_VCO_ENABLE = (1u << 31);
static constexpr uint32_t DPLL_VGA_MODE_DIS = (1u << 28);
static constexpr uint32_t DPLL_MODE_DAC_SDVO = (1u << 26);
static constexpr uint32_t DPLL_MODE_LVDS = (2u << 26);
// FP register fields
static constexpr uint32_t FP_N_DIV_SHIFT = 16;
static constexpr uint32_t FP_N_DIV_MASK = 0x3F0000;
static constexpr uint32_t FP_M1_DIV_SHIFT = 8;
static constexpr uint32_t FP_M1_DIV_MASK = 0x003F00;
static constexpr uint32_t FP_M2_DIV_SHIFT = 0;
static constexpr uint32_t FP_M2_DIV_MASK = 0x00003F;
// --- Display timing registers (Pipe A) ---
static constexpr uint32_t HTOTAL_A = 0x60000;
static constexpr uint32_t HBLANK_A = 0x60004;
static constexpr uint32_t HSYNC_A = 0x60008;
static constexpr uint32_t VTOTAL_A = 0x6000C;
static constexpr uint32_t VBLANK_A = 0x60010;
static constexpr uint32_t VSYNC_A = 0x60014;
static constexpr uint32_t PIPEASRC = 0x6001C;
// --- Display timing registers (Pipe B) ---
static constexpr uint32_t HTOTAL_B = 0x61000;
static constexpr uint32_t HBLANK_B = 0x61004;
static constexpr uint32_t HSYNC_B = 0x61008;
static constexpr uint32_t VTOTAL_B = 0x6100C;
static constexpr uint32_t VBLANK_B = 0x61010;
static constexpr uint32_t VSYNC_B = 0x61014;
static constexpr uint32_t PIPEBSRC = 0x6101C;
// --- Pipe configuration ---
static constexpr uint32_t PIPEACONF = 0x70008;
static constexpr uint32_t PIPEBCONF = 0x71008;
static constexpr uint32_t PIPECONF_ENABLE = (1u << 31);
static constexpr uint32_t PIPECONF_STATE = (1u << 30);
static constexpr uint32_t PIPECONF_8BPC = (0u << 5);
static constexpr uint32_t PIPECONF_10BPC = (1u << 5);
static constexpr uint32_t PIPECONF_6BPC = (2u << 5);
static constexpr uint32_t PIPECONF_12BPC = (3u << 5);
// --- Display plane control (Plane A, pre-Skylake i9xx-style) ---
static constexpr uint32_t DSPACNTR = 0x70180;
static constexpr uint32_t DSPALINOFF = 0x70184;
static constexpr uint32_t DSPASTRIDE = 0x70188;
static constexpr uint32_t DSPAPOS = 0x7018C;
static constexpr uint32_t DSPASIZE = 0x70190;
static constexpr uint32_t DSPASURF = 0x7019C;
static constexpr uint32_t DSPATILEOFF = 0x701A4;
// --- Display plane control (Plane B) ---
static constexpr uint32_t DSPBCNTR = 0x71180;
static constexpr uint32_t DSPBLINOFF = 0x71184;
static constexpr uint32_t DSPBSTRIDE = 0x71188;
static constexpr uint32_t DSPBPOS = 0x7118C;
static constexpr uint32_t DSPBSIZE = 0x71190;
static constexpr uint32_t DSPBSURF = 0x7119C;
static constexpr uint32_t DSPBTILEOFF = 0x711A4;
// DSPCNTR bits
static constexpr uint32_t DISP_ENABLE = (1u << 31);
static constexpr uint32_t DISP_GAMMA_ENABLE = (1u << 30);
static constexpr uint32_t DISP_FORMAT_SHIFT = 26;
static constexpr uint32_t DISP_FORMAT_MASK = (0xFu << 26);
static constexpr uint32_t DISP_FORMAT_BGRX8888 = (0x6u << 26); // 32bpp BGRX (no alpha)
static constexpr uint32_t DISP_FORMAT_BGRA8888 = (0x7u << 26); // 32bpp BGRA (with alpha)
static constexpr uint32_t DISP_FORMAT_RGBX8888 = (0xEu << 26); // 32bpp RGBX
static constexpr uint32_t DISP_FORMAT_BGR565 = (0x5u << 26); // 16bpp BGR 5:6:5
static constexpr uint32_t DISP_FORMAT_BGRX1010102 = (0xAu << 26);
static constexpr uint32_t DISP_PIPE_B_SELECT = (1u << 24);
static constexpr uint32_t DISP_TILED = (1u << 10);
// --- Cursor plane (Pipe A) ---
static constexpr uint32_t CURACNTR = 0x70080;
static constexpr uint32_t CURABASE = 0x70084;
static constexpr uint32_t CURAPOS = 0x70088;
// --- Output connectors ---
static constexpr uint32_t ADPA = 0x61100; // Analog Display Port (VGA/CRT)
static constexpr uint32_t DVOB = 0x61140; // DVO-B
static constexpr uint32_t DVOC = 0x61160; // DVO-C
static constexpr uint32_t LVDS = 0x61180; // LVDS panel
static constexpr uint32_t DP_B = 0x64100; // DisplayPort B
static constexpr uint32_t DP_C = 0x64200; // DisplayPort C
static constexpr uint32_t DP_D = 0x64300; // DisplayPort D
static constexpr uint32_t HDMI_B = 0x61140; // HDMI-B (same as DVOB on some gens)
static constexpr uint32_t HDMI_C = 0x61160; // HDMI-C (same as DVOC on some gens)
// ADPA bits
static constexpr uint32_t ADPA_DAC_ENABLE = (1u << 31);
static constexpr uint32_t ADPA_PIPE_B_SELECT = (1u << 30);
static constexpr uint32_t ADPA_HSYNC_ACTIVE_LOW = (1u << 3);
static constexpr uint32_t ADPA_VSYNC_ACTIVE_LOW = (1u << 4);
// LVDS bits
static constexpr uint32_t LVDS_PORT_ENABLE = (1u << 31);
static constexpr uint32_t LVDS_PIPE_B_SELECT = (1u << 30);
// --- GMBUS (I2C for EDID) ---
static constexpr uint32_t GMBUS0 = 0x5100; // Clock/port select
static constexpr uint32_t GMBUS1 = 0x5104; // Command/status
static constexpr uint32_t GMBUS2 = 0x5108; // Status
static constexpr uint32_t GMBUS3 = 0x510C; // Data buffer
static constexpr uint32_t GMBUS4 = 0x5110; // Interrupt mask
static constexpr uint32_t GMBUS5 = 0x5120; // 2-byte index
// --- Hardware status page ---
static constexpr uint32_t HWS_PGA = 0x02080;
// --- Fence registers (tiling) ---
static constexpr uint32_t FENCE_REG_BASE = 0x02000; // Gen 2-3
static constexpr uint32_t FENCE_REG_965_BASE = 0x03000; // Gen 4+
// =========================================================================
// GTT (Graphics Translation Table)
// =========================================================================
// GTT PTE format for Gen 6/7 (Sandy Bridge through Haswell) - 32-bit entries
// Bits [31:12] = physical page address [31:12]
// Bits [10:4] = physical page address [38:32]
// Bits [3:1] = cacheability
// Bit [0] = valid
static constexpr uint32_t GTT_PTE_VALID = (1u << 0);
static constexpr uint32_t GTT_PTE_WB_LLC = (3u << 1); // Write-back LLC cache
static constexpr uint32_t GTT_PTE_UNCACHED = (0u << 1);
// Gen 8+ uses 64-bit GTT PTEs
static constexpr uint64_t GTT_PTE64_VALID = (1ULL << 0);
// Helper: Build a Gen 6/7 GTT PTE from a physical address
static inline uint32_t MakeGttPte32(uint64_t physAddr) {
uint32_t pte = (uint32_t)(physAddr & 0xFFFFF000u); // bits [31:12]
pte |= (uint32_t)((physAddr >> 28) & 0x7F0u); // bits [38:32] -> PTE [10:4]
pte |= GTT_PTE_VALID;
return pte;
}
// Helper: Build a Gen 8+ GTT PTE from a physical address
static inline uint64_t MakeGttPte64(uint64_t physAddr) {
return (physAddr & ~0xFFFULL) | GTT_PTE64_VALID;
}
// =========================================================================
// DPLL clock calculation structures
// =========================================================================
struct DpllParams {
uint32_t n;
uint32_t m1;
uint32_t m2;
uint32_t p1;
uint32_t p2;
};
// DPLL parameter limits (Gen 6 / Sandy Bridge)
struct DpllLimits {
uint32_t nMin, nMax;
uint32_t m1Min, m1Max;
uint32_t m2Min, m2Max;
uint32_t p1Min, p1Max;
uint32_t p2Slow, p2Fast; // P2 values for slow/fast pixel clocks
uint32_t p2Threshold; // kHz threshold between slow/fast P2
uint32_t vcoMin, vcoMax; // VCO range in kHz
uint32_t refClock; // Reference clock in kHz
};
// Sandy Bridge / Ivy Bridge DAC/SDVO limits
static constexpr DpllLimits SNB_DAC_LIMITS = {
.nMin = 1, .nMax = 5,
.m1Min = 12, .m1Max = 22,
.m2Min = 5, .m2Max = 9,
.p1Min = 1, .p1Max = 8,
.p2Slow = 10, .p2Fast = 5,
.p2Threshold = 225000,
.vcoMin = 1750000, .vcoMax = 3500000,
.refClock = 120000,
};
// Sandy Bridge / Ivy Bridge LVDS limits
static constexpr DpllLimits SNB_LVDS_LIMITS = {
.nMin = 1, .nMax = 3,
.m1Min = 12, .m1Max = 22,
.m2Min = 5, .m2Max = 9,
.p1Min = 1, .p1Max = 8,
.p2Slow = 14, .p2Fast = 7,
.p2Threshold = 225000,
.vcoMin = 1750000, .vcoMax = 3500000,
.refClock = 120000,
};
// =========================================================================
// Display mode timing
// =========================================================================
struct DisplayMode {
uint32_t hdisplay; // Horizontal active pixels
uint32_t hsyncStart;
uint32_t hsyncEnd;
uint32_t htotal;
uint32_t vdisplay; // Vertical active lines
uint32_t vsyncStart;
uint32_t vsyncEnd;
uint32_t vtotal;
uint32_t pixelClock; // in kHz
bool hsyncPositive;
bool vsyncPositive;
};
// Common mode timings (CVT/DMT standard)
static constexpr DisplayMode MODE_1920x1080_60 = {
.hdisplay = 1920, .hsyncStart = 2008, .hsyncEnd = 2052, .htotal = 2200,
.vdisplay = 1080, .vsyncStart = 1084, .vsyncEnd = 1089, .vtotal = 1125,
.pixelClock = 148500,
.hsyncPositive = true, .vsyncPositive = true,
};
static constexpr DisplayMode MODE_1280x720_60 = {
.hdisplay = 1280, .hsyncStart = 1390, .hsyncEnd = 1430, .htotal = 1650,
.vdisplay = 720, .vsyncStart = 725, .vsyncEnd = 730, .vtotal = 750,
.pixelClock = 74250,
.hsyncPositive = true, .vsyncPositive = true,
};
static constexpr DisplayMode MODE_1024x768_60 = {
.hdisplay = 1024, .hsyncStart = 1048, .hsyncEnd = 1184, .htotal = 1344,
.vdisplay = 768, .vsyncStart = 771, .vsyncEnd = 777, .vtotal = 806,
.pixelClock = 65000,
.hsyncPositive = false, .vsyncPositive = false,
};
static constexpr DisplayMode MODE_800x600_60 = {
.hdisplay = 800, .hsyncStart = 840, .hsyncEnd = 968, .htotal = 1056,
.vdisplay = 600, .vsyncStart = 601, .vsyncEnd = 605, .vtotal = 628,
.pixelClock = 40000,
.hsyncPositive = true, .vsyncPositive = true,
};
// =========================================================================
// Detected GPU information
// =========================================================================
struct GpuInfo {
uint16_t deviceId;
uint8_t gen;
const char* name;
uint8_t pciBus;
uint8_t pciDevice;
uint8_t pciFunction;
uint64_t mmioPhys; // BAR0 physical address
uint64_t mmioSize; // BAR0 region size
uint64_t gmadrPhys; // BAR2 physical (aperture)
uint64_t gttSize; // GTT size in bytes
uint32_t stolenMb; // Stolen memory in MB
};
// =========================================================================
// Public API
// =========================================================================
// Initialize the Intel GPU driver (scans PCI, maps MMIO, sets up GTT + FB)
void Initialize();
// Check if an Intel GPU was found and initialized
bool IsInitialized();
// Get detected GPU information
const GpuInfo* GetGpuInfo();
// Framebuffer access (returns HHDM virtual address)
uint32_t* GetFramebufferBase();
// Framebuffer physical base (for userspace mapping)
uint64_t GetFramebufferPhysBase();
// Framebuffer dimensions
uint64_t GetWidth();
uint64_t GetHeight();
uint64_t GetPitch();
};
+14
View File
@@ -6,6 +6,7 @@
#include "Cursor.hpp"
#include <Terminal/Terminal.hpp>
#include <Memory/HHDM.hpp>
namespace Graphics::Cursor {
@@ -30,4 +31,17 @@ namespace Graphics::Cursor {
uint64_t GetFramebufferHeight() { return g_FbHeight; }
uint64_t GetFramebufferPitch() { return g_FbPitch; }
void SetFramebuffer(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch) {
g_FbBase = base;
g_FbWidth = width;
g_FbHeight = height;
g_FbPitch = pitch;
Kt::KernelLogStream(Kt::OK, "Graphics") << "Framebuffer switched ("
<< (uint64_t)g_FbWidth << "x" << (uint64_t)g_FbHeight << ")";
}
uint64_t GetFramebufferPhysBase() {
return Memory::SubHHDM((uint64_t)g_FbBase);
}
};
+3
View File
@@ -17,4 +17,7 @@ namespace Graphics::Cursor {
uint64_t GetFramebufferHeight();
uint64_t GetFramebufferPitch();
void SetFramebuffer(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch);
uint64_t GetFramebufferPhysBase();
};
+29
View File
@@ -26,5 +26,34 @@ namespace Hal {
static constexpr uint32_t IA32_STAR = 0xC0000081;
static constexpr uint32_t IA32_LSTAR = 0xC0000082;
static constexpr uint32_t IA32_FMASK = 0xC0000084;
static constexpr uint32_t IA32_PAT = 0x00000277;
// PAT memory type encodings
static constexpr uint8_t PAT_UC = 0x00; // Uncacheable
static constexpr uint8_t PAT_WC = 0x01; // Write Combining
static constexpr uint8_t PAT_WT = 0x04; // Write Through
static constexpr uint8_t PAT_WP = 0x05; // Write Protect
static constexpr uint8_t PAT_WB = 0x06; // Write Back
static constexpr uint8_t PAT_UCM = 0x07; // UC- (UC minus)
// Program PAT so entry 1 = WC (default is WT).
// PAT index is selected by PTE bits: PAT(bit7) | PCD(bit4) | PWT(bit3)
// Entry 0 (000) = WB — normal memory (unchanged)
// Entry 1 (001) = WC — framebuffers (was WT)
// Entry 2 (010) = UC- — (unchanged)
// Entry 3 (011) = UC — MMIO registers (unchanged)
// Entries 4-7: unchanged from defaults
inline void InitializePAT() {
uint64_t pat =
((uint64_t)PAT_WB << 0) | // Entry 0: WB
((uint64_t)PAT_WC << 8) | // Entry 1: WC (was WT)
((uint64_t)PAT_UCM << 16) | // Entry 2: UC-
((uint64_t)PAT_UC << 24) | // Entry 3: UC
((uint64_t)PAT_WB << 32) | // Entry 4: WB
((uint64_t)PAT_WT << 40) | // Entry 5: WT
((uint64_t)PAT_UCM << 48) | // Entry 6: UC-
((uint64_t)PAT_UC << 56); // Entry 7: UC
WriteMSR(IA32_PAT, pat);
}
}
+45 -2
View File
@@ -35,9 +35,11 @@
#include <Drivers/PS2/Mouse.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Net/Net.hpp>
#include <CppLib/BoxUI.hpp>
#include <Graphics/Cursor.hpp>
#include <Hal/MSR.hpp>
#include <Fs/Ramdisk.hpp>
#include <Fs/Vfs.hpp>
#include <Sched/Scheduler.hpp>
@@ -138,7 +140,38 @@ extern "C" void kmain() {
Memory::VMM::g_paging = &g_paging;
g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), memmap_request.response);
// Reprogram PAT so entry 1 = Write-Combining (default is Write-Through).
// Must be done after paging init and before any WC mappings.
Hal::InitializePAT();
Kt::KernelLogStream(OK, "Hal") << "PAT reprogrammed (entry 1 = WC)";
#endif
// Initialize Cursor early so we can WC-map the framebuffer before
// the bulk of boot logging begins (ACPI, PCI, drivers, etc.)
Graphics::Cursor::Initialize(framebuffer);
#if defined (__x86_64__)
// Map framebuffer as Write-Combining immediately for faster screen writes.
// All subsequent log output benefits from WC burst transfers.
{
uint64_t fbPhys = Graphics::Cursor::GetFramebufferPhysBase();
uint64_t fbSize = Graphics::Cursor::GetFramebufferHeight()
* Graphics::Cursor::GetFramebufferPitch();
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
for (uint64_t i = 0; i < numPages; i++) {
uint64_t phys = fbPhys + i * 0x1000;
g_paging.MapWC(phys, Memory::HHDM(phys));
}
asm volatile("mov %%cr3, %%rax; mov %%rax, %%cr3" ::: "rax", "memory");
Kt::KernelLogStream(OK, "Graphics") << "Framebuffer mapped as Write-Combining ("
<< kcp::dec << numPages << " pages)";
}
#endif
Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(rsdp_request.response->address));
#if defined (__x86_64__)
@@ -147,6 +180,18 @@ extern "C" void kmain() {
Pci::Initialize(g_acpi.GetXSDT());
// Intel GPU driver — initialize right after PCI so the native
// driver takes over early and all subsequent logs use it.
Drivers::Graphics::IntelGPU::Initialize();
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
Graphics::Cursor::SetFramebuffer(
Drivers::Graphics::IntelGPU::GetFramebufferBase(),
Drivers::Graphics::IntelGPU::GetWidth(),
Drivers::Graphics::IntelGPU::GetHeight(),
Drivers::Graphics::IntelGPU::GetPitch()
);
}
Timekeeping::ApicTimerInitialize();
Drivers::PS2::Initialize();
@@ -199,8 +244,6 @@ extern "C" void kmain() {
};
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
Graphics::Cursor::Initialize(framebuffer);
Hal::LoadTSS();
Zenith::InitializeSyscalls();
+55
View File
@@ -94,6 +94,26 @@ namespace Memory::VMM {
pageEntry->Address = physicalAddress >> 12;
}
void Paging::MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Value that isn't page-aligned passed as address to Paging::MapWC!", nullptr);
}
VirtualAddress virtualAddressObj(virtualAddress);
auto PML3 = HandleLevel(virtualAddressObj, PML4, 4);
auto PML2 = HandleLevel(virtualAddressObj, PML3, 3);
auto PML1 = HandleLevel(virtualAddressObj, PML2, 2);
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]);
pageEntry->Present = true;
pageEntry->Writable = true;
pageEntry->WriteThrough = true; // PWT=1, PCD=0 → PAT entry 1 = WC
pageEntry->Address = physicalAddress >> 12;
}
void Paging::MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Value that isn't page-aligned passed as address to Paging::MapMMIO!", nullptr);
@@ -185,6 +205,41 @@ namespace Memory::VMM {
pageEntry->Address = physicalAddress >> 12;
}
void Paging::MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Non-aligned address in Paging::MapUserInWC!", nullptr);
}
VirtualAddress va(virtualAddress);
auto walkLevel = [](PageTable* table, uint64_t index) -> PageTable* {
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[index]);
if (!entry->Present) {
entry->Present = true;
entry->Writable = true;
entry->Supervisor = 1;
uint64_t newPhys = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
entry->Address = newPhys >> 12;
return (PageTable*)newPhys;
} else {
entry->Supervisor = 1;
return (PageTable*)(entry->Address << 12);
}
};
PageTable* pml4 = (PageTable*)pml4Phys;
auto pml3 = walkLevel(pml4, va.GetL4Index());
auto pml2 = walkLevel(pml3, va.GetL3Index());
auto pml1 = walkLevel(pml2, va.GetL2Index());
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]);
pageEntry->Present = true;
pageEntry->Writable = true;
pageEntry->Supervisor = 1;
pageEntry->WriteThrough = true; // PWT=1, PCD=0 → PAT entry 1 = WC
pageEntry->Address = physicalAddress >> 12;
}
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
VirtualAddress virtualAddressObj(virtualAddress);
+4
View File
@@ -94,6 +94,7 @@ public:
void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap);
void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false);
std::uint64_t GetPhysAddr(std::uint64_t virtualAddress);
@@ -104,6 +105,9 @@ public:
// Map a page into an arbitrary PML4 (specified by physical address) with User bit set.
static void MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
// Map a page into an arbitrary PML4 with User + Write-Combining attributes.
static void MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
};
extern Paging* g_paging;
+375
View File
@@ -0,0 +1,375 @@
/*
* Dns.cpp
* DNS resolver (kernel-level, RFC 1035)
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include "Dns.hpp"
#include <Net/Udp.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
#include <Libraries/Memory.hpp>
#include <Libraries/String.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Sched/Scheduler.hpp>
#include <Terminal/Terminal.hpp>
namespace Net::Dns {
// ---- DNS packet constants ----
static constexpr uint16_t DNS_PORT = 53;
static constexpr uint16_t DNS_FLAGS_RD = 0x0100; // Recursion Desired
static constexpr uint16_t DNS_QTYPE_A = 1;
static constexpr uint16_t DNS_QCLASS_IN = 1;
// ---- Simple cache ----
static constexpr int CACHE_SIZE = 8;
struct CacheEntry {
char hostname[128];
uint32_t ip;
uint32_t ttl; // TTL in seconds
uint64_t timestamp; // ms when cached
bool valid;
};
static CacheEntry g_cache[CACHE_SIZE] = {};
static bool streq(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
a++; b++;
}
return *a == *b;
}
static uint32_t CacheLookup(const char* hostname) {
uint64_t now = Timekeeping::GetMilliseconds();
for (int i = 0; i < CACHE_SIZE; i++) {
if (!g_cache[i].valid) continue;
if (!streq(g_cache[i].hostname, hostname)) continue;
// Check TTL
uint64_t elapsed = (now - g_cache[i].timestamp) / 1000;
if (elapsed < g_cache[i].ttl) {
return g_cache[i].ip;
}
// Expired
g_cache[i].valid = false;
return 0;
}
return 0;
}
static void CacheStore(const char* hostname, uint32_t ip, uint32_t ttl) {
if (ttl == 0) ttl = 60; // Minimum 60s TTL
// Find free or oldest slot
int slot = 0;
uint64_t oldestTime = ~0ULL;
for (int i = 0; i < CACHE_SIZE; i++) {
if (!g_cache[i].valid) { slot = i; break; }
if (g_cache[i].timestamp < oldestTime) {
oldestTime = g_cache[i].timestamp;
slot = i;
}
}
CacheEntry& e = g_cache[slot];
// Copy hostname
int len = 0;
while (hostname[len] && len < 126) { e.hostname[len] = hostname[len]; len++; }
e.hostname[len] = '\0';
e.ip = ip;
e.ttl = ttl;
e.timestamp = Timekeeping::GetMilliseconds();
e.valid = true;
}
// ---- DNS query building ----
// Encode a hostname as DNS labels: "example.com" -> "\x07example\x03com\x00"
// Returns number of bytes written, or 0 on error.
static int EncodeName(const char* hostname, uint8_t* out, int maxLen) {
int outPos = 0;
const char* p = hostname;
while (*p) {
// Find the next dot or end
const char* dot = p;
while (*dot && *dot != '.') dot++;
int labelLen = (int)(dot - p);
if (labelLen == 0 || labelLen > 63) return 0;
if (outPos + 1 + labelLen >= maxLen) return 0;
out[outPos++] = (uint8_t)labelLen;
for (int i = 0; i < labelLen; i++) {
out[outPos++] = (uint8_t)p[i];
}
p = dot;
if (*p == '.') p++;
}
if (outPos >= maxLen) return 0;
out[outPos++] = 0; // Root label terminator
return outPos;
}
// Build a DNS query packet. Returns total packet length, or 0 on error.
static int BuildQuery(uint16_t id, const char* hostname, uint8_t* packet, int maxLen) {
if (maxLen < 12) return 0;
// Header (12 bytes)
packet[0] = (uint8_t)(id >> 8);
packet[1] = (uint8_t)(id & 0xFF);
packet[2] = (uint8_t)(DNS_FLAGS_RD >> 8); // Flags high: RD=1
packet[3] = (uint8_t)(DNS_FLAGS_RD & 0xFF); // Flags low
packet[4] = 0; packet[5] = 1; // QDCOUNT = 1
packet[6] = 0; packet[7] = 0; // ANCOUNT = 0
packet[8] = 0; packet[9] = 0; // NSCOUNT = 0
packet[10] = 0; packet[11] = 0; // ARCOUNT = 0
// Question section
int nameLen = EncodeName(hostname, packet + 12, maxLen - 12 - 4);
if (nameLen == 0) return 0;
int pos = 12 + nameLen;
if (pos + 4 > maxLen) return 0;
// QTYPE = A (1)
packet[pos++] = 0;
packet[pos++] = DNS_QTYPE_A;
// QCLASS = IN (1)
packet[pos++] = 0;
packet[pos++] = DNS_QCLASS_IN;
return pos;
}
// ---- DNS response parsing ----
// Skip over a DNS name in the packet (handles compression pointers).
// Returns the new offset, or -1 on error.
static int SkipName(const uint8_t* packet, int packetLen, int offset) {
int maxJumps = 32; // prevent infinite loops
bool jumped = false;
int returnOffset = -1;
while (offset < packetLen && maxJumps > 0) {
uint8_t len = packet[offset];
if (len == 0) {
// End of name
offset++;
return jumped ? returnOffset : offset;
}
if ((len & 0xC0) == 0xC0) {
// Compression pointer
if (offset + 1 >= packetLen) return -1;
if (!jumped) returnOffset = offset + 2;
offset = ((len & 0x3F) << 8) | packet[offset + 1];
jumped = true;
maxJumps--;
continue;
}
// Regular label
offset += 1 + len;
maxJumps--;
}
return -1;
}
struct DnsAnswer {
uint32_t ip;
uint32_t ttl;
bool found;
};
// Parse a DNS response and extract the first A record.
static DnsAnswer ParseResponse(uint16_t expectedId, const uint8_t* packet, int packetLen) {
DnsAnswer result = {0, 0, false};
if (packetLen < 12) return result;
// Check ID
uint16_t id = ((uint16_t)packet[0] << 8) | packet[1];
if (id != expectedId) return result;
// Check QR bit (must be response)
if (!(packet[2] & 0x80)) return result;
// Check RCODE (must be 0 = no error)
uint8_t rcode = packet[3] & 0x0F;
if (rcode != 0) return result;
uint16_t qdcount = ((uint16_t)packet[4] << 8) | packet[5];
uint16_t ancount = ((uint16_t)packet[6] << 8) | packet[7];
// Skip question section
int offset = 12;
for (uint16_t i = 0; i < qdcount; i++) {
offset = SkipName(packet, packetLen, offset);
if (offset < 0) return result;
offset += 4; // QTYPE + QCLASS
if (offset > packetLen) return result;
}
// Parse answers
for (uint16_t i = 0; i < ancount; i++) {
offset = SkipName(packet, packetLen, offset);
if (offset < 0 || offset + 10 > packetLen) return result;
uint16_t atype = ((uint16_t)packet[offset] << 8) | packet[offset + 1];
// uint16_t aclass = ((uint16_t)packet[offset + 2] << 8) | packet[offset + 3];
uint32_t attl = ((uint32_t)packet[offset + 4] << 24) |
((uint32_t)packet[offset + 5] << 16) |
((uint32_t)packet[offset + 6] << 8) |
((uint32_t)packet[offset + 7]);
uint16_t rdlen = ((uint16_t)packet[offset + 8] << 8) | packet[offset + 9];
offset += 10;
if (offset + rdlen > packetLen) return result;
if (atype == DNS_QTYPE_A && rdlen == 4) {
// A record: 4-byte IPv4 address (already in network byte order)
result.ip = ((uint32_t)packet[offset])
| ((uint32_t)packet[offset + 1] << 8)
| ((uint32_t)packet[offset + 2] << 16)
| ((uint32_t)packet[offset + 3] << 24);
result.ttl = attl;
result.found = true;
return result;
}
offset += rdlen;
}
return result;
}
// ---- Resolve state (shared with UDP callback) ----
static volatile bool g_gotResponse = false;
static volatile uint16_t g_currentId = 0;
static uint8_t g_responseBuffer[512];
static volatile int g_responseLen = 0;
static void DnsRecvCallback(uint32_t srcIp, uint16_t srcPort,
uint16_t dstPort,
const uint8_t* data, uint16_t length) {
(void)srcIp;
(void)srcPort;
(void)dstPort;
if (g_gotResponse) return; // Already got a response
if (length > sizeof(g_responseBuffer)) length = sizeof(g_responseBuffer);
memcpy(g_responseBuffer, data, length);
g_responseLen = length;
g_gotResponse = true;
}
// ---- Simple PRNG for transaction IDs ----
static uint16_t g_nextId = 0x4E53; // "NS"
static uint16_t NextId() {
g_nextId = g_nextId * 25173 + 13849;
return g_nextId;
}
// ---- Check if string is already an IP address ----
static bool IsIpAddress(const char* s) {
int dotCount = 0;
bool hasDigit = false;
for (int i = 0; s[i]; i++) {
if (s[i] >= '0' && s[i] <= '9') {
hasDigit = true;
} else if (s[i] == '.') {
if (!hasDigit) return false;
dotCount++;
hasDigit = false;
} else {
return false;
}
}
return hasDigit && dotCount == 3;
}
// ---- Public API ----
uint32_t Resolve(const char* hostname, uint32_t timeoutMs) {
if (hostname == nullptr || hostname[0] == '\0') return 0;
// Don't try to resolve IP addresses
if (IsIpAddress(hostname)) return 0;
// Check cache first
uint32_t cached = CacheLookup(hostname);
if (cached != 0) return cached;
// Check DNS server is configured
uint32_t dnsServer = Net::GetDnsServer();
if (dnsServer == 0) return 0;
// Pick a local port for receiving the response (ephemeral range)
uint16_t localPort = 10000 + (NextId() % 50000);
uint16_t txId = NextId();
// Build DNS query
uint8_t queryPacket[512];
int queryLen = BuildQuery(txId, hostname, queryPacket, sizeof(queryPacket));
if (queryLen == 0) return 0;
// Reset response state
g_gotResponse = false;
g_responseLen = 0;
g_currentId = txId;
// Bind our receive port
if (!Net::Udp::Bind(localPort, DnsRecvCallback)) {
// Port might be in use, try another
localPort = 10000 + (NextId() % 50000);
if (!Net::Udp::Bind(localPort, DnsRecvCallback)) {
return 0;
}
}
// Send the query to DNS server port 53
bool sent = Net::Udp::Send(dnsServer, localPort, DNS_PORT, queryPacket, (uint16_t)queryLen);
if (!sent) {
Net::Udp::Unbind(localPort);
return 0;
}
// Wait for response with timeout
uint64_t start = Timekeeping::GetMilliseconds();
while (!g_gotResponse) {
if (Timekeeping::GetMilliseconds() - start >= timeoutMs) {
Net::Udp::Unbind(localPort);
return 0;
}
Sched::Schedule();
}
// Unbind the port
Net::Udp::Unbind(localPort);
// Parse the response
DnsAnswer answer = ParseResponse(txId, g_responseBuffer, g_responseLen);
if (!answer.found) return 0;
// Cache the result
CacheStore(hostname, answer.ip, answer.ttl);
return answer.ip;
}
}
+16
View File
@@ -0,0 +1,16 @@
/*
* Dns.hpp
* DNS resolver (kernel-level)
* Copyright (c) 2025-2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Net::Dns {
// Resolve a hostname to an IPv4 address.
// Returns the IP in network byte order, or 0 on failure.
uint32_t Resolve(const char* hostname, uint32_t timeoutMs = 5000);
}
+4
View File
@@ -12,6 +12,7 @@ namespace Net {
static uint32_t g_ipAddress = Ipv4Addr(10, 0, 68, 99);
static uint32_t g_subnetMask = Ipv4Addr(255, 255, 255, 0);
static uint32_t g_gateway = Ipv4Addr(10, 0, 68, 1);
static uint32_t g_dnsServer = Ipv4Addr(10, 0, 68, 1);
uint32_t GetIpAddress() { return g_ipAddress; }
void SetIpAddress(uint32_t ip) { g_ipAddress = ip; }
@@ -22,6 +23,9 @@ namespace Net {
uint32_t GetGateway() { return g_gateway; }
void SetGateway(uint32_t gw) { g_gateway = gw; }
uint32_t GetDnsServer() { return g_dnsServer; }
void SetDnsServer(uint32_t dns) { g_dnsServer = dns; }
bool IsLocalSubnet(uint32_t destIp) {
return (destIp & g_subnetMask) == (g_ipAddress & g_subnetMask);
}
+4
View File
@@ -27,6 +27,10 @@ namespace Net {
uint32_t GetGateway();
void SetGateway(uint32_t gw);
// Get/set the DNS server (network byte order)
uint32_t GetDnsServer();
void SetDnsServer(uint32_t dns);
// Check if a destination IP is on the local subnet
bool IsLocalSubnet(uint32_t destIp);
-3
View File
@@ -255,9 +255,6 @@ namespace Net::Tcp {
// Send ACK
SendSegment(conn, FLAG_ACK, nullptr, 0);
KernelLogStream(INFO, "Net") << "TCP connection established to port "
<< base::dec << (uint64_t)conn->RemotePort;
}
}
break;
+124 -2
View File
@@ -5,16 +5,131 @@
*/
#include "Terminal.hpp"
#define FLANTERM_IN_FLANTERM
#include "../Libraries/flanterm/src/flanterm_backends/fb.h"
#include "../Libraries/flanterm/src/flanterm.h"
#include "../Libraries/String.hpp"
#include "../Libraries/Memory.hpp"
#include <CppLib/CString.hpp>
namespace Kt {
flanterm_context *ctx;
std::size_t g_terminal_width = 0;
// Maximum grid cells allocated at init (scale 1,1). Used to validate
// that a requested scale does not exceed the original buffer capacity.
static std::size_t g_max_grid_cells = 0;
// Custom plot_char that works for any font_scale_x/y >= 1.
// This is the same algorithm as flanterm's plot_char_scaled_uncanvas
// but lives outside fb.c so we can install it after rescaling.
static void plot_char_universal(struct flanterm_context *_ctx,
struct flanterm_fb_char *c,
size_t x, size_t y) {
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)_ctx;
if (x >= _ctx->cols || y >= _ctx->rows) {
return;
}
uint32_t default_bg = fbctx->default_bg;
uint32_t bg = c->bg == 0xffffffff ? default_bg : c->bg;
uint32_t fg = c->fg == 0xffffffff ? fbctx->default_fg : c->fg;
x = fbctx->offset_x + x * fbctx->glyph_width;
y = fbctx->offset_y + y * fbctx->glyph_height;
bool *glyph = &fbctx->font_bool[c->c * fbctx->font_height * fbctx->font_width];
// Only ROTATE_0 is used in ZenithOS
volatile uint32_t *dest = fbctx->framebuffer + x + y * (fbctx->pitch / 4);
size_t stride = fbctx->pitch / 4;
for (size_t gy = 0; gy < fbctx->glyph_height; gy++) {
size_t fy = gy / fbctx->font_scale_y;
volatile uint32_t *fb_line = dest;
bool *glyph_pointer = glyph + (fy * fbctx->font_width);
for (size_t fx = 0; fx < fbctx->font_width; fx++) {
for (size_t i = 0; i < fbctx->font_scale_x; i++) {
*fb_line = *glyph_pointer ? fg : bg;
fb_line++;
}
glyph_pointer++;
}
dest += stride;
}
}
void Rescale(std::size_t scale_x, std::size_t scale_y) {
if (scale_x == 0) scale_x = 1;
if (scale_y == 0) scale_y = 1;
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
// Calculate new dimensions
size_t new_glyph_w = fbctx->font_width * scale_x;
size_t new_glyph_h = fbctx->font_height * scale_y;
size_t new_cols = fbctx->width / new_glyph_w;
size_t new_rows = fbctx->height / new_glyph_h;
if (new_cols == 0 || new_rows == 0) return;
// Ensure the new grid fits within original buffer allocation
if (new_cols * new_rows > g_max_grid_cells) return;
// Update scale and glyph dimensions
fbctx->font_scale_x = scale_x;
fbctx->font_scale_y = scale_y;
fbctx->glyph_width = new_glyph_w;
fbctx->glyph_height = new_glyph_h;
// Update terminal grid dimensions
ctx->cols = new_cols;
ctx->rows = new_rows;
// Center the text area
fbctx->offset_x = (fbctx->width % new_glyph_w) / 2;
fbctx->offset_y = (fbctx->height % new_glyph_h) / 2;
// Install our universal plot_char
fbctx->plot_char = plot_char_universal;
// Reinitialize grid data (reuse existing buffers)
for (size_t i = 0; i < new_rows * new_cols; i++) {
fbctx->grid[i].c = ' ';
fbctx->grid[i].fg = fbctx->text_fg;
fbctx->grid[i].bg = fbctx->text_bg;
}
fbctx->queue_i = 0;
memset(fbctx->queue, 0, new_rows * new_cols * sizeof(struct flanterm_fb_queue_item));
memset(fbctx->map, 0, new_rows * new_cols * sizeof(struct flanterm_fb_queue_item *));
// Clear the framebuffer
for (size_t y = 0; y < fbctx->height; y++) {
volatile uint32_t *row = fbctx->framebuffer + y * (fbctx->pitch / 4);
for (size_t x = 0; x < fbctx->width; x++) {
row[x] = fbctx->default_bg;
}
}
// Reset terminal state and refresh
flanterm_context_reinit(ctx);
flanterm_full_refresh(ctx);
}
std::size_t GetFontScaleX() {
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
return fbctx->font_scale_x;
}
std::size_t GetFontScaleY() {
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
return fbctx->font_scale_y;
}
void UpdatePanelBar(CString panelText) {
kout << "\033[s";
kout << "\033[H";
@@ -48,13 +163,20 @@ namespace Kt {
NULL, NULL,
NULL, NULL,
NULL, 0, 0, 1,
0, 0,
1, 1,
0,
FLANTERM_FB_ROTATE_0
);
g_terminal_width = width;
// Store max grid cells for rescale buffer bounds checking
g_max_grid_cells = ctx->cols * ctx->rows;
// Install our universal plot_char so rescaling works at any scale
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
fbctx->plot_char = plot_char_universal;
UpdatePanelBar("Initializing...");
kout << "\n\n\n";
}
@@ -73,4 +195,4 @@ namespace Kt {
}
}
};
};
+4
View File
@@ -44,6 +44,10 @@ namespace Kt
void UpdatePanelBar(const char* panelText);
void Rescale(std::size_t font_scale_x, std::size_t font_scale_y);
std::size_t GetFontScaleX();
std::size_t GetFontScaleY();
inline base base_custom(int custom)
{
return (base)custom;
+32 -5
View File
@@ -56,9 +56,9 @@ BINDIR := bin
# Discover all programs (each subdirectory under src/ is a program).
PROGRAMS := $(notdir $(wildcard src/*))
# Games are built separately (doom has its own Makefile).
GAMES := doom
SYSTEM_PROGRAMS := $(filter-out $(GAMES),$(PROGRAMS))
# Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := doom fetch wiki
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, games are handled separately.
TARGETS := $(addprefix $(BINDIR)/os/,$(addsuffix .elf,$(SYSTEM_PROGRAMS)))
@@ -76,12 +76,31 @@ WWWDIR := www
WWWSRC := $(wildcard $(WWWDIR)/*.*)
WWWDST := $(patsubst $(WWWDIR)/%,$(BINDIR)/www/%,$(WWWSRC))
# CA certificate bundle.
CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
# Home directory placeholder.
HOMEKEEP := $(BINDIR)/home/.keep
.PHONY: all clean doom
.PHONY: all clean doom fetch wiki bearssl libc
all: $(TARGETS) doom $(GAME_DATA) $(MANDST) $(WWWDST) $(HOMEKEEP)
all: bearssl libc $(TARGETS) fetch wiki doom $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP)
# Build BearSSL static library.
bearssl:
$(MAKE) -C lib/bearssl
# Build shared libc static library.
libc:
$(MAKE) -C lib/libc
# Build fetch via its own Makefile (depends on bearssl and libc).
fetch: bearssl libc
$(MAKE) -C src/fetch
# Build wiki via its own Makefile (depends on bearssl and libc).
wiki: bearssl libc
$(MAKE) -C src/wiki
# Build doom via its own Makefile.
doom:
@@ -102,6 +121,11 @@ $(BINDIR)/www/%: $(WWWDIR)/%
mkdir -p $(BINDIR)/www
cp $< $@
# Copy CA certificate bundle into bin/etc/.
$(CA_CERTS): data/ca-certificates.crt
mkdir -p $(BINDIR)/etc
cp $< $@
# Create empty home directory with a keep file.
$(HOMEKEEP):
mkdir -p $(BINDIR)/home
@@ -115,4 +139,7 @@ $(BINDIR)/os/%.elf: src/%/main.cpp link.ld GNUmakefile
clean:
rm -rf $(BINDIR) obj
$(MAKE) -C lib/bearssl clean
$(MAKE) -C lib/libc clean
$(MAKE) -C src/fetch clean
$(MAKE) -C src/doom clean
File diff suppressed because it is too large Load Diff
+4
View File
@@ -54,6 +54,9 @@ namespace Zenith {
static constexpr uint64_t SYS_RECVFROM = 40;
static constexpr uint64_t SYS_FWRITE = 41;
static constexpr uint64_t SYS_FCREATE = 42;
static constexpr uint64_t SYS_TERMSCALE = 43;
static constexpr uint64_t SYS_RESOLVE = 44;
static constexpr uint64_t SYS_GETRANDOM = 45;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
@@ -64,6 +67,7 @@ namespace Zenith {
uint32_t gateway; // network byte order
uint8_t macAddress[6];
uint8_t _pad[2];
uint32_t dnsServer; // network byte order
};
struct DateTime {
+20
View File
@@ -165,6 +165,11 @@ namespace zenith {
return (int32_t)syscall2(Zenith::SYS_PING, (uint64_t)ip, (uint64_t)timeoutMs);
}
// DNS resolve: returns IP in network byte order, or 0 on failure
inline uint32_t resolve(const char* hostname) {
return (uint32_t)syscall1(Zenith::SYS_RESOLVE, (uint64_t)hostname);
}
// Network configuration
inline void get_netcfg(Zenith::NetCfg* out) { syscall1(Zenith::SYS_GETNETCFG, (uint64_t)out); }
inline int set_netcfg(const Zenith::NetCfg* cfg) { return (int)syscall1(Zenith::SYS_SETNETCFG, (uint64_t)cfg); }
@@ -222,9 +227,24 @@ namespace zenith {
if (rows) *rows = (int)(r >> 32);
}
inline void termscale(int scale_x, int scale_y) {
syscall2(Zenith::SYS_TERMSCALE, (uint64_t)scale_x, (uint64_t)scale_y);
}
inline void get_termscale(int* scale_x, int* scale_y) {
uint64_t r = (uint64_t)syscall2(Zenith::SYS_TERMSCALE, 0, 0);
if (scale_x) *scale_x = (int)(r & 0xFFFFFFFF);
if (scale_y) *scale_y = (int)(r >> 32);
}
// Timekeeping (wall-clock)
inline void gettime(Zenith::DateTime* out) { syscall1(Zenith::SYS_GETTIME, (uint64_t)out); }
// Random number generation
inline int64_t getrandom(void* buf, uint32_t len) {
return syscall2(Zenith::SYS_GETRANDOM, (uint64_t)buf, (uint64_t)len);
}
// Power management
[[noreturn]] inline void reset() {
syscall0(Zenith::SYS_RESET);
Submodule programs/lib/bearssl added at 3d9be2f60b
+64
View File
@@ -0,0 +1,64 @@
# Makefile for shared libc static library on ZenithOS
# Copyright (c) 2025-2026 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
AR := $(TOOLCHAIN_PREFIX)ar
else
CC := gcc
AR := ar
endif
# ---- Paths ----
LIBC_INC := ../../include/libc
OBJDIR := obj
# ---- Compiler flags ----
CFLAGS := \
-std=gnu11 \
-g -O2 -pipe \
-Wall \
-Wno-unused-parameter \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-mno-80387 \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mno-red-zone \
-mcmodel=small \
-isystem $(LIBC_INC) \
-isystem $(shell $(CC) -print-file-name=include)
# ---- Target ----
TARGET := liblibc.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJDIR)/libc.o
$(AR) rcs $@ $^
$(OBJDIR)/libc.o: libc.c Makefile
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
+662
View File
@@ -0,0 +1,662 @@
/*
* libc.c
* Minimal C standard library for ZenithOS userspace programs
* Based on the proven libc from the DOOM port.
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
#include <limits.h>
/* ========================================================================
Raw syscall wrappers (C versions matching kernel ABI)
======================================================================== */
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;
}
static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
long ret;
__asm__ volatile(
"mov %[a1], %%rdi\n\t"
"mov %[a2], %%rsi\n\t"
"mov %[a3], %%rdx\n\t"
"mov %[a4], %%r10\n\t"
"syscall"
: "=a"(ret)
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4)
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
return ret;
}
/* Syscall numbers */
#define SYS_EXIT 0
#define SYS_PRINT 4
#define SYS_PUTCHAR 5
#define SYS_ALLOC 11
#define SYS_FREE 12
/* ========================================================================
errno
======================================================================== */
int errno = 0;
/* ========================================================================
string.h functions
======================================================================== */
void *memcpy(void *dest, const void *src, size_t n) {
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
for (size_t i = 0; i < n; i++)
d[i] = s[i];
return dest;
}
void *memset(void *s, int c, size_t n) {
unsigned char *p = (unsigned char *)s;
for (size_t i = 0; i < n; i++)
p[i] = (unsigned char)c;
return s;
}
void *memmove(void *dest, const void *src, size_t n) {
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
if (s < d && d < s + n) {
for (size_t i = n; i > 0; i--)
d[i - 1] = s[i - 1];
} else {
for (size_t i = 0; i < n; i++)
d[i] = s[i];
}
return dest;
}
int memcmp(const void *s1, const void *s2, size_t n) {
const unsigned char *a = (const unsigned char *)s1;
const unsigned char *b = (const unsigned char *)s2;
for (size_t i = 0; i < n; i++) {
if (a[i] != b[i])
return a[i] < b[i] ? -1 : 1;
}
return 0;
}
size_t strlen(const char *s) {
size_t len = 0;
while (s[len]) len++;
return len;
}
int strcmp(const char *a, const char *b) {
while (*a && *a == *b) { a++; b++; }
return (unsigned char)*a - (unsigned char)*b;
}
int strncmp(const char *a, const char *b, size_t n) {
for (size_t i = 0; i < n; i++) {
if (a[i] != b[i] || a[i] == 0)
return (unsigned char)a[i] - (unsigned char)b[i];
}
return 0;
}
char *strcpy(char *dest, const char *src) {
char *d = dest;
while ((*d++ = *src++));
return dest;
}
char *strncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n && src[i]; i++)
dest[i] = src[i];
for (; i < n; i++)
dest[i] = '\0';
return dest;
}
char *strcat(char *dest, const char *src) {
char *d = dest + strlen(dest);
while ((*d++ = *src++));
return dest;
}
char *strncat(char *dest, const char *src, size_t n) {
char *d = dest + strlen(dest);
size_t i;
for (i = 0; i < n && src[i]; i++)
d[i] = src[i];
d[i] = '\0';
return dest;
}
char *strchr(const char *s, int c) {
while (*s) {
if (*s == (char)c) return (char *)s;
s++;
}
return (c == 0) ? (char *)s : NULL;
}
char *strrchr(const char *s, int c) {
const char *last = NULL;
while (*s) {
if (*s == (char)c) last = s;
s++;
}
if (c == 0) return (char *)s;
return (char *)last;
}
int strcasecmp(const char *a, const char *b) {
while (*a && ((*a >= 'A' && *a <= 'Z') ? *a + 32 : *a) ==
((*b >= 'A' && *b <= 'Z') ? *b + 32 : *b)) { a++; b++; }
int ca = (*a >= 'A' && *a <= 'Z') ? *a + 32 : *a;
int cb = (*b >= 'A' && *b <= 'Z') ? *b + 32 : *b;
return ca - cb;
}
int strncasecmp(const char *a, const char *b, size_t n) {
for (size_t i = 0; i < n; i++) {
int ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i];
int cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i];
if (ca != cb || ca == 0) return ca - cb;
}
return 0;
}
char *strstr(const char *haystack, const char *needle) {
if (!*needle) return (char *)haystack;
size_t nlen = strlen(needle);
while (*haystack) {
if (strncmp(haystack, needle, nlen) == 0)
return (char *)haystack;
haystack++;
}
return NULL;
}
/* Forward declaration for strdup */
void *malloc(size_t size);
char *strdup(const char *s) {
size_t len = strlen(s) + 1;
char *d = (char *)malloc(len);
if (d) memcpy(d, s, len);
return d;
}
/* ========================================================================
ctype.h functions
======================================================================== */
int isalpha(int c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
int isdigit(int c) { return c >= '0' && c <= '9'; }
int isalnum(int c) { return isalpha(c) || isdigit(c); }
int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; }
int isupper(int c) { return c >= 'A' && c <= 'Z'; }
int islower(int c) { return c >= 'a' && c <= 'z'; }
int isprint(int c) { return c >= 0x20 && c <= 0x7E; }
int ispunct(int c) { return isprint(c) && !isalnum(c) && c != ' '; }
int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); }
int iscntrl(int c) { return (c >= 0 && c < 0x20) || c == 0x7F; }
int isgraph(int c) { return c > 0x20 && c <= 0x7E; }
int toupper(int c) { return (c >= 'a' && c <= 'z') ? c - 32 : c; }
int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; }
/* ========================================================================
Heap allocator (free-list, backed by SYS_ALLOC)
======================================================================== */
#define HEAP_MAGIC 0x5A484541ULL /* "ZHEA" */
struct HeapHeader {
uint64_t magic;
uint64_t size;
} __attribute__((packed));
struct FreeNode {
uint64_t size;
struct FreeNode *next;
};
static struct FreeNode g_heapHead = { 0, NULL };
static int g_heapInit = 0;
static void heap_insert_free(void *ptr, uint64_t size) {
struct FreeNode *node = (struct FreeNode *)ptr;
node->size = size;
node->next = g_heapHead.next;
g_heapHead.next = node;
}
static void heap_grow(uint64_t bytes) {
uint64_t pages = (bytes + 0xFFF) / 0x1000;
if (pages < 4) pages = 4;
void *mem = (void *)_zos_syscall1(SYS_ALLOC, (long)(pages * 0x1000));
if (mem != NULL)
heap_insert_free(mem, pages * 0x1000);
}
void *malloc(size_t size) {
if (!g_heapInit) {
heap_grow(16 * 0x1000);
g_heapInit = 1;
}
uint64_t needed = size + sizeof(struct HeapHeader);
needed = (needed + 15) & ~15ULL;
struct FreeNode *prev = &g_heapHead;
struct FreeNode *cur = g_heapHead.next;
while (cur != NULL) {
if (cur->size >= needed) {
uint64_t blockSize = cur->size;
prev->next = cur->next;
if (blockSize > needed + sizeof(struct FreeNode) + 16) {
void *rest = (void *)((uint8_t *)cur + needed);
heap_insert_free(rest, blockSize - needed);
}
struct HeapHeader *hdr = (struct HeapHeader *)cur;
hdr->magic = HEAP_MAGIC;
hdr->size = size;
return (void *)((uint8_t *)hdr + sizeof(struct HeapHeader));
}
prev = cur;
cur = cur->next;
}
heap_grow(needed);
return malloc(size);
}
void free(void *ptr) {
if (ptr == NULL) return;
struct HeapHeader *hdr = (struct HeapHeader *)((uint8_t *)ptr - sizeof(struct HeapHeader));
uint64_t blockSize = hdr->size + sizeof(struct HeapHeader);
blockSize = (blockSize + 15) & ~15ULL;
heap_insert_free((void *)hdr, blockSize);
}
void *calloc(size_t nmemb, size_t size) {
size_t total = nmemb * size;
void *p = malloc(total);
if (p) memset(p, 0, total);
return p;
}
void *realloc(void *ptr, size_t size) {
if (ptr == NULL) return malloc(size);
if (size == 0) { free(ptr); return NULL; }
struct HeapHeader *hdr = (struct HeapHeader *)((uint8_t *)ptr - sizeof(struct HeapHeader));
uint64_t old = hdr->size;
void *newp = malloc(size);
if (newp == NULL) return NULL;
size_t copySize = old < size ? old : size;
memcpy(newp, ptr, copySize);
free(ptr);
return newp;
}
/* ========================================================================
stdlib.h functions
======================================================================== */
int abs(int x) { return x < 0 ? -x : x; }
long labs(long x) { return x < 0 ? -x : x; }
int atoi(const char *s) {
int neg = 0, val = 0;
while (isspace((unsigned char)*s)) s++;
if (*s == '-') { neg = 1; s++; }
else if (*s == '+') { s++; }
while (isdigit((unsigned char)*s)) {
val = val * 10 + (*s - '0');
s++;
}
return neg ? -val : val;
}
long strtol(const char *nptr, char **endptr, int base) {
const char *s = nptr;
long val = 0;
int neg = 0;
while (isspace((unsigned char)*s)) s++;
if (*s == '-') { neg = 1; s++; }
else if (*s == '+') { s++; }
if (base == 0) {
if (*s == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; }
else if (*s == '0') { base = 8; s++; }
else base = 10;
} else if (base == 16 && *s == '0' && (s[1] == 'x' || s[1] == 'X')) {
s += 2;
}
while (*s) {
int digit;
if (*s >= '0' && *s <= '9') digit = *s - '0';
else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10;
else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10;
else break;
if (digit >= base) break;
val = val * base + digit;
s++;
}
if (endptr) *endptr = (char *)s;
return neg ? -val : val;
}
unsigned long strtoul(const char *nptr, char **endptr, int base) {
return (unsigned long)strtol(nptr, endptr, base);
}
char *getenv(const char *name) {
(void)name;
return NULL;
}
void exit(int status) {
_zos_syscall1(SYS_EXIT, (long)status);
__builtin_unreachable();
}
void abort(void) {
_zos_syscall1(SYS_PRINT, (long)"abort() called\n");
_zos_syscall1(SYS_EXIT, 1);
__builtin_unreachable();
}
int system(const char *command) {
(void)command;
return -1;
}
/* ========================================================================
printf family — vsnprintf core
======================================================================== */
struct _pf_state {
char *buf;
size_t pos;
size_t max;
};
static void _pf_putc(struct _pf_state *st, char c) {
if (st->pos < st->max)
st->buf[st->pos] = c;
st->pos++;
}
static void _pf_puts(struct _pf_state *st, const char *s) {
while (*s) _pf_putc(st, *s++);
}
static void _pf_putnum(struct _pf_state *st, unsigned long val, int base, int upper, int width, char pad, int neg, int precision) {
char tmp[24];
int i = 0;
const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
if (val == 0) {
tmp[i++] = '0';
} else {
while (val > 0) {
tmp[i++] = digits[val % base];
val /= base;
}
}
int digitCount = i;
int precPad = 0;
if (precision > digitCount)
precPad = precision - digitCount;
int total = (neg ? 1 : 0) + precPad + digitCount;
if (neg && pad == '0' && precision < 0) {
_pf_putc(st, '-');
}
if (precision < 0 && pad == '0') {
for (int w = total; w < width; w++)
_pf_putc(st, '0');
} else {
for (int w = total; w < width; w++)
_pf_putc(st, ' ');
}
if (neg && !(pad == '0' && precision < 0)) {
_pf_putc(st, '-');
}
for (int p = 0; p < precPad; p++)
_pf_putc(st, '0');
while (i > 0)
_pf_putc(st, tmp[--i]);
}
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) {
struct _pf_state st;
st.buf = buf;
st.pos = 0;
st.max = size > 0 ? size - 1 : 0;
while (*fmt) {
if (*fmt != '%') {
_pf_putc(&st, *fmt++);
continue;
}
fmt++;
char pad = ' ';
int left_align = 0;
while (*fmt == '0' || *fmt == '-' || *fmt == '+' || *fmt == ' ') {
if (*fmt == '0') pad = '0';
if (*fmt == '-') { left_align = 1; pad = ' '; }
fmt++;
}
int width = 0;
if (*fmt == '*') {
width = va_arg(ap, int);
fmt++;
} else {
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt - '0');
fmt++;
}
}
int precision = -1;
if (*fmt == '.') {
fmt++;
precision = 0;
if (*fmt == '*') {
precision = va_arg(ap, int);
fmt++;
} else {
while (*fmt >= '0' && *fmt <= '9') {
precision = precision * 10 + (*fmt - '0');
fmt++;
}
}
}
int is_long = 0;
if (*fmt == 'l') { is_long = 1; fmt++; if (*fmt == 'l') { is_long = 2; fmt++; } }
else if (*fmt == 'h') { fmt++; if (*fmt == 'h') fmt++; }
else if (*fmt == 'z') { is_long = 1; fmt++; }
switch (*fmt) {
case 'd': case 'i': {
long val;
if (is_long >= 1) val = va_arg(ap, long);
else val = va_arg(ap, int);
int neg = 0;
unsigned long uval;
if (val < 0) { neg = 1; uval = (unsigned long)(-val); }
else uval = (unsigned long)val;
if (left_align) {
size_t before = st.pos;
_pf_putnum(&st, uval, 10, 0, 0, pad, neg, precision);
size_t len = st.pos - before;
for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' ');
} else {
_pf_putnum(&st, uval, 10, 0, width, pad, neg, precision);
}
break;
}
case 'u': {
unsigned long val;
if (is_long >= 1) val = va_arg(ap, unsigned long);
else val = va_arg(ap, unsigned int);
if (left_align) {
size_t before = st.pos;
_pf_putnum(&st, val, 10, 0, 0, ' ', 0, precision);
size_t len = st.pos - before;
for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' ');
} else {
_pf_putnum(&st, val, 10, 0, width, pad, 0, precision);
}
break;
}
case 'x': case 'X': {
unsigned long val;
if (is_long >= 1) val = va_arg(ap, unsigned long);
else val = va_arg(ap, unsigned int);
int upper = (*fmt == 'X');
if (left_align) {
size_t before = st.pos;
_pf_putnum(&st, val, 16, upper, 0, pad, 0, precision);
size_t len = st.pos - before;
for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' ');
} else {
_pf_putnum(&st, val, 16, upper, width, pad, 0, precision);
}
break;
}
case 'p': {
void *val = va_arg(ap, void *);
_pf_puts(&st, "0x");
_pf_putnum(&st, (unsigned long)val, 16, 0, 0, '0', 0, -1);
break;
}
case 's': {
const char *s = va_arg(ap, const char *);
if (s == NULL) s = "(null)";
int slen = (int)strlen(s);
if (precision >= 0 && precision < slen) slen = precision;
if (!left_align) {
for (int w = slen; w < width; w++) _pf_putc(&st, ' ');
}
for (int i = 0; i < slen; i++) _pf_putc(&st, s[i]);
if (left_align) {
for (int w = slen; w < width; w++) _pf_putc(&st, ' ');
}
break;
}
case 'c': {
char c = (char)va_arg(ap, int);
_pf_putc(&st, c);
break;
}
case '%':
_pf_putc(&st, '%');
break;
default:
_pf_putc(&st, '%');
_pf_putc(&st, *fmt);
break;
}
if (*fmt) fmt++;
}
if (size > 0) {
if (st.pos < size)
st.buf[st.pos] = '\0';
else
st.buf[size - 1] = '\0';
}
return (int)st.pos;
}
int snprintf(char *buf, size_t size, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
int sprintf(char *buf, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, (size_t)-1, fmt, ap);
va_end(ap);
return ret;
}
static char _printbuf[4096];
int printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(_printbuf, sizeof(_printbuf), fmt, ap);
va_end(ap);
_zos_syscall1(SYS_PRINT, (long)_printbuf);
return ret;
}
int puts(const char *s) {
_zos_syscall1(SYS_PRINT, (long)s);
_zos_syscall1(SYS_PUTCHAR, (long)'\n');
return 0;
}
int putchar(int c) {
_zos_syscall1(SYS_PUTCHAR, (long)c);
return c;
}
/* ========================================================================
assert.h support
======================================================================== */
void __assert_fail(const char *expr, const char *file, int line, const char *func) {
_zos_syscall1(SYS_PRINT, (long)"Assertion failed: ");
_zos_syscall1(SYS_PRINT, (long)expr);
_zos_syscall1(SYS_PRINT, (long)" at ");
_zos_syscall1(SYS_PRINT, (long)file);
_zos_syscall1(SYS_PRINT, (long)"\n");
(void)line; (void)func;
abort();
}
Binary file not shown.
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
.TH FETCH 1
.SH NAME
fetch - HTTP/HTTPS client for ZenithOS
.SH SYNOPSIS
fetch [-v] <url>
fetch [-v] <host> <port> [path]
.SH DESCRIPTION
fetch performs an HTTP/1.0 GET request and prints the response
body to the terminal. Supports both plain HTTP and HTTPS (TLS 1.2)
connections. By default only the body is printed.
In URL mode, the scheme (http:// or https://) determines whether
TLS is used. The port defaults to 80 for HTTP and 443 for HTTPS.
In legacy mode, the host and port are specified as separate
arguments and the connection is always plain HTTP.
The host may be an IP address or a hostname. Hostnames are
resolved via the configured DNS server.
If no path is given, "/" is used.
.SH OPTIONS
.B -v
Verbose mode. Print connection info, trust anchor count, TLS
handshake progress, and the HTTP status/size header before
the body.
.SH EXAMPLES
fetch https://icanhazip.com
Print your public IP address over HTTPS.
fetch http://icanhazip.com
Same, but over plain HTTP.
fetch -v https://example.com
Fetch a page with verbose output showing:
Connecting to example.com:443 (HTTPS)...
Loaded 128 trust anchors
TLS handshake...
TLS connection established
GET /
HTTP 200 OK (1256 bytes)
fetch 10.0.68.1 80 /
Fetch from a local server by IP (legacy syntax).
.SH TLS SUPPORT
HTTPS connections use BearSSL for TLS 1.2. Server certificates
are validated against the system CA bundle at
0:/etc/ca-certificates.crt.
Entropy for the TLS handshake is provided by RDTSC-seeded
random data via the SYS_GETRANDOM syscall.
.SH KEYBOARD
Ctrl+Q Abort the request
.SH SEE ALSO
ping(1), nslookup(1), tcpconnect(1), shell(1), syscalls(2)
+10 -1
View File
@@ -67,8 +67,17 @@
}
zenith::close(h);
.SH WRITING FILES
Files can be created and written on the ramdisk:
.BI int zenith::fcreate(const char* path);
.BI int zenith::fwrite(int handle, const uint8_t* buf, uint64_t offset, uint64_t size);
fcreate creates a new file and returns a handle. fwrite writes
bytes at the given offset. Changes persist only until reboot --
the ramdisk is reloaded from the USTAR archive on each boot.
.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.
+43
View File
@@ -0,0 +1,43 @@
.TH FONTSCALE 1
.SH NAME
fontscale - get or set terminal font scale
.SH SYNOPSIS
fontscale
fontscale <n>
fontscale <x> <y>
.SH DESCRIPTION
Controls the terminal font scale factor. The Flanterm terminal
emulator renders text at a configurable scale multiplier.
Increasing the scale makes text larger, which is useful on
high-resolution displays or real hardware where text may be
too small to read comfortably.
With no arguments, prints the current scale factor and terminal
dimensions.
With one argument, sets both the horizontal and vertical scale
to the same value.
With two arguments, sets asymmetric horizontal and vertical
scale factors independently.
Valid scale values are 1 through 8. After rescaling, the screen
is cleared.
.SH OUTPUT
fontscale
Scale: 1x1 (160 cols x 50 rows)
fontscale 2
Scale set to 2x2 (80 cols x 25 rows)
.SH EXAMPLES
fontscale Show current scale and dimensions
fontscale 2 Double the font size
fontscale 3 2 3x horizontal, 2x vertical
fontscale 1 Reset to default size
.SH SEE ALSO
shell(1), syscalls(2)
+5
View File
@@ -53,6 +53,11 @@
shell(1) Shell commands reference
init(1) Init system
dhcp(1) DHCP client
fetch(1) HTTP client
ping(1) ICMP ping
nslookup(1) DNS lookup
fontscale(1) Terminal font scaling
edit(1) Text editor
man(1) The man command itself
legal(7) Copyright and legal information
syscalls(2) Overview of all syscalls
+36
View File
@@ -0,0 +1,36 @@
.TH NSLOOKUP 1
.SH NAME
nslookup - DNS hostname lookup
.SH SYNOPSIS
nslookup <hostname>
.SH DESCRIPTION
Resolves a hostname to an IPv4 address using the configured
DNS server and prints the result.
The kernel DNS resolver sends a UDP query to port 53 of the
configured DNS server and waits up to 5 seconds for a reply.
Results are cached in an 8-entry kernel cache with TTL support.
.SH OUTPUT
Server: 10.0.68.1
Name: example.com
Address: 93.184.216.34
Time: 3ms
If the lookup fails:
Could not resolve: badhost.invalid
.SH DNS CONFIGURATION
The DNS server address is obtained automatically via DHCP.
It can also be viewed and set with ifconfig. The default
is 10.0.68.1 (QEMU user-mode networking).
.SH EXAMPLES
nslookup google.com
nslookup icanhazip.com
.SH SEE ALSO
ping(1), fetch(1), dhcp(1), ifconfig(1), syscalls(2)
+37
View File
@@ -0,0 +1,37 @@
.TH PING 1
.SH NAME
ping - send ICMP echo requests
.SH SYNOPSIS
ping <host>
.SH DESCRIPTION
Sends 4 ICMP echo requests to the specified host and prints
the round-trip time for each reply.
The host may be an IP address or a hostname. Hostnames are
resolved via the configured DNS server.
Each request has a 3-second timeout. Requests are sent at
1-second intervals.
.SH OUTPUT
PING example.com (93.184.216.34)
Reply from 93.184.216.34: time=12ms
Reply from 93.184.216.34: time=11ms
Reply from 93.184.216.34: time=13ms
Reply from 93.184.216.34: time=11ms
If a reply is not received within the timeout:
Request timed out
.SH EXAMPLES
ping 10.0.68.1
Ping the gateway by IP address.
ping google.com
Ping by hostname (requires DNS).
.SH SEE ALSO
nslookup(1), ifconfig(1), shell(1), syscalls(2)
+9 -3
View File
@@ -61,18 +61,24 @@
date Show current date and time
uptime Show system uptime
clear Clear the screen and framebuffer
fontscale [n] Get or set terminal font scale
reset Reboot the system
shutdown Shut down the system
.SS Network commands (0:/os/)
ping <ip> Send ICMP echo requests
ping <host> Send ICMP echo requests
nslookup <host> DNS lookup
ifconfig Show/set network configuration
tcpconnect <ip> <port> Interactive TCP client
tcpconnect <host> <port> Interactive TCP client
irc IRC client
dhcp DHCP client
fetch HTTP client
fetch HTTP/HTTPS client (TLS 1.2)
wiki Wikipedia article viewer
httpd HTTP server
Network commands accept both IP addresses and hostnames.
Hostnames are resolved via the configured DNS server.
.SS Games (0:/games/)
doom DOOM
+37 -3
View File
@@ -3,7 +3,7 @@
syscalls - overview of ZenithOS system calls
.SH DESCRIPTION
ZenithOS provides 41 system calls (numbers 0-40) for userspace
ZenithOS provides 46 system calls (numbers 0-45) for userspace
programs. Syscalls use the x86-64 SYSCALL instruction with the
following register convention:
@@ -121,13 +121,22 @@
int32_t zenith::ping(uint32_t ip, uint32_t timeoutMs);
.B SYS_GETNETCFG (37)
Get the current network configuration (IP, mask, gateway, MAC).
Get the current network configuration (IP, mask, gateway, MAC,
DNS server).
void zenith::get_netcfg(Zenith::NetCfg* out);
.B SYS_SETNETCFG (38)
Set the network configuration (IP, mask, gateway).
Set the network configuration (IP, mask, gateway, DNS server).
int zenith::set_netcfg(const Zenith::NetCfg* cfg);
.B SYS_RESOLVE (44)
Resolve a hostname to an IPv4 address via DNS. Sends a UDP
query to the configured DNS server and waits up to 5 seconds
for a reply. Returns the IP in network byte order, or 0 on
failure. IP address strings (e.g. "10.0.0.1") are detected
and returned directly without a DNS query.
uint32_t zenith::resolve(const char* hostname);
.SH SOCKETS
.B SYS_SOCKET (29)
Create a socket. type=SOCK_TCP (1) or SOCK_UDP (2).
@@ -174,6 +183,17 @@
int zenith::recvfrom(int fd, void* buf, uint32_t maxLen,
uint32_t* srcIp, uint16_t* srcPort);
.SH FILE WRITE
.B SYS_FWRITE (41)
Write bytes to a file at a given offset.
int zenith::fwrite(int handle, const uint8_t* buf,
uint64_t offset, uint64_t size);
.B SYS_FCREATE (42)
Create a new file on the ramdisk. Returns a handle or negative
on error.
int zenith::fcreate(const char* path);
.SH FRAMEBUFFER
.B SYS_FBINFO (21)
Get framebuffer dimensions and format.
@@ -188,11 +208,25 @@
Get terminal dimensions (columns and rows).
void zenith::termsize(int* cols, int* rows);
.B SYS_TERMSCALE (43)
Get or set the terminal font scale factor. When scale_x is 0,
returns the current scale as (scale_y << 32 | scale_x). When
scale_x is non-zero, sets the font scale and returns the new
terminal dimensions as (rows << 32 | cols).
void zenith::termscale(int scale_x, int scale_y);
void zenith::get_termscale(int* scale_x, int* scale_y);
.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 RANDOM
.B SYS_GETRANDOM (45)
Fill a buffer with random bytes using RDTSC-seeded entropy.
Returns the number of bytes written.
int64_t zenith::getrandom(void* buf, uint32_t len);
.SH POWER MANAGEMENT
.B SYS_RESET (26)
Reboot the system.
+237
View File
@@ -0,0 +1,237 @@
.TH TLS-ERRORS 5
.SH NAME
tls-errors - BearSSL TLS and X.509 error codes
.SH DESCRIPTION
ZenithOS uses BearSSL for TLS 1.2 connections. When a TLS
operation fails, an integer error code is reported. This page
lists all possible error codes.
.SH SSL/TLS ENGINE ERRORS
.B 0 BR_ERR_OK
No error.
.B 1 BR_ERR_BAD_PARAM
Caller-provided parameter is incorrect.
.B 2 BR_ERR_BAD_STATE
Operation cannot be applied in the current engine state.
.B 3 BR_ERR_UNSUPPORTED_VERSION
Incoming protocol or record version is unsupported.
.B 4 BR_ERR_BAD_VERSION
Incoming record version does not match the expected version.
.B 5 BR_ERR_BAD_LENGTH
Incoming record length is invalid.
.B 6 BR_ERR_TOO_LARGE
Incoming record is too large, or buffer is too small for the
handshake message to send.
.B 7 BR_ERR_BAD_MAC
Decryption found invalid padding, or the record MAC is
not correct.
.B 8 BR_ERR_NO_RANDOM
No initial entropy was provided and none could be obtained
from the OS.
.B 9 BR_ERR_UNKNOWN_TYPE
Incoming record type is unknown.
.B 10 BR_ERR_UNEXPECTED
Incoming record or message has wrong type for the current
engine state.
.B 12 BR_ERR_BAD_CCS
ChangeCipherSpec message from the peer has invalid contents.
.B 13 BR_ERR_BAD_ALERT
Alert message from the peer has invalid contents (odd length).
.B 14 BR_ERR_BAD_HANDSHAKE
Incoming handshake message decoding failed.
.B 15 BR_ERR_OVERSIZED_ID
ServerHello contains a session ID larger than 32 bytes.
.B 16 BR_ERR_BAD_CIPHER_SUITE
Server wants to use a cipher suite that we did not advertise,
or we tried to advertise a cipher suite that we do not support.
.B 17 BR_ERR_BAD_COMPRESSION
Server wants to use a compression method that we did not
advertise.
.B 18 BR_ERR_BAD_FRAGLEN
Server's max fragment length does not match client's.
.B 19 BR_ERR_BAD_SECRENEG
Secure renegotiation failed.
.B 20 BR_ERR_EXTRA_EXTENSION
Server sent an extension type that we did not announce, or
used the same extension type more than once in ServerHello.
.B 21 BR_ERR_BAD_SNI
Invalid Server Name Indication contents (when used by the
server, this extension shall be empty).
.B 22 BR_ERR_BAD_HELLO_DONE
Invalid ServerHelloDone from the server (length is not 0).
.B 23 BR_ERR_LIMIT_EXCEEDED
Internal limit exceeded (e.g. server's public key is too
large).
.B 24 BR_ERR_BAD_FINISHED
Finished message from peer does not match the expected value.
.B 25 BR_ERR_RESUME_MISMATCH
Session resumption attempted with a different version or
cipher suite.
.B 26 BR_ERR_INVALID_ALGORITHM
Unsupported or invalid algorithm (ECDHE curve, signature
algorithm, hash function).
.B 27 BR_ERR_BAD_SIGNATURE
Invalid signature on ServerKeyExchange or CertificateVerify.
.B 28 BR_ERR_WRONG_KEY_USAGE
Peer's public key does not have the proper type or is not
allowed for the requested operation.
.B 29 BR_ERR_NO_CLIENT_AUTH
Client did not send a certificate upon request, or the client
certificate could not be validated.
.B 31 BR_ERR_IO
I/O error or premature close on the underlying transport.
.SH X.509 CERTIFICATE ERRORS
.B 32 BR_ERR_X509_OK
X.509 validation was successful (not an error).
.B 33 BR_ERR_X509_INVALID_VALUE
Invalid value in an ASN.1 structure.
.B 34 BR_ERR_X509_TRUNCATED
Truncated certificate.
.B 35 BR_ERR_X509_EMPTY_CHAIN
Empty certificate chain (no certificate at all).
.B 36 BR_ERR_X509_INNER_TRUNC
Inner element extends beyond outer element size.
.B 37 BR_ERR_X509_BAD_TAG_CLASS
Unsupported tag class (application or private).
.B 38 BR_ERR_X509_BAD_TAG_VALUE
Unsupported tag value.
.B 39 BR_ERR_X509_INDEFINITE_LENGTH
Indefinite length encoding found.
.B 40 BR_ERR_X509_EXTRA_ELEMENT
Extraneous element in certificate.
.B 41 BR_ERR_X509_UNEXPECTED
Unexpected element in certificate.
.B 42 BR_ERR_X509_NOT_CONSTRUCTED
Expected constructed element, but found primitive.
.B 43 BR_ERR_X509_NOT_PRIMITIVE
Expected primitive element, but found constructed.
.B 44 BR_ERR_X509_PARTIAL_BYTE
BIT STRING length is not a multiple of 8.
.B 45 BR_ERR_X509_BAD_BOOLEAN
BOOLEAN value has invalid length.
.B 46 BR_ERR_X509_OVERFLOW
Value is off-limits (overflow).
.B 47 BR_ERR_X509_BAD_DN
Invalid distinguished name.
.B 48 BR_ERR_X509_BAD_TIME
Invalid date/time representation in certificate.
.B 49 BR_ERR_X509_UNSUPPORTED
Certificate contains unsupported features that cannot be
ignored.
.B 50 BR_ERR_X509_LIMIT_EXCEEDED
Key or signature size exceeds internal limits.
.B 51 BR_ERR_X509_WRONG_KEY_TYPE
Key type does not match that which was expected.
.B 52 BR_ERR_X509_BAD_SIGNATURE
Signature is invalid.
.B 53 BR_ERR_X509_TIME_UNKNOWN
Validation time is unknown (no time was set).
.B 54 BR_ERR_X509_EXPIRED
Certificate is expired or not yet valid.
.B 55 BR_ERR_X509_DN_MISMATCH
Issuer/subject DN mismatch in the chain.
.B 56 BR_ERR_X509_BAD_SERVER_NAME
Expected server name was not found in the chain.
.B 57 BR_ERR_X509_CRITICAL_EXTENSION
Unknown critical extension in certificate.
.B 58 BR_ERR_X509_NOT_CA
Not a CA, or path length constraint violation.
.B 59 BR_ERR_X509_FORBIDDEN_KEY_USAGE
Key Usage extension prohibits the intended usage.
.B 60 BR_ERR_X509_WEAK_PUBLIC_KEY
Public key found in certificate is too small.
.B 62 BR_ERR_X509_NOT_TRUSTED
Chain could not be linked to a trust anchor.
.SH FATAL ALERTS
When a fatal alert is received from the peer, the error code
is 256 + the TLS alert value. When a fatal alert is sent to
the peer, the error code is 512 + the TLS alert value.
Common alert values:
0 close_notify
10 unexpected_message
20 bad_record_mac
40 handshake_failure
42 bad_certificate
43 unsupported_certificate
44 certificate_revoked
45 certificate_expired
46 certificate_unknown
47 illegal_parameter
48 unknown_ca
50 decode_error
51 decrypt_error
70 protocol_version
71 insufficient_security
80 internal_error
86 unrecognized_name
112 no_application_protocol
For example, error 296 means a handshake_failure alert was
received (256 + 40 = 296).
.SH SEE ALSO
fetch(1), syscalls(2)
+64
View File
@@ -0,0 +1,64 @@
.TH WIKI 1
.SH NAME
wiki - Wikipedia article viewer for ZenithOS
.SH SYNOPSIS
wiki <title>
wiki -f <title>
wiki -s <query>
.SH DESCRIPTION
wiki fetches and displays Wikipedia articles in the terminal.
It connects to en.wikipedia.org over HTTPS (TLS 1.2) and
uses the Wikipedia REST and Action APIs to retrieve article
content as plain text.
Articles are displayed in a fullscreen interactive pager with
color-coded headings and word-wrapped text. Multi-word titles
are accepted as separate arguments and joined automatically.
.SH OPTIONS
.B -f
Full article mode. Display the complete article text instead
of just the summary. Section headings are color-coded.
.B -s
Search mode. Search Wikipedia for articles matching the
query and display a numbered list of up to 10 results.
Press a number key to view that article's summary.
.SH EXAMPLES
wiki Linux
Show a summary of the Linux article.
wiki -f C programming language
Show the full text of the C programming language article.
wiki -s operating system
Search for articles related to "operating system".
.SH TLS SUPPORT
Connections use BearSSL for TLS 1.2. Server certificates
are validated against the system CA bundle at
0:/etc/ca-certificates.crt.
.SH KEYBOARD
.SS Article pager
j / Down Scroll down one line
k / Up Scroll up one line
Space / PgDn Scroll down one page
b / PgUp Scroll up one page
g / Home Jump to top
G / End Jump to bottom
q Quit pager
.SS Search results
1-9, 0 View article (0 = result 10)
q Quit search
.SS General
Ctrl+Q Abort during network request
.SH SEE ALSO
fetch(1), ping(1), nslookup(1), shell(1)
Binary file not shown.
Binary file not shown.
+1
View File
@@ -463,6 +463,7 @@ extern "C" void _start() {
newCfg.ipAddress = offer.offeredIp;
newCfg.subnetMask = offer.subnetMask;
newCfg.gateway = offer.router;
newCfg.dnsServer = offer.dns;
zenith::set_netcfg(&newCfg);
// 9. Print results
+89
View File
@@ -0,0 +1,89 @@
# Makefile for fetch (HTTP/HTTPS client) on ZenithOS
# Copyright (c) 2025-2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
# ---- Toolchain ----
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
# ---- Paths ----
BEARSSL := ../../lib/bearssl
LIBC_LIB := ../../lib/libc
LIBC_INC := ../../include/libc
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
# ---- Compiler flags ----
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-mno-80387 \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(LIBC_INC) \
-I $(BEARSSL)/inc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/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)
# ---- Libraries ----
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
# ---- Target ----
TARGET := $(BINDIR)/os/fetch.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJDIR)/main.o $(LIBS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJDIR)/main.o $(LIBS) -o $@
$(OBJDIR)/main.o: main.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
File diff suppressed because it is too large Load Diff
Binary file not shown.
+88
View File
@@ -0,0 +1,88 @@
/*
* main.cpp
* fontscale - Change terminal font scale
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
static int atoi(const char* s) {
int n = 0;
while (*s >= '0' && *s <= '9') {
n = n * 10 + (*s - '0');
s++;
}
return n;
}
static void print_int(int n) {
char buf[16];
int i = 0;
if (n == 0) {
zenith::putchar('0');
return;
}
while (n > 0 && i < 15) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
while (i > 0) zenith::putchar(buf[--i]);
}
extern "C" void _start() {
char args[128];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
// No args: show current scale
int sx, sy;
zenith::get_termscale(&sx, &sy);
int cols, rows;
zenith::termsize(&cols, &rows);
zenith::print("Font scale: ");
print_int(sx);
zenith::print("x");
print_int(sy);
zenith::print(" Terminal: ");
print_int(cols);
zenith::print("x");
print_int(rows);
zenith::putchar('\n');
zenith::exit(0);
}
// Parse arguments
const char* p = zenith::skip_spaces(args);
int scale_x = atoi(p);
// Skip past first number to find optional second
while (*p >= '0' && *p <= '9') p++;
p = zenith::skip_spaces(p);
int scale_y = (*p >= '1' && *p <= '8') ? atoi(p) : scale_x;
if (scale_x < 1 || scale_x > 8 || scale_y < 1 || scale_y > 8) {
zenith::print("fontscale: scale must be 1-8\n");
zenith::exit(1);
}
zenith::termscale(scale_x, scale_y);
// Clear and show result
zenith::print("\033[2J\033[H");
int cols, rows;
zenith::termsize(&cols, &rows);
zenith::print("Font scale set to ");
print_int(scale_x);
zenith::print("x");
print_int(scale_y);
zenith::print(" (");
print_int(cols);
zenith::print("x");
print_int(rows);
zenith::print(")\n");
zenith::exit(0);
}
+1 -1
View File
@@ -1,7 +1,7 @@
/*
* main.cpp
* HTTP/1.0 server for ZenithOS
* Usage: run httpd.elf [port] (default: 80)
* Usage: httpd [port] (default: 80)
* Serves a built-in index page and files from the VFS
* Copyright (c) 2025-2026 Daniel Hammer
*/
+3
View File
@@ -81,6 +81,9 @@ extern "C" void _start() {
zenith::print(" Gateway: ");
print_ip(cfg.gateway);
zenith::putchar('\n');
zenith::print(" DNS Server: ");
print_ip(cfg.dnsServer);
zenith::putchar('\n');
zenith::exit(0);
}
+15 -12
View File
@@ -836,23 +836,26 @@ extern "C" void _start() {
const char* arg = skip_spaces(argbuf);
if (*arg == '\0') {
zenith::print("Usage: irc.elf <server_ip> <port> <nickname> [#channel]\n");
zenith::print("Example: run irc.elf 10.0.68.1 6667 ZenithUser #general\n");
zenith::print("Usage: irc <server> <port> <nickname> [#channel]\n");
zenith::print("Example: irc irc.libera.chat 6667 ZenithUser #general\n");
return;
}
// Parse IP
char ipStr[32];
// Parse host (IP or hostname)
char hostStr[128];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 31) { ipStr[i] = arg[i]; i++; }
ipStr[i] = '\0';
while (arg[i] && arg[i] != ' ' && i < 127) { hostStr[i] = arg[i]; i++; }
hostStr[i] = '\0';
arg = skip_spaces(arg + i);
if (!parse_ip(ipStr, &irc.serverIp)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
return;
if (!parse_ip(hostStr, &irc.serverIp)) {
irc.serverIp = zenith::resolve(hostStr);
if (irc.serverIp == 0) {
zenith::print("Could not resolve: ");
zenith::print(hostStr);
zenith::putchar('\n');
return;
}
}
// Parse port
@@ -910,7 +913,7 @@ extern "C" void _start() {
// Initial draw
msg_add("\033[1m*** ZenithOS IRC Client\033[0m");
msg_add_fmt("*** Connecting to %s:%d as %s...", ipStr, (int)irc.serverPort, irc.nick);
msg_add_fmt("*** Connecting to %s:%d as %s...", hostStr, (int)irc.serverPort, irc.nick);
ui_render();
// Create socket and connect
+83
View File
@@ -0,0 +1,83 @@
/*
* main.cpp
* DNS lookup utility for ZenithOS
* Usage: nslookup <hostname>
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::skip_spaces;
static void print_ip(uint32_t ip) {
auto print_int = [](uint32_t n) {
if (n == 0) { zenith::putchar('0'); return; }
char buf[4];
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]);
};
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
const char* hostname = skip_spaces(args);
if (len <= 0 || hostname[0] == '\0') {
zenith::print("Usage: nslookup <hostname>\n");
zenith::print("Example: nslookup example.com\n");
zenith::exit(0);
}
// Show DNS server
Zenith::NetCfg cfg;
zenith::get_netcfg(&cfg);
zenith::print("Server: ");
print_ip(cfg.dnsServer);
zenith::putchar('\n');
zenith::print("Querying ");
zenith::print(hostname);
zenith::print("...\n");
uint64_t start = zenith::get_milliseconds();
uint32_t ip = zenith::resolve(hostname);
uint64_t elapsed = zenith::get_milliseconds() - start;
if (ip == 0) {
zenith::print("Error: could not resolve ");
zenith::print(hostname);
zenith::putchar('\n');
zenith::exit(1);
}
zenith::print("Name: ");
zenith::print(hostname);
zenith::putchar('\n');
zenith::print("Address: ");
print_ip(ip);
zenith::putchar('\n');
// Print timing
zenith::print("Time: ");
char buf[20];
int i = 0;
uint64_t ms = elapsed;
if (ms == 0) { buf[i++] = '0'; }
else { while (ms > 0) { buf[i++] = '0' + (ms % 10); ms /= 10; } }
for (int j = i - 1; j >= 0; j--) zenith::putchar(buf[j]);
zenith::print(" ms\n");
zenith::exit(0);
}
+11 -6
View File
@@ -65,21 +65,26 @@ extern "C" void _start() {
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: ping <ip address>\n");
zenith::print("Usage: ping <host>\n");
zenith::exit(1);
}
uint32_t ip;
if (!parse_ip(args, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(args);
zenith::putchar('\n');
zenith::exit(1);
ip = zenith::resolve(args);
if (ip == 0) {
zenith::print("Could not resolve: ");
zenith::print(args);
zenith::putchar('\n');
zenith::exit(1);
}
}
zenith::print("PING ");
zenith::print(args);
zenith::print(" (");
print_ip(ip);
zenith::putchar('\n');
zenith::print(")\n");
for (int i = 0; i < 4; i++) {
int32_t rtt = zenith::ping(ip, 3000);
+2
View File
@@ -118,11 +118,13 @@ static void cmd_help() {
zenith::print(" date Show current date and time\n");
zenith::print(" uptime Show uptime\n");
zenith::print(" clear Clear the screen\n");
zenith::print(" fontscale [n] Set terminal font scale (1-8)\n");
zenith::print(" reset Reboot the system\n");
zenith::print(" shutdown Shut down the system\n");
zenith::print("\n");
zenith::print("Network commands:\n");
zenith::print(" ping <ip> Send ICMP echo requests\n");
zenith::print(" nslookup DNS lookup\n");
zenith::print(" ifconfig Show/set network configuration\n");
zenith::print(" tcpconnect Connect to a TCP server\n");
zenith::print(" irc IRC client\n");
+14 -11
View File
@@ -81,25 +81,28 @@ extern "C" void _start() {
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
zenith::print("Usage: tcpconnect <host> <port>\n");
zenith::exit(1);
}
// Parse IP address (up to first space)
char ipStr[32];
// Parse host (IP or hostname)
char hostStr[128];
int i = 0;
while (args[i] && args[i] != ' ' && i < 31) {
ipStr[i] = args[i];
while (args[i] && args[i] != ' ' && i < 127) {
hostStr[i] = args[i];
i++;
}
ipStr[i] = '\0';
hostStr[i] = '\0';
uint32_t ip;
if (!parse_ip(ipStr, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
zenith::exit(1);
if (!parse_ip(hostStr, &ip)) {
ip = zenith::resolve(hostStr);
if (ip == 0) {
zenith::print("Could not resolve: ");
zenith::print(hostStr);
zenith::putchar('\n');
zenith::exit(1);
}
}
// Parse port
+89
View File
@@ -0,0 +1,89 @@
# Makefile for wiki (Wikipedia client) on ZenithOS
# Copyright (c) 2025-2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
# ---- Toolchain ----
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
# ---- Paths ----
BEARSSL := ../../lib/bearssl
LIBC_LIB := ../../lib/libc
LIBC_INC := ../../include/libc
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
# ---- Compiler flags ----
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-mno-80387 \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(LIBC_INC) \
-I $(BEARSSL)/inc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/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)
# ---- Libraries ----
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
# ---- Target ----
TARGET := $(BINDIR)/os/wiki.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJDIR)/main.o $(LIBS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJDIR)/main.o $(LIBS) -o $@
$(OBJDIR)/main.o: main.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
File diff suppressed because it is too large Load Diff
Binary file not shown.
BIN
View File
Binary file not shown.