feat: e100e Ethernet driver, ramdisk reorganization, shell rewrite, web server/client, and more

This commit is contained in:
2026-02-19 01:18:11 +01:00
parent 1a5d943649
commit d355d376f9
63 changed files with 5368 additions and 614 deletions
+71 -1
View File
@@ -20,6 +20,9 @@
#include <Net/Icmp.hpp>
#include <Net/Socket.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Hal/MSR.hpp>
#include <Hal/GDT.hpp>
#include <Graphics/Cursor.hpp>
@@ -303,6 +306,45 @@ namespace Zenith {
Net::Socket::Close(fd, Sched::GetCurrentPid());
}
static int Sys_SendTo(int fd, const uint8_t* data, uint32_t len,
uint32_t destIp, uint16_t destPort) {
return Net::Socket::SendTo(fd, data, len, destIp, destPort, Sched::GetCurrentPid());
}
static int Sys_RecvFrom(int fd, uint8_t* buf, uint32_t maxLen,
uint32_t* srcIp, uint16_t* srcPort) {
return Net::Socket::RecvFrom(fd, buf, maxLen, srcIp, srcPort, Sched::GetCurrentPid());
}
static void Sys_GetNetCfg(NetCfg* out) {
if (out == nullptr) return;
out->ipAddress = Net::GetIpAddress();
out->subnetMask = Net::GetSubnetMask();
out->gateway = Net::GetGateway();
const uint8_t* mac = nullptr;
if (Drivers::Net::E1000::IsInitialized()) {
mac = Drivers::Net::E1000::GetMacAddress();
} else if (Drivers::Net::E1000E::IsInitialized()) {
mac = Drivers::Net::E1000E::GetMacAddress();
}
if (mac) {
for (int i = 0; i < 6; i++) out->macAddress[i] = mac[i];
} else {
for (int i = 0; i < 6; i++) out->macAddress[i] = 0;
}
out->_pad[0] = 0;
out->_pad[1] = 0;
}
static int Sys_SetNetCfg(const NetCfg* in) {
if (in == nullptr) return -1;
Net::SetIpAddress(in->ipAddress);
Net::SetSubnetMask(in->subnetMask);
Net::SetGateway(in->gateway);
return 0;
}
static void Sys_Reset() {
/*
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
@@ -320,6 +362,16 @@ namespace Zenith {
__builtin_unreachable();
}
// ---- File write/create ----
static int Sys_FWrite(int handle, const uint8_t* data, uint64_t offset, uint64_t size) {
return Fs::Vfs::VfsWrite(handle, data, offset, size);
}
static int Sys_FCreate(const char* path) {
return Fs::Vfs::VfsCreate(path);
}
// ---- Dispatch ----
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
@@ -416,6 +468,24 @@ namespace Zenith {
case SYS_CLOSESOCK:
Sys_CloseSock((int)frame->arg1);
return 0;
case SYS_GETNETCFG:
Sys_GetNetCfg((NetCfg*)frame->arg1);
return 0;
case SYS_SETNETCFG:
return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1);
case SYS_SENDTO:
return (int64_t)Sys_SendTo((int)frame->arg1, (const uint8_t*)frame->arg2,
(uint32_t)frame->arg3, (uint32_t)frame->arg4,
(uint16_t)frame->arg5);
case SYS_RECVFROM:
return (int64_t)Sys_RecvFrom((int)frame->arg1, (uint8_t*)frame->arg2,
(uint32_t)frame->arg3, (uint32_t*)frame->arg4,
(uint16_t*)frame->arg5);
case SYS_FWRITE:
return (int64_t)Sys_FWrite((int)frame->arg1, (const uint8_t*)frame->arg2,
frame->arg3, frame->arg4);
case SYS_FCREATE:
return (int64_t)Sys_FCreate((const char*)frame->arg1);
default:
return -1;
}
@@ -442,7 +512,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 << ", 37 syscalls)";
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 43 syscalls)";
}
}
+15
View File
@@ -48,8 +48,15 @@ namespace Zenith {
static constexpr uint64_t SYS_SEND = 34;
static constexpr uint64_t SYS_RECV = 35;
static constexpr uint64_t SYS_CLOSESOCK = 36;
static constexpr uint64_t SYS_GETNETCFG = 37;
static constexpr uint64_t SYS_SETNETCFG = 38;
static constexpr uint64_t SYS_SENDTO = 39;
static constexpr uint64_t SYS_RECVFROM = 40;
static constexpr uint64_t SYS_FWRITE = 41;
static constexpr uint64_t SYS_FCREATE = 42;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
struct DateTime {
uint16_t Year;
@@ -75,6 +82,14 @@ namespace Zenith {
uint32_t maxProcesses;
};
struct NetCfg {
uint32_t ipAddress; // network byte order
uint32_t subnetMask; // network byte order
uint32_t gateway; // network byte order
uint8_t macAddress[6];
uint8_t _pad[2];
};
struct KeyEvent {
uint8_t scancode;
char ascii;
+763
View File
@@ -0,0 +1,763 @@
/*
* E1000E.cpp
* Intel I217/I218/I219 (E1000E) Ethernet driver
* Copyright (c) 2025 Daniel Hammer
*/
#include "E1000E.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 <Libraries/Memory.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
using namespace Kt;
namespace Drivers::Net::E1000E {
// Supported device entry
struct DeviceEntry {
uint16_t DeviceId;
const char* Name;
};
// PCI vendor ID for Intel
static constexpr uint16_t VendorIntel = 0x8086;
// Device ID table for I217/I218/I219 series
static constexpr DeviceEntry g_deviceTable[] = {
// I217
{ 0x153A, "I217-LM" },
{ 0x153B, "I217-V" },
// I218
{ 0x155A, "I218-LM" },
{ 0x1559, "I218-V" },
{ 0x15A0, "I218-LM (2)" },
{ 0x15A1, "I218-V (2)" },
{ 0x15A2, "I218-LM (3)" },
{ 0x15A3, "I218-V (3)" },
// I219-LM variants
{ 0x156F, "I219-LM" },
{ 0x15B7, "I219-LM (2)" },
{ 0x15BB, "I219-LM (3)" },
{ 0x15BD, "I219-LM (4)" },
{ 0x15DF, "I219-LM (5)" },
{ 0x15E1, "I219-LM (6)" },
{ 0x15E3, "I219-LM (7)" },
{ 0x15D7, "I219-LM (8)" },
{ 0x0D4C, "I219-LM (9)" },
{ 0x0D4E, "I219-LM (10)" },
{ 0x0D53, "I219-LM (11)" },
{ 0x0D55, "I219-LM (12)" },
{ 0x0DC5, "I219-LM (13)" },
{ 0x0DC7, "I219-LM (14)" },
{ 0x1A1C, "I219-LM (15)" },
{ 0x1A1E, "I219-LM (16)" },
// I219-V variants
{ 0x1570, "I219-V" },
{ 0x15B8, "I219-V (2)" },
{ 0x15BC, "I219-V (3)" },
{ 0x15BE, "I219-V (4)" },
{ 0x15E0, "I219-V (5)" },
{ 0x15E2, "I219-V (6)" },
{ 0x15D6, "I219-V (7)" },
{ 0x15D8, "I219-V (8)" },
{ 0x0D4D, "I219-V (9)" },
{ 0x0D4F, "I219-V (10)" },
{ 0x0D54, "I219-V (11)" },
{ 0x0DC6, "I219-V (13)" },
{ 0x0DC8, "I219-V (14)" },
{ 0x1A1D, "I219-V (15)" },
{ 0x1A1F, "I219-V (16)" },
};
static constexpr uint32_t g_deviceTableSize = sizeof(g_deviceTable) / sizeof(g_deviceTable[0]);
// PCI config space offsets
static constexpr uint8_t PCI_REG_BAR0 = 0x10;
static constexpr uint8_t PCI_REG_BAR1 = 0x14;
static constexpr uint8_t PCI_REG_COMMAND = 0x04;
static constexpr uint8_t PCI_REG_INTERRUPT = 0x3C;
// PCI command register bits
static constexpr uint16_t PCI_CMD_BUS_MASTER = (1 << 2);
static constexpr uint16_t PCI_CMD_MEM_SPACE = (1 << 1);
static constexpr uint16_t PCI_CMD_INTX_DISABLE = (1 << 10);
// MSI configuration
static constexpr uint8_t MSI_IRQ = 24; // IRQ slot 24 = vector 56 (first MSI slot)
static constexpr uint32_t MSI_VECTOR = 56; // IRQ_VECTOR_BASE + MSI_IRQ
static constexpr uint32_t MSI_ADDR_BASE = 0xFEE00000; // Local APIC message address range
// Driver state
static bool g_initialized = false;
static bool g_pollingMode = false;
static volatile uint8_t* g_mmioBase = nullptr;
static uint8_t g_macAddress[6] = {};
static uint8_t g_irqLine = 0;
// Descriptor rings (physical addresses for DMA, virtual for CPU access)
static RxDescriptor* g_rxDescs = nullptr;
static TxDescriptor* g_txDescs = nullptr;
static uint64_t g_rxDescsPhys = 0;
static uint64_t g_txDescsPhys = 0;
// Packet buffers (virtual addresses)
static uint8_t* g_rxBuffers[RX_DESC_COUNT] = {};
static uint8_t* g_txBuffers[TX_DESC_COUNT] = {};
static uint64_t g_rxBuffersPhys[RX_DESC_COUNT] = {};
static uint64_t g_txBuffersPhys[TX_DESC_COUNT] = {};
// Current descriptor indices
static uint32_t g_rxTail = 0;
static uint32_t g_txTail = 0;
// Statistics
static uint64_t g_rxPacketCount = 0;
static uint64_t g_txPacketCount = 0;
// RX callback
static RxCallback g_rxCallback = nullptr;
// -------------------------------------------------------------------------
// Register access helpers
// -------------------------------------------------------------------------
static void WriteReg(uint32_t reg, uint32_t value) {
*(volatile uint32_t*)(g_mmioBase + reg) = value;
}
static uint32_t ReadReg(uint32_t reg) {
return *(volatile uint32_t*)(g_mmioBase + reg);
}
// -------------------------------------------------------------------------
// SW/FW semaphore (prevents conflicts with Intel Management Engine)
// -------------------------------------------------------------------------
static bool AcquireSwFwSync() {
// Step 1: Acquire the software semaphore (SWSM.SMBI)
bool gotSmbi = false;
for (int i = 0; i < 2000; i++) {
uint32_t swsm = ReadReg(REG_SWSM);
if (!(swsm & SWSM_SMBI)) {
WriteReg(REG_SWSM, swsm | SWSM_SMBI);
if (ReadReg(REG_SWSM) & SWSM_SMBI) {
gotSmbi = true;
break;
}
}
}
if (!gotSmbi) {
KernelLogStream(WARNING, "E1000E") << "Could not acquire SMBI, proceeding anyway";
return false;
}
// Step 2: Acquire the SW/FW semaphore (EXTCNF_CTRL.SW_OWN)
for (int i = 0; i < 2000; i++) {
uint32_t extcnf = ReadReg(REG_EXTCNF_CTRL);
if (!(extcnf & EXTCNF_CTRL_SWFLAG)) {
WriteReg(REG_EXTCNF_CTRL, extcnf | EXTCNF_CTRL_SWFLAG);
if (ReadReg(REG_EXTCNF_CTRL) & EXTCNF_CTRL_SWFLAG) {
return true;
}
}
}
// Failed to acquire SWFLAG — release SMBI
uint32_t swsm = ReadReg(REG_SWSM);
WriteReg(REG_SWSM, swsm & ~SWSM_SMBI);
KernelLogStream(WARNING, "E1000E") << "Could not acquire SWFLAG, proceeding anyway";
return false;
}
static void ReleaseSwFwSync() {
uint32_t extcnf = ReadReg(REG_EXTCNF_CTRL);
WriteReg(REG_EXTCNF_CTRL, extcnf & ~EXTCNF_CTRL_SWFLAG);
uint32_t swsm = ReadReg(REG_SWSM);
WriteReg(REG_SWSM, swsm & ~SWSM_SMBI);
}
// -------------------------------------------------------------------------
// PHY access via MDIC register
// -------------------------------------------------------------------------
static uint16_t PhyRead(uint32_t phyReg) {
// PHY address = 1 for internal PHY
uint32_t mdic = ((phyReg & 0x1F) << MDIC_REG_SHIFT)
| (1u << MDIC_PHY_SHIFT)
| MDIC_OP_READ;
WriteReg(REG_MDIC, mdic);
for (int i = 0; i < 200000; i++) {
mdic = ReadReg(REG_MDIC);
if (mdic & MDIC_READY) {
if (mdic & MDIC_ERROR) {
KernelLogStream(WARNING, "E1000E") << "PHY read error for reg " << base::hex << (uint64_t)phyReg;
return 0;
}
return (uint16_t)(mdic & MDIC_DATA_MASK);
}
}
KernelLogStream(WARNING, "E1000E") << "PHY read timeout for reg " << base::hex << (uint64_t)phyReg;
return 0;
}
static void PhyWrite(uint32_t phyReg, uint16_t value) {
uint32_t mdic = (uint32_t)value
| ((phyReg & 0x1F) << MDIC_REG_SHIFT)
| (1u << MDIC_PHY_SHIFT)
| MDIC_OP_WRITE;
WriteReg(REG_MDIC, mdic);
for (int i = 0; i < 200000; i++) {
mdic = ReadReg(REG_MDIC);
if (mdic & MDIC_READY) {
if (mdic & MDIC_ERROR) {
KernelLogStream(WARNING, "E1000E") << "PHY write error for reg " << base::hex << (uint64_t)phyReg;
}
return;
}
}
KernelLogStream(WARNING, "E1000E") << "PHY write timeout for reg " << base::hex << (uint64_t)phyReg;
}
// -------------------------------------------------------------------------
// PHY initialization
// -------------------------------------------------------------------------
static void InitPhy() {
// Reset the PHY
PhyWrite(PHY_CONTROL, PHY_CTRL_RESET);
// Wait for reset to complete
for (int i = 0; i < 100000; i++) {
uint16_t ctrl = PhyRead(PHY_CONTROL);
if (!(ctrl & PHY_CTRL_RESET)) {
break;
}
}
// Advertise 10/100/1000 Mbps capabilities
uint16_t anar = PhyRead(PHY_AUTONEG_ADV);
anar |= (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8); // 10/100 HD/FD
PhyWrite(PHY_AUTONEG_ADV, anar);
// Advertise 1000BASE-T
uint16_t gbcr = PhyRead(PHY_1000T_CTRL);
gbcr |= (1 << 8) | (1 << 9); // 1000BASE-T HD/FD
PhyWrite(PHY_1000T_CTRL, gbcr);
// Enable and restart auto-negotiation
PhyWrite(PHY_CONTROL, PHY_CTRL_AUTONEG_EN | PHY_CTRL_RESTART_AN);
KernelLogStream(OK, "E1000E") << "PHY initialized, auto-negotiation started";
}
// -------------------------------------------------------------------------
// EEPROM access (e1000e encoding differs from e1000)
// -------------------------------------------------------------------------
static uint16_t EepromRead(uint8_t address) {
// E1000E: address shifted left by 2 (not 8), done bit at position 1 (not 4)
WriteReg(REG_EERD, ((uint32_t)address << 2) | 1);
uint32_t value;
for (int i = 0; i < 10000; i++) {
value = ReadReg(REG_EERD);
if (value & (1 << 1)) {
return (uint16_t)(value >> 16);
}
}
KernelLogStream(WARNING, "E1000E") << "EEPROM read timeout for address " << base::hex << (uint64_t)address;
return 0;
}
// -------------------------------------------------------------------------
// MAC address
// -------------------------------------------------------------------------
static void ReadMacAddress() {
// Try reading from RAL/RAH first
uint32_t ral = ReadReg(REG_RAL);
uint32_t rah = ReadReg(REG_RAH);
if (ral != 0) {
g_macAddress[0] = (uint8_t)(ral);
g_macAddress[1] = (uint8_t)(ral >> 8);
g_macAddress[2] = (uint8_t)(ral >> 16);
g_macAddress[3] = (uint8_t)(ral >> 24);
g_macAddress[4] = (uint8_t)(rah);
g_macAddress[5] = (uint8_t)(rah >> 8);
} else {
// Fallback: read from EEPROM
uint16_t word0 = EepromRead(0);
uint16_t word1 = EepromRead(1);
uint16_t word2 = EepromRead(2);
g_macAddress[0] = (uint8_t)(word0);
g_macAddress[1] = (uint8_t)(word0 >> 8);
g_macAddress[2] = (uint8_t)(word1);
g_macAddress[3] = (uint8_t)(word1 >> 8);
g_macAddress[4] = (uint8_t)(word2);
g_macAddress[5] = (uint8_t)(word2 >> 8);
}
// Write MAC back to RAL/RAH to ensure the filter is set
WriteReg(REG_RAL,
(uint32_t)g_macAddress[0] |
((uint32_t)g_macAddress[1] << 8) |
((uint32_t)g_macAddress[2] << 16) |
((uint32_t)g_macAddress[3] << 24));
WriteReg(REG_RAH,
(uint32_t)g_macAddress[4] |
((uint32_t)g_macAddress[5] << 8) |
(1u << 31)); // AV (Address Valid) bit
}
// -------------------------------------------------------------------------
// Allocate page-aligned DMA buffer, returns virtual address
// -------------------------------------------------------------------------
static uint8_t* AllocateDmaBuffer(uint64_t& outPhysAddr) {
void* virt = Memory::g_pfa->AllocateZeroed();
outPhysAddr = Memory::SubHHDM(virt);
return (uint8_t*)virt;
}
// -------------------------------------------------------------------------
// RX setup
// -------------------------------------------------------------------------
static void SetupRx() {
uint64_t descPhys;
g_rxDescs = (RxDescriptor*)AllocateDmaBuffer(descPhys);
g_rxDescsPhys = descPhys;
for (uint32_t i = 0; i < RX_DESC_COUNT; i++) {
g_rxBuffers[i] = AllocateDmaBuffer(g_rxBuffersPhys[i]);
// Allocate second page for larger buffer support
uint64_t secondPhys;
AllocateDmaBuffer(secondPhys);
g_rxDescs[i].BufferAddress = g_rxBuffersPhys[i];
g_rxDescs[i].Status = 0;
g_rxDescs[i].Length = 0;
g_rxDescs[i].Checksum = 0;
g_rxDescs[i].Errors = 0;
g_rxDescs[i].Special = 0;
}
WriteReg(REG_RDBAL, (uint32_t)(g_rxDescsPhys & 0xFFFFFFFF));
WriteReg(REG_RDBAH, (uint32_t)(g_rxDescsPhys >> 32));
WriteReg(REG_RDLEN, RX_DESC_COUNT * sizeof(RxDescriptor));
WriteReg(REG_RDH, 0);
WriteReg(REG_RDT, RX_DESC_COUNT - 1);
g_rxTail = RX_DESC_COUNT - 1;
uint32_t rctl = RCTL_EN | RCTL_BAM | RCTL_SECRC | RCTL_BSIZE_4096 | RCTL_BSEX;
WriteReg(REG_RCTL, rctl);
KernelLogStream(OK, "E1000E") << "RX ring configured: " << base::dec << (uint64_t)RX_DESC_COUNT << " descriptors";
}
// -------------------------------------------------------------------------
// TX setup
// -------------------------------------------------------------------------
static void SetupTx() {
uint64_t descPhys;
g_txDescs = (TxDescriptor*)AllocateDmaBuffer(descPhys);
g_txDescsPhys = descPhys;
for (uint32_t i = 0; i < TX_DESC_COUNT; i++) {
g_txBuffers[i] = AllocateDmaBuffer(g_txBuffersPhys[i]);
g_txDescs[i].BufferAddress = g_txBuffersPhys[i];
g_txDescs[i].Length = 0;
g_txDescs[i].Command = 0;
g_txDescs[i].Status = TXSTA_DD;
g_txDescs[i].ChecksumOffset = 0;
g_txDescs[i].ChecksumStart = 0;
g_txDescs[i].Special = 0;
}
WriteReg(REG_TDBAL, (uint32_t)(g_txDescsPhys & 0xFFFFFFFF));
WriteReg(REG_TDBAH, (uint32_t)(g_txDescsPhys >> 32));
WriteReg(REG_TDLEN, TX_DESC_COUNT * sizeof(TxDescriptor));
WriteReg(REG_TDH, 0);
WriteReg(REG_TDT, 0);
g_txTail = 0;
uint32_t tctl = TCTL_EN | TCTL_PSP
| (15u << TCTL_CT_SHIFT)
| (64u << TCTL_COLD_SHIFT);
WriteReg(REG_TCTL, tctl);
WriteReg(REG_TIPG, 10 | (10 << 10) | (10 << 20));
KernelLogStream(OK, "E1000E") << "TX ring configured: " << base::dec << (uint64_t)TX_DESC_COUNT << " descriptors";
}
// -------------------------------------------------------------------------
// MSI setup
// -------------------------------------------------------------------------
static void HandleInterrupt(uint8_t irq); // forward declaration
static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) {
uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI);
if (cap == 0) {
KernelLogStream(INFO, "E1000E") << "MSI capability not found";
return false;
}
KernelLogStream(INFO, "E1000E") << "MSI capability at offset " << base::hex << (uint64_t)cap;
// Read Message Control (cap+2)
uint16_t msgCtrl = Pci::LegacyRead16(bus, dev, func, cap + 2);
bool is64bit = (msgCtrl & (1 << 7)) != 0;
// Write Message Address (cap+4): BSP APIC ID 0, physical destination, fixed delivery
Pci::LegacyWrite32(bus, dev, func, cap + 4, MSI_ADDR_BASE);
// Write Message Data (vector number, edge-triggered, fixed delivery)
if (is64bit) {
// 64-bit: Upper Address at cap+8, Data at cap+12
Pci::LegacyWrite32(bus, dev, func, cap + 8, 0);
Pci::LegacyWrite16(bus, dev, func, cap + 12, MSI_VECTOR);
} else {
// 32-bit: Data at cap+8
Pci::LegacyWrite16(bus, dev, func, cap + 8, MSI_VECTOR);
}
// Enable MSI: set bit 0 (MSI Enable), clear bits 6:4 (single message)
msgCtrl &= ~(0x70); // Clear Multiple Message Enable (bits 6:4)
msgCtrl |= (1 << 0); // MSI Enable
Pci::LegacyWrite16(bus, dev, func, cap + 2, msgCtrl);
// Disable legacy INTx in PCI command register
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, PCI_REG_COMMAND);
pciCmd |= PCI_CMD_INTX_DISABLE;
Pci::LegacyWrite16(bus, dev, func, PCI_REG_COMMAND, pciCmd);
// Register the interrupt handler for MSI vector
Hal::RegisterIrqHandler(MSI_IRQ, HandleInterrupt);
KernelLogStream(OK, "E1000E") << "MSI enabled: vector " << base::dec << (uint64_t)MSI_VECTOR
<< " (IRQ slot " << (uint64_t)MSI_IRQ << ")" << (is64bit ? " [64-bit]" : " [32-bit]");
return true;
}
// -------------------------------------------------------------------------
// Interrupt handler
// -------------------------------------------------------------------------
static void HandleInterrupt(uint8_t irq) {
(void)irq;
uint32_t icr = ReadReg(REG_ICR);
// Spurious IRQ guard
if (icr == 0) {
return;
}
if (icr & ICR_LSC) {
uint32_t status = ReadReg(REG_STATUS);
bool linkUp = (status & (1 << 1)) != 0;
KernelLogStream(INFO, "E1000E") << "Link status change: " << (linkUp ? "UP" : "DOWN");
}
if (icr & ICR_RXT0) {
while (true) {
uint32_t nextIdx = (g_rxTail + 1) % RX_DESC_COUNT;
RxDescriptor& desc = g_rxDescs[nextIdx];
if (!(desc.Status & RXSTA_DD)) {
break;
}
uint16_t length = desc.Length;
g_rxPacketCount++;
if (g_rxCallback != nullptr) {
g_rxCallback(g_rxBuffers[nextIdx], length);
}
desc.Status = 0;
desc.Length = 0;
desc.Errors = 0;
g_rxTail = nextIdx;
WriteReg(REG_RDT, g_rxTail);
}
}
if (icr & (ICR_TXDW | ICR_TXQE)) {
// TX completion — nothing to do for now
}
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
void Initialize() {
KernelLogStream(INFO, "E1000E") << "Scanning for Intel e1000e NIC...";
auto& devices = Pci::GetDevices();
const Pci::PciDevice* foundDev = nullptr;
const char* foundName = nullptr;
for (uint64_t i = 0; i < devices.size(); i++) {
if (devices[i].VendorId != VendorIntel) {
continue;
}
for (uint32_t j = 0; j < g_deviceTableSize; j++) {
if (devices[i].DeviceId == g_deviceTable[j].DeviceId) {
foundDev = &devices[i];
foundName = g_deviceTable[j].Name;
break;
}
}
if (foundDev != nullptr) {
break;
}
}
if (foundDev == nullptr) {
KernelLogStream(WARNING, "E1000E") << "No e1000e NIC found";
return;
}
KernelLogStream(OK, "E1000E") << "Found " << foundName << " at PCI "
<< base::hex << (uint64_t)foundDev->Bus << ":"
<< (uint64_t)foundDev->Device << "." << (uint64_t)foundDev->Function;
// Read BAR0 (MMIO base address), check for 64-bit BAR
uint32_t bar0 = Pci::LegacyRead32(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_BAR0);
uint64_t mmioPhys = bar0 & 0xFFFFFFF0;
// Check if 64-bit BAR (type field bits 2:1 == 0b10)
if ((bar0 & 0x06) == 0x04) {
uint32_t bar1 = Pci::LegacyRead32(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_BAR1);
mmioPhys |= ((uint64_t)bar1 << 32);
}
KernelLogStream(INFO, "E1000E") << "BAR0 physical: " << base::hex << mmioPhys;
// Map the MMIO region (128KB = 32 pages)
constexpr uint64_t MmioSize = 0x20000;
for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) {
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
}
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
// Enable bus mastering and memory space in PCI command register
uint16_t pciCmd = Pci::LegacyRead16(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_COMMAND);
pciCmd |= PCI_CMD_BUS_MASTER | PCI_CMD_MEM_SPACE;
Pci::LegacyWrite16(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_COMMAND, pciCmd);
KernelLogStream(OK, "E1000E") << "Bus mastering enabled";
// Read interrupt line from PCI config (used for legacy IRQ fallback)
g_irqLine = Pci::LegacyRead8(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_INTERRUPT);
KernelLogStream(INFO, "E1000E") << "PCI IRQ line: " << base::dec << (uint64_t)g_irqLine;
// --- ICH/PCH reset sequence ---
// 1. Disable interrupts, flush ICR
KernelLogStream(INFO, "E1000E") << "Disabling interrupts...";
WriteReg(REG_IMC, 0xFFFFFFFF);
ReadReg(REG_ICR);
// 2. Acquire SW/FW semaphore (non-fatal if it fails)
KernelLogStream(INFO, "E1000E") << "Acquiring semaphore...";
AcquireSwFwSync();
// 3. Reset the device
KernelLogStream(INFO, "E1000E") << "Resetting device...";
uint32_t ctrl = ReadReg(REG_CTRL);
WriteReg(REG_CTRL, ctrl | CTRL_RST);
// Post-reset settling delay (give hardware time before polling)
for (int i = 0; i < 100000; i++) {
asm volatile("" ::: "memory");
}
// Poll for reset completion
for (int i = 0; i < 10000; i++) {
if (!(ReadReg(REG_CTRL) & CTRL_RST)) {
break;
}
}
// 4. Release semaphore
ReleaseSwFwSync();
// 5. Disable interrupts again after reset
WriteReg(REG_IMC, 0xFFFFFFFF);
ReadReg(REG_ICR);
KernelLogStream(OK, "E1000E") << "Reset complete";
// Set link up: SLU on, but let auto-negotiation decide speed/duplex
ctrl = ReadReg(REG_CTRL);
ctrl |= CTRL_SLU;
ctrl &= ~CTRL_FRCSPD;
ctrl &= ~CTRL_FRCDPLX;
ctrl &= ~(1u << 3); // Clear LRST
ctrl &= ~(1u << 31); // Clear PHY_RST
ctrl &= ~(1u << 7); // Clear ILOS
WriteReg(REG_CTRL, ctrl);
// Initialize PHY
InitPhy();
// Read MAC address
ReadMacAddress();
KernelLogStream(OK, "E1000E") << "MAC: "
<< base::hex
<< (uint64_t)g_macAddress[0] << ":"
<< (uint64_t)g_macAddress[1] << ":"
<< (uint64_t)g_macAddress[2] << ":"
<< (uint64_t)g_macAddress[3] << ":"
<< (uint64_t)g_macAddress[4] << ":"
<< (uint64_t)g_macAddress[5];
// Zero out the Multicast Table Array (128 entries)
for (uint32_t i = 0; i < 128; i++) {
WriteReg(REG_MTA + (i * 4), 0);
}
// Set up RX and TX descriptor rings
SetupRx();
SetupTx();
// Three-tier interrupt strategy: MSI → legacy IRQ → polling
if (SetupMsi(foundDev->Bus, foundDev->Device, foundDev->Function)) {
// MSI configured — enable NIC interrupt causes
WriteReg(REG_IMS, ICR_RXT0 | ICR_TXDW | ICR_TXQE | ICR_LSC | ICR_RXDMT0);
} else if (g_irqLine != 0xFF) {
// Legacy IRQ fallback
KernelLogStream(INFO, "E1000E") << "Falling back to legacy IRQ " << base::dec << (uint64_t)g_irqLine;
Hal::RegisterIrqHandler(g_irqLine, HandleInterrupt);
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(g_irqLine));
WriteReg(REG_IMS, ICR_RXT0 | ICR_TXDW | ICR_TXQE | ICR_LSC | ICR_RXDMT0);
} else {
// Polling as last resort
KernelLogStream(WARNING, "E1000E") << "No MSI or legacy IRQ available, using polling mode";
g_pollingMode = true;
}
g_initialized = true;
uint32_t status = ReadReg(REG_STATUS);
bool linkUp = (status & (1 << 1)) != 0;
KernelLogStream(OK, "E1000E") << "Initialization complete, link: " << (linkUp ? "UP" : "DOWN");
}
// -------------------------------------------------------------------------
// Polling: process received packets (with reentrancy guard)
// -------------------------------------------------------------------------
static bool g_polling = false;
static void PollRx() {
// Guard against reentrancy: RX callback can trigger ARP reply →
// Ethernet::Send → SendPacket → PollRx(). Two concurrent callers
// modifying g_rxTail / RDT would corrupt the descriptor ring.
if (g_polling) {
return;
}
g_polling = true;
while (true) {
uint32_t nextIdx = (g_rxTail + 1) % RX_DESC_COUNT;
RxDescriptor& desc = g_rxDescs[nextIdx];
if (!(desc.Status & RXSTA_DD)) {
break;
}
uint16_t length = desc.Length;
g_rxPacketCount++;
if (g_rxCallback != nullptr) {
g_rxCallback(g_rxBuffers[nextIdx], length);
}
desc.Status = 0;
desc.Length = 0;
desc.Errors = 0;
g_rxTail = nextIdx;
WriteReg(REG_RDT, g_rxTail);
}
g_polling = false;
}
bool SendPacket(const uint8_t* data, uint16_t length) {
if (!g_initialized || data == nullptr || length == 0 || length > 1518) {
return false;
}
TxDescriptor& desc = g_txDescs[g_txTail];
if (!(desc.Status & TXSTA_DD)) {
KernelLogStream(WARNING, "E1000E") << "TX ring full";
return false;
}
memcpy(g_txBuffers[g_txTail], data, length);
desc.BufferAddress = g_txBuffersPhys[g_txTail];
desc.Length = length;
desc.Command = TXCMD_EOP | TXCMD_IFCS | TXCMD_RS;
desc.Status = 0;
g_txTail = (g_txTail + 1) % TX_DESC_COUNT;
WriteReg(REG_TDT, g_txTail);
g_txPacketCount++;
return true;
}
const uint8_t* GetMacAddress() {
return g_macAddress;
}
bool IsInitialized() {
return g_initialized;
}
void SetRxCallback(RxCallback callback) {
g_rxCallback = callback;
}
void Poll() {
if (!g_initialized) {
return;
}
PollRx();
}
};
+155
View File
@@ -0,0 +1,155 @@
/*
* E1000E.hpp
* Intel I217/I218/I219 (E1000E) Ethernet driver
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Net::E1000E {
// E1000E register offsets (memory-mapped via BAR0)
constexpr uint32_t REG_CTRL = 0x0000; // Device Control
constexpr uint32_t REG_STATUS = 0x0008; // Device Status
constexpr uint32_t REG_EERD = 0x0014; // EEPROM Read
constexpr uint32_t REG_CTRL_EXT = 0x0018; // Extended Device Control
constexpr uint32_t REG_MDIC = 0x0020; // MDI Control (PHY access)
constexpr uint32_t REG_ICR = 0x00C0; // Interrupt Cause Read
constexpr uint32_t REG_IMS = 0x00D0; // Interrupt Mask Set
constexpr uint32_t REG_IMC = 0x00D8; // Interrupt Mask Clear
constexpr uint32_t REG_RCTL = 0x0100; // Receive Control
constexpr uint32_t REG_TCTL = 0x0400; // Transmit Control
constexpr uint32_t REG_TIPG = 0x0410; // Transmit IPG
constexpr uint32_t REG_RDBAL = 0x2800; // RX Descriptor Base Low
constexpr uint32_t REG_RDBAH = 0x2804; // RX Descriptor Base High
constexpr uint32_t REG_RDLEN = 0x2808; // RX Descriptor Length
constexpr uint32_t REG_RDH = 0x2810; // RX Descriptor Head
constexpr uint32_t REG_RDT = 0x2818; // RX Descriptor Tail
constexpr uint32_t REG_TDBAL = 0x3800; // TX Descriptor Base Low
constexpr uint32_t REG_TDBAH = 0x3804; // TX Descriptor Base High
constexpr uint32_t REG_TDLEN = 0x3808; // TX Descriptor Length
constexpr uint32_t REG_TDH = 0x3810; // TX Descriptor Head
constexpr uint32_t REG_TDT = 0x3818; // TX Descriptor Tail
constexpr uint32_t REG_MTA = 0x5200; // Multicast Table Array (128 entries)
constexpr uint32_t REG_RAL = 0x5400; // Receive Address Low
constexpr uint32_t REG_RAH = 0x5404; // Receive Address High
constexpr uint32_t REG_EXTCNF_CTRL = 0x0F00; // Extended Configuration Control
constexpr uint32_t REG_SWSM = 0x5B50; // Software Semaphore
constexpr uint32_t REG_FWSM = 0x5B54; // Firmware Semaphore
// CTRL register bits
constexpr uint32_t CTRL_SLU = (1 << 6); // Set Link Up
constexpr uint32_t CTRL_FRCSPD = (1 << 11); // Force Speed
constexpr uint32_t CTRL_FRCDPLX = (1 << 12); // Force Duplex
constexpr uint32_t CTRL_RST = (1 << 26); // Device Reset
// MDIC register
constexpr uint32_t MDIC_DATA_MASK = 0x0000FFFF;
constexpr uint32_t MDIC_REG_SHIFT = 16; // PHY register address shift
constexpr uint32_t MDIC_PHY_SHIFT = 21; // PHY address shift
constexpr uint32_t MDIC_OP_READ = (2u << 26);
constexpr uint32_t MDIC_OP_WRITE = (1u << 26);
constexpr uint32_t MDIC_READY = (1u << 28);
constexpr uint32_t MDIC_ERROR = (1u << 30);
// PHY register addresses
constexpr uint32_t PHY_CONTROL = 0x00; // PHY Control Register
constexpr uint32_t PHY_STATUS = 0x01; // PHY Status Register
constexpr uint32_t PHY_AUTONEG_ADV = 0x04; // Auto-Negotiation Advertisement
constexpr uint32_t PHY_1000T_CTRL = 0x09; // 1000BASE-T Control
// PHY Control register bits
constexpr uint16_t PHY_CTRL_RESET = (1 << 15);
constexpr uint16_t PHY_CTRL_AUTONEG_EN = (1 << 12);
constexpr uint16_t PHY_CTRL_RESTART_AN = (1 << 9);
// Semaphore bits
constexpr uint32_t SWSM_SMBI = (1 << 0);
constexpr uint32_t SWSM_SWESMBI = (1 << 1);
constexpr uint32_t EXTCNF_CTRL_SWFLAG = (1 << 5);
// RCTL register bits
constexpr uint32_t RCTL_EN = (1 << 1); // Receiver Enable
constexpr uint32_t RCTL_SBP = (1 << 2); // Store Bad Packets
constexpr uint32_t RCTL_UPE = (1 << 3); // Unicast Promiscuous
constexpr uint32_t RCTL_MPE = (1 << 4); // Multicast Promiscuous
constexpr uint32_t RCTL_BAM = (1 << 15); // Broadcast Accept Mode
constexpr uint32_t RCTL_BSIZE_4096 = (3 << 16); // Buffer Size 4096 (with BSEX)
constexpr uint32_t RCTL_BSEX = (1 << 25); // Buffer Size Extension
constexpr uint32_t RCTL_SECRC = (1 << 26); // Strip Ethernet CRC
// TCTL register bits
constexpr uint32_t TCTL_EN = (1 << 1); // Transmit Enable
constexpr uint32_t TCTL_PSP = (1 << 3); // Pad Short Packets
constexpr uint32_t TCTL_CT_SHIFT = 4; // Collision Threshold shift
constexpr uint32_t TCTL_COLD_SHIFT = 12; // Collision Distance shift
// ICR (interrupt cause) bits
constexpr uint32_t ICR_TXDW = (1 << 0); // TX Descriptor Written Back
constexpr uint32_t ICR_TXQE = (1 << 1); // TX Queue Empty
constexpr uint32_t ICR_LSC = (1 << 2); // Link Status Change
constexpr uint32_t ICR_RXDMT0 = (1 << 4); // RX Descriptor Minimum Threshold
constexpr uint32_t ICR_RXO = (1 << 6); // Receiver Overrun
constexpr uint32_t ICR_RXT0 = (1 << 7); // Receiver Timer Interrupt
// TX descriptor command bits
constexpr uint8_t TXCMD_EOP = (1 << 0); // End Of Packet
constexpr uint8_t TXCMD_IFCS = (1 << 1); // Insert FCS/CRC
constexpr uint8_t TXCMD_RS = (1 << 3); // Report Status
// TX descriptor status bits
constexpr uint8_t TXSTA_DD = (1 << 0); // Descriptor Done
// RX descriptor status bits
constexpr uint8_t RXSTA_DD = (1 << 0); // Descriptor Done
constexpr uint8_t RXSTA_EOP = (1 << 1); // End Of Packet
// Descriptor ring sizes
constexpr uint32_t RX_DESC_COUNT = 32;
constexpr uint32_t TX_DESC_COUNT = 32;
constexpr uint32_t PACKET_BUFFER_SIZE = 8192;
// RX descriptor (legacy format, 16 bytes)
struct RxDescriptor {
uint64_t BufferAddress;
uint16_t Length;
uint16_t Checksum;
uint8_t Status;
uint8_t Errors;
uint16_t Special;
} __attribute__((packed));
// TX descriptor (legacy format, 16 bytes)
struct TxDescriptor {
uint64_t BufferAddress;
uint16_t Length;
uint8_t ChecksumOffset;
uint8_t Command;
uint8_t Status;
uint8_t ChecksumStart;
uint16_t Special;
} __attribute__((packed));
// Initialize the E1000E driver (scans PCI for the device)
void Initialize();
// Send a raw Ethernet frame
bool SendPacket(const uint8_t* data, uint16_t length);
// Get the MAC address (6 bytes)
const uint8_t* GetMacAddress();
// Check if the device was found and initialized
bool IsInitialized();
// RX callback type: called with (packet data, length)
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
// Register a callback for received packets
void SetRxCallback(RxCallback callback);
// Poll for received packets (used when legacy IRQ is unavailable)
void Poll();
};
+107
View File
@@ -8,6 +8,7 @@
#include <Terminal/Terminal.hpp>
#include <Libraries/String.hpp>
#include <Libraries/Memory.hpp>
#include <Memory/Heap.hpp>
namespace Fs::Ramdisk {
@@ -105,6 +106,8 @@ namespace Fs::Ramdisk {
entry.isDirectory = (typeFlag == '5');
entry.size = size;
entry.capacity = size;
entry.heapAllocated = false;
// Data starts at next 512-byte block
entry.data = ptr + 512;
@@ -235,6 +238,110 @@ namespace Fs::Ramdisk {
return count;
}
int Write(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size) {
if (handle < 0 || handle >= fileCount) return -1;
if (buffer == nullptr || size == 0) return 0;
FileEntry& entry = fileTable[handle];
if (entry.isDirectory) return -1;
uint64_t endOffset = offset + size;
// Copy-on-write: if data points into tar memory, copy to heap
if (!entry.heapAllocated) {
uint64_t newCap = entry.size;
if (endOffset > newCap) newCap = endOffset;
if (newCap < 256) newCap = 256;
// Round up to next power of 2 for growth
uint64_t rounded = 256;
while (rounded < newCap) rounded *= 2;
newCap = rounded;
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
if (newBuf == nullptr) return -1;
if (entry.data && entry.size > 0) {
memcpy(newBuf, entry.data, entry.size);
}
entry.data = newBuf;
entry.capacity = newCap;
entry.heapAllocated = true;
}
// Grow buffer if needed
if (endOffset > entry.capacity) {
uint64_t newCap = entry.capacity;
while (newCap < endOffset) newCap *= 2;
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
if (newBuf == nullptr) return -1;
if (entry.data && entry.size > 0) {
memcpy(newBuf, entry.data, entry.size);
}
Memory::g_heap->Free(entry.data);
entry.data = newBuf;
entry.capacity = newCap;
}
memcpy(entry.data + offset, buffer, size);
if (endOffset > entry.size) {
entry.size = endOffset;
}
return (int)size;
}
int Create(const char* path) {
if (path == nullptr) return -1;
if (fileCount >= MaxFiles) return -1;
// Normalize: skip leading '/'
if (path[0] == '/') path++;
// Check if file already exists
for (int i = 0; i < fileCount; i++) {
if (StrEqual(fileTable[i].name, path)) {
// File exists — truncate it
FileEntry& entry = fileTable[i];
if (!entry.heapAllocated) {
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(256);
if (newBuf == nullptr) return -1;
entry.data = newBuf;
entry.capacity = 256;
entry.heapAllocated = true;
}
entry.size = 0;
entry.isDirectory = false;
return i;
}
}
// Create new file entry
FileEntry& entry = fileTable[fileCount];
int nameLen = 0;
while (nameLen < MaxNameLen - 1 && path[nameLen] != '\0') {
entry.name[nameLen] = path[nameLen];
nameLen++;
}
entry.name[nameLen] = '\0';
uint8_t* buf = (uint8_t*)Memory::g_heap->Request(256);
if (buf == nullptr) return -1;
entry.data = buf;
entry.size = 0;
entry.capacity = 256;
entry.isDirectory = false;
entry.heapAllocated = true;
return fileCount++;
}
int GetFileCount() {
return fileCount;
}
+4
View File
@@ -17,13 +17,17 @@ namespace Fs::Ramdisk {
char name[MaxNameLen];
uint8_t* data;
uint64_t size;
uint64_t capacity;
bool isDirectory;
bool heapAllocated;
};
void Initialize(void* moduleData, uint64_t moduleSize);
int Open(const char* path);
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
int Write(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
int Create(const char* path);
uint64_t GetSize(int handle);
void Close(int handle);
+40
View File
@@ -123,6 +123,46 @@ namespace Fs::Vfs {
entry.inUse = false;
}
int VfsWrite(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size) {
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return -1;
HandleEntry& entry = handleTable[handle];
if (driveTable[entry.driveNumber]->Write == nullptr) return -1;
return driveTable[entry.driveNumber]->Write(entry.localHandle, buffer, offset, size);
}
int VfsCreate(const char* path) {
int drive;
const char* localPath;
if (!ParsePath(path, drive, localPath)) {
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Invalid path format for Create";
return -1;
}
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) {
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Drive " << drive << " not registered";
return -1;
}
if (driveTable[drive]->Create == nullptr) return -1;
int localHandle = driveTable[drive]->Create(localPath);
if (localHandle < 0) return -1;
int globalHandle = AllocHandle();
if (globalHandle < 0) {
Kt::KernelLogStream(Kt::ERROR, "VFS") << "No free handles";
return -1;
}
handleTable[globalHandle].inUse = true;
handleTable[globalHandle].driveNumber = drive;
handleTable[globalHandle].localHandle = localHandle;
return globalHandle;
}
int VfsReadDir(const char* path, const char** outNames, int maxEntries) {
int drive;
const char* localPath;
+4
View File
@@ -19,6 +19,8 @@ namespace Fs::Vfs {
uint64_t (*GetSize)(int handle);
void (*Close)(int handle);
int (*ReadDir)(const char* path, const char** outNames, int maxEntries);
int (*Write)(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
int (*Create)(const char* path);
};
void Initialize();
@@ -26,6 +28,8 @@ namespace Fs::Vfs {
int VfsOpen(const char* path);
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
int VfsWrite(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
int VfsCreate(const char* path);
uint64_t VfsGetSize(int handle);
void VfsClose(int handle);
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
+4 -4
View File
@@ -8,11 +8,11 @@
#include <cstdint>
namespace Hal {
// IRQ handler function type. The parameter is the IRQ number (0-23).
// IRQ handler function type. The parameter is the IRQ number (0-47).
using IrqHandler = void(*)(uint8_t irq);
// Number of IRQ lines supported (IOAPIC inputs)
constexpr int IRQ_COUNT = 24;
// Number of IRQ slots supported (0-23: legacy ISA via IOAPIC, 24-47: MSI)
constexpr int IRQ_COUNT = 48;
// IRQ vector base: hardware IRQs start at IDT vector 32
constexpr uint8_t IRQ_VECTOR_BASE = 32;
@@ -29,7 +29,7 @@ namespace Hal {
constexpr uint8_t IRQ_ATA1 = 14;
constexpr uint8_t IRQ_ATA2 = 15;
// Register a handler for the given IRQ number (0-23)
// Register a handler for the given IRQ number (0-47)
void RegisterIrqHandler(uint8_t irq, IrqHandler handler);
// Install IRQ stubs into the IDT and set up the dispatch table
+50 -2
View File
@@ -65,8 +65,8 @@ IrqCommon:
iretq
; Define stubs for IRQs 0..23 (vectors 32..55)
; This covers all standard ISA IRQs plus some extra IOAPIC inputs
; Define stubs for IRQs 0..47 (vectors 32..79)
; 0-23: legacy ISA IRQs via IOAPIC, 24-47: MSI vectors
IRQ_STUB 0
IRQ_STUB 1
IRQ_STUB 2
@@ -91,6 +91,30 @@ IRQ_STUB 20
IRQ_STUB 21
IRQ_STUB 22
IRQ_STUB 23
IRQ_STUB 24
IRQ_STUB 25
IRQ_STUB 26
IRQ_STUB 27
IRQ_STUB 28
IRQ_STUB 29
IRQ_STUB 30
IRQ_STUB 31
IRQ_STUB 32
IRQ_STUB 33
IRQ_STUB 34
IRQ_STUB 35
IRQ_STUB 36
IRQ_STUB 37
IRQ_STUB 38
IRQ_STUB 39
IRQ_STUB 40
IRQ_STUB 41
IRQ_STUB 42
IRQ_STUB 43
IRQ_STUB 44
IRQ_STUB 45
IRQ_STUB 46
IRQ_STUB 47
; Spurious interrupt handler (vector 0xFF) - do nothing, no EOI
global IrqStubSpurious
@@ -125,3 +149,27 @@ IrqStubTable:
dq IrqStub21
dq IrqStub22
dq IrqStub23
dq IrqStub24
dq IrqStub25
dq IrqStub26
dq IrqStub27
dq IrqStub28
dq IrqStub29
dq IrqStub30
dq IrqStub31
dq IrqStub32
dq IrqStub33
dq IrqStub34
dq IrqStub35
dq IrqStub36
dq IrqStub37
dq IrqStub38
dq IrqStub39
dq IrqStub40
dq IrqStub41
dq IrqStub42
dq IrqStub43
dq IrqStub44
dq IrqStub45
dq IrqStub46
dq IrqStub47
+9 -2
View File
@@ -34,6 +34,7 @@
#include <Drivers/PS2/Keyboard.hpp>
#include <Drivers/PS2/Mouse.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Net/Net.hpp>
#include <CppLib/BoxUI.hpp>
#include <Graphics/Cursor.hpp>
@@ -153,6 +154,10 @@ extern "C" void kmain() {
Drivers::PS2::Mouse::Initialize();
Drivers::Net::E1000::Initialize();
if (!Drivers::Net::E1000::IsInitialized()) {
KernelLogStream(INFO, "Init") << "E1000 not found, trying E1000E...";
Drivers::Net::E1000E::Initialize();
}
Net::Initialize();
}
#endif
@@ -188,7 +193,9 @@ extern "C" void kmain() {
Fs::Ramdisk::Read,
Fs::Ramdisk::GetSize,
Fs::Ramdisk::Close,
Fs::Ramdisk::ReadDir
Fs::Ramdisk::ReadDir,
Fs::Ramdisk::Write,
Fs::Ramdisk::Create
};
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
@@ -198,7 +205,7 @@ extern "C" void kmain() {
Zenith::InitializeSyscalls();
Sched::Initialize();
Sched::Spawn("0:/shell.elf");
Sched::Spawn("0:/os/init.elf");
// Enable preemptive scheduling via the APIC timer
Timekeeping::EnableSchedulerTick();
+9 -2
View File
@@ -10,6 +10,7 @@
#include <Net/Ipv4.hpp>
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -19,6 +20,12 @@ using namespace Kt;
namespace Net::Arp {
static const uint8_t* GetActiveNicMac() {
if (Drivers::Net::E1000::IsInitialized())
return Drivers::Net::E1000::GetMacAddress();
return Drivers::Net::E1000E::GetMacAddress();
}
// ARP cache entry
struct CacheEntry {
uint32_t Ip;
@@ -107,7 +114,7 @@ namespace Net::Arp {
reply.ProtocolAddrLen = 4;
reply.Operation = Htons(OP_REPLY);
memcpy(reply.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6);
memcpy(reply.SenderMac, GetActiveNicMac(), 6);
reply.SenderIp = GetIpAddress();
memcpy(reply.TargetMac, pkt->SenderMac, 6);
reply.TargetIp = senderIp;
@@ -141,7 +148,7 @@ namespace Net::Arp {
req.ProtocolAddrLen = 4;
req.Operation = Htons(OP_REQUEST);
memcpy(req.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6);
memcpy(req.SenderMac, GetActiveNicMac(), 6);
req.SenderIp = GetIpAddress();
memset(req.TargetMac, 0, 6);
req.TargetIp = targetIp;
+15 -2
View File
@@ -9,6 +9,7 @@
#include <Net/Arp.hpp>
#include <Net/Ipv4.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -17,6 +18,18 @@ using namespace Kt;
namespace Net::Ethernet {
static const uint8_t* GetActiveNicMac() {
if (Drivers::Net::E1000::IsInitialized())
return Drivers::Net::E1000::GetMacAddress();
return Drivers::Net::E1000E::GetMacAddress();
}
static bool ActiveNicSend(const uint8_t* data, uint16_t length) {
if (Drivers::Net::E1000::IsInitialized())
return Drivers::Net::E1000::SendPacket(data, length);
return Drivers::Net::E1000E::SendPacket(data, length);
}
void Initialize() {
KernelLogStream(OK, "Net") << "Ethernet layer initialized";
}
@@ -30,14 +43,14 @@ namespace Net::Ethernet {
Header* hdr = (Header*)frame;
memcpy(hdr->DestMac, destMac, 6);
memcpy(hdr->SrcMac, Drivers::Net::E1000::GetMacAddress(), 6);
memcpy(hdr->SrcMac, GetActiveNicMac(), 6);
hdr->EtherType = Htons(etherType);
memcpy(frame + HEADER_SIZE, payload, payloadLen);
uint16_t totalLen = HEADER_SIZE + payloadLen;
return Drivers::Net::E1000::SendPacket(frame, totalLen);
return ActiveNicSend(frame, totalLen);
}
void OnFrameReceived(const uint8_t* data, uint16_t length) {
+8 -3
View File
@@ -14,6 +14,7 @@
#include <Net/Socket.hpp>
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -22,8 +23,8 @@ using namespace Kt;
namespace Net {
void Initialize() {
if (!Drivers::Net::E1000::IsInitialized()) {
KernelLogStream(WARNING, "Net") << "E1000 not initialized, skipping network stack";
if (!Drivers::Net::E1000::IsInitialized() && !Drivers::Net::E1000E::IsInitialized()) {
KernelLogStream(WARNING, "Net") << "No NIC initialized, skipping network stack";
return;
}
@@ -36,8 +37,12 @@ namespace Net {
Tcp::Initialize();
Socket::Initialize();
// Hook E1000 RX to our Ethernet dispatcher
// Hook the active NIC's RX to our Ethernet dispatcher
if (Drivers::Net::E1000::IsInitialized()) {
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
} else {
Drivers::Net::E1000E::SetRxCallback(Ethernet::OnFrameReceived);
}
// Send a gratuitous ARP to announce ourselves on the network
Arp::SendRequest(GetIpAddress());
+185 -2
View File
@@ -1,19 +1,42 @@
/*
* Socket.cpp
* Socket descriptor table wrapping kernel TCP layer
* Socket descriptor table wrapping kernel TCP and UDP layers
* Copyright (c) 2025 Daniel Hammer
*/
#include "Socket.hpp"
#include <Net/Tcp.hpp>
#include <Net/Udp.hpp>
#include <Net/NetConfig.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp>
using namespace Kt;
namespace Net::Socket {
// ---- UDP socket state ----
static constexpr uint32_t UDP_RING_SIZE = 4096;
static constexpr int MAX_UDP_SOCKETS = 16;
struct UdpDgramHeader {
uint32_t SrcIp;
uint16_t SrcPort;
uint16_t DataLen;
};
struct UdpSocketState {
uint8_t Ring[UDP_RING_SIZE];
uint32_t Head;
uint32_t Tail;
uint32_t Count;
uint16_t LocalPort;
bool Active;
};
static UdpSocketState g_udpSockets[MAX_UDP_SOCKETS] = {};
static SocketEntry g_sockets[MAX_SOCKETS] = {};
static uint16_t g_nextEphemeralPort = 49152;
@@ -30,15 +53,78 @@ namespace Net::Socket {
return true;
}
static UdpSocketState* AllocUdpState() {
for (int i = 0; i < MAX_UDP_SOCKETS; i++) {
if (!g_udpSockets[i].Active) {
g_udpSockets[i].Active = true;
g_udpSockets[i].Head = 0;
g_udpSockets[i].Tail = 0;
g_udpSockets[i].Count = 0;
g_udpSockets[i].LocalPort = 0;
return &g_udpSockets[i];
}
}
return nullptr;
}
static void FreeUdpState(UdpSocketState* state) {
if (state) {
if (state->LocalPort != 0) {
Udp::Unbind(state->LocalPort);
}
state->Active = false;
}
}
static void UdpSocketDispatcher(uint32_t srcIp, uint16_t srcPort,
uint16_t dstPort,
const uint8_t* data, uint16_t length) {
for (int i = 0; i < MAX_UDP_SOCKETS; i++) {
if (g_udpSockets[i].Active && g_udpSockets[i].LocalPort == dstPort) {
UdpSocketState* st = &g_udpSockets[i];
uint32_t needed = sizeof(UdpDgramHeader) + length;
if (st->Count + needed > UDP_RING_SIZE) {
return; // drop if buffer full
}
// Enqueue header
UdpDgramHeader hdr;
hdr.SrcIp = srcIp;
hdr.SrcPort = srcPort;
hdr.DataLen = length;
const uint8_t* hdrBytes = (const uint8_t*)&hdr;
for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) {
st->Ring[st->Tail] = hdrBytes[j];
st->Tail = (st->Tail + 1) % UDP_RING_SIZE;
}
// Enqueue payload
for (uint16_t j = 0; j < length; j++) {
st->Ring[st->Tail] = data[j];
st->Tail = (st->Tail + 1) % UDP_RING_SIZE;
}
st->Count += needed;
return;
}
}
}
// ---- Public API ----
void Initialize() {
for (int i = 0; i < MAX_SOCKETS; i++) {
g_sockets[i].Active = false;
}
for (int i = 0; i < MAX_UDP_SOCKETS; i++) {
g_udpSockets[i].Active = false;
}
KernelLogStream(OK, "Net") << "Socket table initialized";
}
int Create(int type, int pid) {
if (type != SOCK_TCP) return -1;
if (type != SOCK_TCP && type != SOCK_UDP) return -1;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (!g_sockets[i].Active) {
@@ -46,7 +132,18 @@ namespace Net::Socket {
g_sockets[i].Type = type;
g_sockets[i].OwnerPid = pid;
g_sockets[i].TcpConn = nullptr;
g_sockets[i].UdpState = nullptr;
g_sockets[i].LocalPort = 0;
if (type == SOCK_UDP) {
UdpSocketState* us = AllocUdpState();
if (!us) {
g_sockets[i].Active = false;
return -1;
}
g_sockets[i].UdpState = us;
}
return i;
}
}
@@ -55,6 +152,7 @@ namespace Net::Socket {
int Connect(int fd, uint32_t ip, uint16_t port, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_TCP) return -1;
if (g_sockets[fd].TcpConn != nullptr) return -1;
uint16_t srcPort = AllocEphemeralPort();
@@ -70,11 +168,20 @@ namespace Net::Socket {
int Bind(int fd, uint16_t port, int pid) {
if (!ValidFd(fd, pid)) return -1;
g_sockets[fd].LocalPort = port;
if (g_sockets[fd].Type == SOCK_UDP) {
UdpSocketState* us = g_sockets[fd].UdpState;
if (!us) return -1;
us->LocalPort = port;
if (!Udp::Bind(port, UdpSocketDispatcher)) return -1;
}
return 0;
}
int Listen(int fd, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_TCP) return -1;
if (g_sockets[fd].LocalPort == 0) return -1;
if (g_sockets[fd].TcpConn != nullptr) return -1;
@@ -87,6 +194,7 @@ namespace Net::Socket {
int Accept(int fd, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_TCP) return -1;
if (g_sockets[fd].TcpConn == nullptr) return -1;
Tcp::Connection* clientConn = Tcp::Accept(g_sockets[fd].TcpConn);
@@ -99,6 +207,7 @@ namespace Net::Socket {
g_sockets[i].Type = SOCK_TCP;
g_sockets[i].OwnerPid = pid;
g_sockets[i].TcpConn = clientConn;
g_sockets[i].UdpState = nullptr;
g_sockets[i].LocalPort = g_sockets[fd].LocalPort;
return i;
}
@@ -111,22 +220,92 @@ namespace Net::Socket {
int Send(int fd, const uint8_t* data, uint32_t len, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_TCP) return -1;
if (g_sockets[fd].TcpConn == nullptr) return -1;
return Tcp::Send(g_sockets[fd].TcpConn, data, (uint16_t)len);
}
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_TCP) return -1;
if (g_sockets[fd].TcpConn == nullptr) return -1;
return Tcp::ReceiveNonBlocking(g_sockets[fd].TcpConn, buf, (uint16_t)maxLen);
}
int SendTo(int fd, const uint8_t* data, uint32_t len,
uint32_t destIp, uint16_t destPort, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_UDP) return -1;
UdpSocketState* us = g_sockets[fd].UdpState;
if (!us) return -1;
// Auto-bind ephemeral port if not already bound
if (us->LocalPort == 0) {
uint16_t ep = AllocEphemeralPort();
us->LocalPort = ep;
g_sockets[fd].LocalPort = ep;
if (!Udp::Bind(ep, UdpSocketDispatcher)) return -1;
}
if (!Udp::Send(destIp, us->LocalPort, destPort, data, (uint16_t)len)) {
return -1;
}
return (int)len;
}
int RecvFrom(int fd, uint8_t* buf, uint32_t maxLen,
uint32_t* srcIp, uint16_t* srcPort, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].Type != SOCK_UDP) return -1;
UdpSocketState* us = g_sockets[fd].UdpState;
if (!us) return -1;
if (us->Count < sizeof(UdpDgramHeader)) {
return -1; // no data available
}
// Dequeue header
UdpDgramHeader hdr;
uint8_t* hdrBytes = (uint8_t*)&hdr;
for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) {
hdrBytes[j] = us->Ring[us->Head];
us->Head = (us->Head + 1) % UDP_RING_SIZE;
}
us->Count -= sizeof(UdpDgramHeader);
// Dequeue payload
uint16_t copyLen = hdr.DataLen;
if (copyLen > maxLen) copyLen = (uint16_t)maxLen;
for (uint16_t j = 0; j < copyLen; j++) {
buf[j] = us->Ring[us->Head];
us->Head = (us->Head + 1) % UDP_RING_SIZE;
}
// Skip remaining data if buffer was too small
for (uint16_t j = copyLen; j < hdr.DataLen; j++) {
us->Head = (us->Head + 1) % UDP_RING_SIZE;
}
us->Count -= hdr.DataLen;
if (srcIp) *srcIp = hdr.SrcIp;
if (srcPort) *srcPort = hdr.SrcPort;
return (int)copyLen;
}
void Close(int fd, int pid) {
if (!ValidFd(fd, pid)) return;
if (g_sockets[fd].TcpConn != nullptr) {
Tcp::Close(g_sockets[fd].TcpConn);
g_sockets[fd].TcpConn = nullptr;
}
if (g_sockets[fd].UdpState != nullptr) {
FreeUdpState(g_sockets[fd].UdpState);
g_sockets[fd].UdpState = nullptr;
}
g_sockets[fd].Active = false;
}
@@ -137,6 +316,10 @@ namespace Net::Socket {
Tcp::Close(g_sockets[i].TcpConn);
g_sockets[i].TcpConn = nullptr;
}
if (g_sockets[i].UdpState != nullptr) {
FreeUdpState(g_sockets[i].UdpState);
g_sockets[i].UdpState = nullptr;
}
g_sockets[i].Active = false;
}
}
+12 -1
View File
@@ -1,6 +1,6 @@
/*
* Socket.hpp
* Socket descriptor table for userspace TCP access
* Socket descriptor table for userspace TCP/UDP access
* Copyright (c) 2025 Daniel Hammer
*/
@@ -14,11 +14,14 @@ namespace Net::Socket {
static constexpr int SOCK_UDP = 2;
static constexpr int MAX_SOCKETS = 64;
struct UdpSocketState;
struct SocketEntry {
bool Active;
int Type;
int OwnerPid;
Tcp::Connection* TcpConn;
UdpSocketState* UdpState;
uint16_t LocalPort;
};
@@ -45,6 +48,14 @@ namespace Net::Socket {
// Receive data from a connected socket. Returns bytes received, 0 on close, or -1.
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid);
// Send a UDP datagram to a specific destination. Returns bytes sent or -1.
int SendTo(int fd, const uint8_t* data, uint32_t len,
uint32_t destIp, uint16_t destPort, int pid);
// Receive a UDP datagram, returning source info. Returns bytes received or -1.
int RecvFrom(int fd, uint8_t* buf, uint32_t maxLen,
uint32_t* srcIp, uint16_t* srcPort, int pid);
// Close a socket.
void Close(int fd, int pid);
+1 -1
View File
@@ -61,7 +61,7 @@ namespace Net::Udp {
// Dispatch to bound callback
for (uint32_t i = 0; i < MAX_BINDINGS; i++) {
if (g_bindings[i].Active && g_bindings[i].Port == dstPort) {
g_bindings[i].Callback(srcIp, srcPort, payload, payloadLen);
g_bindings[i].Callback(srcIp, srcPort, dstPort, payload, payloadLen);
return;
}
}
+3 -1
View File
@@ -19,7 +19,9 @@ namespace Net::Udp {
} __attribute__((packed));
// Callback type for receiving UDP data
using RecvCallback = void(*)(uint32_t srcIp, uint16_t srcPort, const uint8_t* data, uint16_t length);
using RecvCallback = void(*)(uint32_t srcIp, uint16_t srcPort,
uint16_t dstPort,
const uint8_t* data, uint16_t length);
// Initialize the UDP subsystem
void Initialize();
+26
View File
@@ -223,6 +223,32 @@ namespace Pci {
return LegacyRead8(bus, device, function, (uint8_t)offset);
}
// -------------------------------------------------------------------------
// PCI capability list traversal
// -------------------------------------------------------------------------
uint8_t FindCapability(uint8_t bus, uint8_t device, uint8_t function, uint8_t capId) {
// Check Status register bit 4 (Capabilities List present)
uint16_t status = ReadConfig16(bus, device, function, RegStatus);
if (!(status & (1 << 4))) {
return 0;
}
// Read Capabilities Pointer (offset 0x34), mask to dword-aligned
uint8_t offset = ReadConfig8(bus, device, function, 0x34) & 0xFC;
// Walk the linked list (cap_id @ +0, next_ptr @ +1)
while (offset != 0) {
uint8_t id = ReadConfig8(bus, device, function, offset);
if (id == capId) {
return offset;
}
offset = ReadConfig8(bus, device, function, offset + 1) & 0xFC;
}
return 0;
}
// -------------------------------------------------------------------------
// PCI class code names
// -------------------------------------------------------------------------
+7
View File
@@ -60,6 +60,13 @@ namespace Pci {
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value);
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
// PCI capability IDs
constexpr uint8_t PCI_CAP_MSI = 0x05;
// Walk the PCI capability linked list for a given device.
// Returns the config-space offset of the capability, or 0 if not found.
uint8_t FindCapability(uint8_t bus, uint8_t device, uint8_t function, uint8_t capId);
// Class code name lookup
const char* GetClassName(uint8_t classCode, uint8_t subClass);
-1
View File
@@ -54,7 +54,6 @@ namespace Sched {
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
int handle = Fs::Vfs::VfsOpen(vfsPath);
if (handle < 0) {
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
return 0;
}
+14 -4
View File
@@ -106,7 +106,6 @@ namespace Sched {
// Load ELF into the process's address space
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
if (entry == 0) {
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
return -1;
}
@@ -204,6 +203,13 @@ namespace Sched {
}
void Schedule() {
// Reclaim terminated process slots so they can be reused
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].state == ProcessState::Terminated) {
processTable[i].state = ProcessState::Free;
}
}
int next = -1;
int start = (currentPid >= 0) ? currentPid + 1 : 0;
@@ -311,9 +317,13 @@ namespace Sched {
}
bool IsAlive(int pid) {
if (pid < 0 || pid >= MaxProcesses) return false;
return processTable[pid].state == ProcessState::Ready
|| processTable[pid].state == ProcessState::Running;
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].pid == pid) {
return processTable[i].state == ProcessState::Ready
|| processTable[i].state == ProcessState::Running;
}
}
return false;
}
}
+6 -1
View File
@@ -11,6 +11,7 @@
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Sched/Scheduler.hpp>
#include <Drivers/Net/E1000E.hpp>
using namespace Kt;
@@ -37,10 +38,14 @@ namespace Timekeeping {
static bool g_schedEnabled = false;
// Timer IRQ handler: increment tick count and drive scheduler
// Timer IRQ handler: increment tick count, poll NIC, and drive scheduler
static void TimerHandler(uint8_t) {
g_tickCount = g_tickCount + 1;
// In polling mode, drain the NIC's RX ring from timer context
// (equivalent to a real NIC IRQ handler, runs with interrupts disabled)
Drivers::Net::E1000E::Poll();
if (g_schedEnabled) {
Sched::Tick();
}
+42 -8
View File
@@ -56,29 +56,63 @@ BINDIR := bin
# Discover all programs (each subdirectory under src/ is a program).
PROGRAMS := $(notdir $(wildcard src/*))
# Build targets: one ELF per program.
TARGETS := $(addprefix $(BINDIR)/,$(addsuffix .elf,$(PROGRAMS)))
# Games are built separately (doom has its own Makefile).
GAMES := doom
SYSTEM_PROGRAMS := $(filter-out $(GAMES),$(PROGRAMS))
# Build targets: system programs go to bin/os/, games are handled separately.
TARGETS := $(addprefix $(BINDIR)/os/,$(addsuffix .elf,$(SYSTEM_PROGRAMS)))
# Game data files to copy into bin/games/.
GAME_DATA := $(BINDIR)/games/doom1.wad
# Man pages source directory.
MANDIR := man
MANSRC := $(wildcard $(MANDIR)/*.*)
MANDST := $(patsubst $(MANDIR)/%,$(BINDIR)/man/%,$(MANSRC))
.PHONY: all clean
# Web content source directory.
WWWDIR := www
WWWSRC := $(wildcard $(WWWDIR)/*.*)
WWWDST := $(patsubst $(WWWDIR)/%,$(BINDIR)/www/%,$(WWWSRC))
all: $(TARGETS) $(MANDST)
# Home directory placeholder.
HOMEKEEP := $(BINDIR)/home/.keep
.PHONY: all clean doom
all: $(TARGETS) doom $(GAME_DATA) $(MANDST) $(WWWDST) $(HOMEKEEP)
# Build doom via its own Makefile.
doom:
$(MAKE) -C src/doom
# Copy doom1.wad from data/games/ into bin/games/.
$(BINDIR)/games/doom1.wad: data/games/doom1.wad
mkdir -p $(BINDIR)/games
cp $< $@
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
$(BINDIR)/man/%: $(MANDIR)/%
mkdir -p $(BINDIR)/man
cp $< $@
# Build each program from its source files.
# For now each program is a single .cpp file compiled and linked directly.
$(BINDIR)/%.elf: src/%/main.cpp link.ld GNUmakefile
mkdir -p $(BINDIR) obj/$*
# Copy web content into bin/www/ so mkramdisk.sh picks them up.
$(BINDIR)/www/%: $(WWWDIR)/%
mkdir -p $(BINDIR)/www
cp $< $@
# Create empty home directory with a keep file.
$(HOMEKEEP):
mkdir -p $(BINDIR)/home
touch $@
# Build each system program from its source files.
$(BINDIR)/os/%.elf: src/%/main.cpp link.ld GNUmakefile
mkdir -p $(BINDIR)/os obj/$*
$(CXX) $(CXXFLAGS) -c src/$*/main.cpp -o obj/$*/main.o
$(CXX) $(CXXFLAGS) $(LDFLAGS) obj/$*/main.o -o $@
clean:
rm -rf $(BINDIR) obj
$(MAKE) -C src/doom clean
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15
View File
@@ -48,8 +48,23 @@ namespace Zenith {
static constexpr uint64_t SYS_SEND = 34;
static constexpr uint64_t SYS_RECV = 35;
static constexpr uint64_t SYS_CLOSESOCK = 36;
static constexpr uint64_t SYS_GETNETCFG = 37;
static constexpr uint64_t SYS_SETNETCFG = 38;
static constexpr uint64_t SYS_SENDTO = 39;
static constexpr uint64_t SYS_RECVFROM = 40;
static constexpr uint64_t SYS_FWRITE = 41;
static constexpr uint64_t SYS_FCREATE = 42;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
struct NetCfg {
uint32_t ipAddress; // network byte order
uint32_t subnetMask; // network byte order
uint32_t gateway; // network byte order
uint8_t macAddress[6];
uint8_t _pad[2];
};
struct DateTime {
uint16_t Year;
+71
View File
@@ -0,0 +1,71 @@
/*
* string.h
* Common string and memory utility functions for ZenithOS programs
* Copyright (c) 2025-2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace zenith {
inline int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
}
inline bool streq(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
a++; b++;
}
return *a == *b;
}
inline bool starts_with(const char* str, const char* prefix) {
while (*prefix) {
if (*str != *prefix) return false;
str++; prefix++;
}
return true;
}
inline const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
inline void memcpy(void* dst, const void* src, uint64_t n) {
auto* d = (uint8_t*)dst;
auto* s = (const uint8_t*)src;
for (uint64_t i = 0; i < n; i++) d[i] = s[i];
}
inline void memmove(void* dst, const void* src, uint64_t n) {
auto* d = (uint8_t*)dst;
auto* s = (const uint8_t*)src;
if (d < s) {
for (uint64_t i = 0; i < n; i++) d[i] = s[i];
} else {
for (uint64_t i = n; i > 0; i--) d[i-1] = s[i-1];
}
}
inline void memset(void* dst, int val, uint64_t n) {
auto* d = (uint8_t*)dst;
for (uint64_t i = 0; i < n; i++) d[i] = (uint8_t)val;
}
inline void strcpy(char* dst, const char* src) {
while (*src) *dst++ = *src++;
*dst = '\0';
}
inline void strncpy(char* dst, const char* src, int max) {
int i = 0;
while (src[i] && i < max - 1) { dst[i] = src[i]; i++; }
dst[i] = '\0';
}
}
+20
View File
@@ -136,6 +136,14 @@ namespace zenith {
return (int)syscall3(Zenith::SYS_READDIR, (uint64_t)path, (uint64_t)names, (uint64_t)max);
}
// File write/create
inline int fwrite(int handle, const uint8_t* buf, uint64_t off, uint64_t size) {
return (int)syscall4(Zenith::SYS_FWRITE, (uint64_t)handle, (uint64_t)buf, off, size);
}
inline int fcreate(const char* path) {
return (int)syscall1(Zenith::SYS_FCREATE, (uint64_t)path);
}
// Memory
inline void* alloc(uint64_t size) { return (void*)syscall1(Zenith::SYS_ALLOC, size); }
inline void free(void* ptr) { syscall1(Zenith::SYS_FREE, (uint64_t)ptr); }
@@ -157,6 +165,10 @@ namespace zenith {
return (int32_t)syscall2(Zenith::SYS_PING, (uint64_t)ip, (uint64_t)timeoutMs);
}
// 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); }
// Sockets
inline int socket(int type) {
return (int)syscall1(Zenith::SYS_SOCKET, (uint64_t)type);
@@ -182,6 +194,14 @@ namespace zenith {
inline int closesocket(int fd) {
return (int)syscall1(Zenith::SYS_CLOSESOCK, (uint64_t)fd);
}
inline int sendto(int fd, const void* data, uint32_t len, uint32_t destIp, uint16_t destPort) {
return (int)syscall5(Zenith::SYS_SENDTO, (uint64_t)fd, (uint64_t)data,
(uint64_t)len, (uint64_t)destIp, (uint64_t)destPort);
}
inline int recvfrom(int fd, void* buf, uint32_t maxLen, uint32_t* srcIp, uint16_t* srcPort) {
return (int)syscall5(Zenith::SYS_RECVFROM, (uint64_t)fd, (uint64_t)buf,
(uint64_t)maxLen, (uint64_t)srcIp, (uint64_t)srcPort);
}
// Process management
inline void waitpid(int pid) { syscall1(Zenith::SYS_WAITPID, (uint64_t)pid); }
+48
View File
@@ -0,0 +1,48 @@
.TH DHCP 1
.SH NAME
dhcp - obtain network configuration via DHCP
.SH SYNOPSIS
dhcp
.SH DESCRIPTION
The DHCP client automatically obtains an IP address, subnet mask,
default gateway, and other network parameters from a DHCP server
on the local network using the Dynamic Host Configuration Protocol
(RFC 2131).
On success the network configuration is applied immediately via
set_netcfg(). On failure the original configuration is restored.
The client is run automatically by the init system at boot, but
may also be invoked manually from the shell.
.SH PROTOCOL
The client performs the standard four-message DHCP exchange:
1. DHCPDISCOVER Broadcast to 255.255.255.255:67
2. DHCPOFFER Server offers an IP address
3. DHCPREQUEST Client accepts the offered address
4. DHCPACK Server confirms the lease
The BROADCAST flag (0x8000) is set so that server replies are
sent to the broadcast address, since the client has no IP yet.
Each step has a 10-second timeout. If no response is received
the client exits with an error and restores the previous config.
.SH OUTPUT
On success the client prints the assigned configuration:
IP Address, Subnet Mask, Gateway, DNS Server, Lease Time
.SH OPTIONS
The DHCP client requests the following options from the server:
1 Subnet Mask
3 Router (default gateway)
6 DNS Server
51 Lease Time
.SH SEE ALSO
ifconfig(1), shell(1), syscalls(2)
+51
View File
@@ -0,0 +1,51 @@
.TH EDIT 1
.SH NAME
edit - text editor for ZenithOS
.SH SYNOPSIS
edit [filename]
.SH DESCRIPTION
edit is an interactive text editor. When invoked with a filename,
it opens the file for editing. If the file does not exist, a new
empty buffer is created and will be saved to that path on write.
When invoked without arguments, edit opens an empty buffer. You
will be prompted for a filename when saving.
.SH KEYBOARD SHORTCUTS
.SS Navigation
Arrow Keys Move cursor up/down/left/right
Home Move to start of line
End Move to end of line
Page Up Scroll up one page
Page Down Scroll down one page
.SS Editing
Backspace Delete character before cursor
Delete Delete character at cursor
Enter Insert new line
Tab Insert 4 spaces
.SS Commands
Ctrl+S Save file
Ctrl+Q Quit (warns if unsaved changes)
Ctrl+F Search for text
Ctrl+G Find next occurrence
.SH DISPLAY
The top line shows the filename, a modified indicator [+],
and the current cursor position (Ln, Col).
The bottom line shows keyboard shortcuts or status messages.
Line numbers are displayed in a gutter on the left side.
Lines past the end of the file are marked with ~.
.SH EXAMPLES
edit intro.1 Edit a file
edit Open a new empty buffer
.SH SEE ALSO
cat(1), shell(1)
+6 -4
View File
@@ -12,13 +12,13 @@
.SH DESCRIPTION
ZenithOS provides a simple read-only Virtual File System (VFS)
backed by the boot ramdisk. Files are accessed via paths in the
format "<drive>:/<name>", where drive 0 is the ramdisk.
format "<drive>:/<path>", where drive 0 is the ramdisk.
.SS open
Opens a file and returns a non-negative handle on success, or a
negative value on error (file not found, no free handles).
int h = zenith::open("0:/shell.elf");
int h = zenith::open("0:/os/hello.elf");
.SS read
Reads up to 'size' bytes starting at 'offset' into 'buf'.
@@ -42,15 +42,17 @@
.SS readdir
Lists entries in a directory. Up to 'max' entry names (max 64)
are written to the 'names' array. The kernel allocates a user-
accessible page for the string data automatically.
accessible page for the string data automatically. Directory
entries are returned with a trailing slash.
const char* entries[64];
int count = zenith::readdir("0:/", entries, 64);
// entries: "os/", "games/", "man/", "www/", "home/"
.SH READING PATTERN
The standard pattern for reading a file:
int h = zenith::open("0:/myfile.txt");
int h = zenith::open("0:/man/intro.1");
uint64_t size = zenith::getsize(h);
uint8_t buf[512];
uint64_t off = 0;
+39
View File
@@ -0,0 +1,39 @@
.TH INIT 1
.SH NAME
init - ZenithOS init system
.SH SYNOPSIS
Spawned automatically by the kernel as PID 0.
.SH DESCRIPTION
init is the first userspace process started by the ZenithOS
kernel. It chains system services in sequence, then launches
the interactive shell.
Each service is spawned as a child process. init waits for it
to exit before starting the next one. If a service fails to
spawn, init logs an error and continues to the next stage.
Log output is timestamped and color-coded:
HH:MM:SS INFO init Starting dhcp
HH:MM:SS OK init dhcp finished (pid 1)
.SH BOOT SEQUENCE
The following services are started in order:
1. 0:/os/dhcp.elf Obtain network configuration via DHCP
2. 0:/os/shell.elf Launch the interactive shell
After the shell exits, init enters an idle loop.
.SH LOG LEVELS
init uses four log levels, each with a distinct color:
OK Green Service completed successfully
INFO Cyan Informational (service starting, etc.)
WARN Yellow Non-fatal warning
FAIL Red Service failed to start
.SH SEE ALSO
dhcp(1), shell(1), syscalls(2)
+22 -6
View File
@@ -19,26 +19,42 @@
extern "C" void _start() { ... }
There is no argc/argv. Include <zenith/syscall.h> for the full
typed syscall API. Include <zenith/heap.h> for malloc/mfree.
There is no argc/argv. Use zenith::getargs() to retrieve any
arguments passed by the parent process. Include <zenith/syscall.h>
for the full typed syscall API.
Build with:
cd programs && make
The resulting ELF binary appears in programs/bin/.
The resulting ELF binary appears in programs/bin/os/.
.SH RAMDISK LAYOUT
The boot ramdisk is mounted as drive 0 with the following
directory structure:
0:/os/ System binaries (shell, init, man, etc.)
0:/games/ Games (doom)
0:/man/ Manual pages
0:/www/ Web server content
0:/home/ User home directory
.SH SHELL
The built-in shell is the primary way to interact with ZenithOS.
Type 'help' at the shell prompt for a list of commands. Use
'man shell' for detailed shell documentation.
The interactive shell is the primary way to interact with
ZenithOS. Commands are resolved by searching the PATH
directories (0:/os/, 0:/games/) for matching .elf binaries.
Type 'help' at the shell prompt for a list of commands.
Use 'man shell' for detailed shell documentation.
.SH MAN PAGES
The following man pages are available:
intro(1) This page
shell(1) Shell commands reference
init(1) Init system
dhcp(1) DHCP client
man(1) The man command itself
legal(7) Copyright and legal information
syscalls(2) Overview of all syscalls
spawn(2) Process spawning
file(2) File I/O syscalls
+4 -2
View File
@@ -11,7 +11,7 @@
fullscreen pager. Pages are stored as plain text files with
simple formatting directives.
If no section is specified, sections 1, 2, and 3 are searched
If no section is specified, sections 1 through 7 are searched
in order. If a section number is given, only that section is
checked.
@@ -27,9 +27,10 @@
q Quit
.SH SECTIONS
1 User commands (shell built-ins)
1 User commands and programs
2 System calls (kernel interface)
3 Library functions (userspace libraries)
7 Miscellaneous (legal, conventions)
.SH FILES
Man pages are stored on the ramdisk at:
@@ -42,6 +43,7 @@
man intro View the introduction
man 2 syscalls View syscall overview (section 2)
man malloc View malloc documentation
man legal View copyright information
.SH SEE ALSO
intro(1), shell(1), syscalls(2)
+77 -42
View File
@@ -3,66 +3,101 @@
shell - ZenithOS interactive command shell
.SH DESCRIPTION
The ZenithOS shell is a simple command interpreter that runs as
the first userspace process. It provides basic file inspection,
process management, networking, and documentation access.
The ZenithOS shell is a command interpreter launched by init
after system services have started. It provides command
execution, file navigation, and command history.
.SH COMMANDS
Commands are either shell builtins or external programs. When
a command is not a builtin, the shell searches for a matching
ELF binary and executes it as a child process.
.SH COMMAND RESOLUTION
When a non-builtin command is entered, the shell searches for
a matching binary in the following order:
1. 0:/os/<command>.elf
2. 0:/games/<command>.elf
3. 0:/<cwd>/<command>.elf (if cwd is set)
4. 0:/<command>.elf
The first match is spawned and the shell waits for it to exit.
If no match is found, the shell prints:
<command>: command not found
Arguments after the command name are passed to the spawned
process. File path arguments are resolved against the current
working directory before being passed to external programs.
.SH BUILTINS
.SS help
Display a list of available commands.
.SS info
Show the OS name, version, and syscall API version number.
.SS man <topic>
Open a manual page in the fullscreen pager. See man(1).
Display a categorized list of available commands.
.SS ls [dir]
List files in the current directory, or in the specified
directory. When a directory argument is given, the output
shows only the entries inside that directory with the
directory prefix stripped.
Examples: ls, ls man
directory prefix stripped. Directory entries are shown
with a trailing slash.
Examples: ls, ls man, ls os
.SS cd [dir]
Change the working directory. With no argument or with /,
returns to the root (0:/). Use cd .. to go up one level.
Trailing slashes on directory names are stripped.
The shell prompt reflects the current directory.
Examples: cd man, cd .., cd
.SS cat <file>
Print the contents of a file to the terminal. The file
path is resolved relative to the current directory.
Example: cat hello.elf
.SS run <file>
Spawn a new process from an ELF binary and wait for it to
exit. The file path is resolved relative to the current
directory. The shell blocks until the child process terminates.
Example: run hello.elf
.SS ping <ip>
Send 4 ICMP echo requests to the given IP address and display
round-trip times. Timeout is 3 seconds per request.
Example: ping 10.0.2.2
.SS date
Display the current date and time in UTC.
.SS uptime
Display the system uptime in minutes, seconds, and milliseconds.
.SS clear
Clear the terminal screen.
Examples: cd os, cd .., cd
.SS exit
Terminate the shell process.
.SH EXTERNAL COMMANDS
.SS System commands (0:/os/)
man <topic> View manual pages
cat <file> Display file contents
info Show system information
date Show current date and time
uptime Show system uptime
clear Clear the screen and framebuffer
reset Reboot the system
shutdown Shut down the system
.SS Network commands (0:/os/)
ping <ip> Send ICMP echo requests
ifconfig Show/set network configuration
tcpconnect <ip> <port> Interactive TCP client
irc IRC client
dhcp DHCP client
fetch HTTP client
httpd HTTP server
.SS Games (0:/games/)
doom DOOM
.SH INPUT
The shell reads input character by character using SYS_GETCHAR.
Backspace is supported. Lines are limited to 255 characters.
There is no command history or tab completion.
The shell uses non-blocking keyboard input via SYS_GETKEY to
support arrow key detection. Lines are limited to 255
characters.
.SS Editing
Backspace Delete character before cursor
Enter Execute command
.SS History
The shell stores the last 32 unique commands. Duplicate
consecutive entries are suppressed.
Up Arrow Recall previous command
Down Arrow Recall next command (or clear line)
.SH PROMPT
The prompt displays the current working directory:
0:/> _ (at root)
0:/os> _ (in os/ directory)
0:/man> _ (in man/ directory)
.SH SEE ALSO
man(1), intro(1), syscalls(2)
+3 -3
View File
@@ -13,11 +13,11 @@
Loads the ELF64 binary at the given VFS path and creates a new
process. The path must include the drive prefix, for example:
int pid = zenith::spawn("0:/hello.elf");
int pid = zenith::spawn("0:/os/hello.elf");
An optional second argument passes a string to the child:
int pid = zenith::spawn("0:/man.elf", "intro");
int pid = zenith::spawn("0:/os/man.elf", "intro");
The new process gets its own PML4 page table, a 16 KiB stack
(at 0x7FFFFEF000-0x7FFFFFF000), and begins executing at the
@@ -40,7 +40,7 @@
.SH EXAMPLES
Spawn a program and wait for it:
int pid = zenith::spawn("0:/hello.elf");
int pid = zenith::spawn("0:/os/hello.elf");
if (pid < 0) {
zenith::print("spawn failed\n");
} else {
+23 -3
View File
@@ -3,7 +3,7 @@
syscalls - overview of ZenithOS system calls
.SH DESCRIPTION
ZenithOS provides 37 system calls (numbers 0-36) for userspace
ZenithOS provides 41 system calls (numbers 0-40) for userspace
programs. Syscalls use the x86-64 SYSCALL instruction with the
following register convention:
@@ -120,8 +120,18 @@
Send an ICMP echo request and wait for reply.
int32_t zenith::ping(uint32_t ip, uint32_t timeoutMs);
.B SYS_GETNETCFG (37)
Get the current network configuration (IP, mask, gateway, MAC).
void zenith::get_netcfg(Zenith::NetCfg* out);
.B SYS_SETNETCFG (38)
Set the network configuration (IP, mask, gateway).
int zenith::set_netcfg(const Zenith::NetCfg* cfg);
.SH SOCKETS
.B SYS_SOCKET (29)
Create a socket. type=SOCK_TCP (1). Returns fd or -1.
Create a socket. type=SOCK_TCP (1) or SOCK_UDP (2).
Returns fd or -1.
int zenith::socket(int type);
.B SYS_CONNECT (30)
@@ -147,13 +157,23 @@
.B SYS_RECV (35)
Receive data from a connected socket. Returns bytes
received, 0 on connection close, or -1 on error.
received, 0 if no data available, or -1 on close/error.
int zenith::recv(int fd, void* buf, uint32_t maxLen);
.B SYS_CLOSESOCK (36)
Close a socket and release its resources.
int zenith::closesocket(int fd);
.B SYS_SENDTO (39)
Send a UDP datagram to a specific destination.
int zenith::sendto(int fd, const void* data, uint32_t len,
uint32_t destIp, uint16_t destPort);
.B SYS_RECVFROM (40)
Receive a UDP datagram. Returns the source address.
int zenith::recvfrom(int fd, void* buf, uint32_t maxLen,
uint32_t* srcIp, uint16_t* srcPort);
.SH FRAMEBUFFER
.B SYS_FBINFO (21)
Get framebuffer dimensions and format.
Binary file not shown.
Binary file not shown.
+66
View File
@@ -0,0 +1,66 @@
/*
* main.cpp
* cat - Display file contents
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: cat <filename>\n");
zenith::exit(1);
}
// Build VFS path. If the path already starts with "<digit>:", use as-is.
// Otherwise, prefix "0:/" to it.
char path[256];
bool hasPrefix = false;
if (args[0] >= '0' && args[0] <= '9' && args[1] == ':') {
hasPrefix = true;
}
int i = 0;
if (!hasPrefix) {
path[0] = '0'; path[1] = ':'; path[2] = '/';
i = 3;
}
int j = 0;
while (args[j] && i < 255) {
path[i++] = args[j++];
}
path[i] = '\0';
int handle = zenith::open(path);
if (handle < 0) {
zenith::print("cat: cannot open '");
zenith::print(args);
zenith::print("'\n");
zenith::exit(1);
}
uint64_t size = zenith::getsize(handle);
if (size == 0) {
zenith::close(handle);
zenith::exit(0);
}
uint8_t buf[512];
uint64_t offset = 0;
while (offset < size) {
uint64_t chunk = size - offset;
if (chunk > sizeof(buf) - 1) chunk = sizeof(buf) - 1;
int bytesRead = zenith::read(handle, buf, offset, chunk);
if (bytesRead <= 0) break;
buf[bytesRead] = '\0';
zenith::print((const char*)buf);
offset += bytesRead;
}
zenith::close(handle);
zenith::putchar('\n');
zenith::exit(0);
}
+27
View File
@@ -0,0 +1,27 @@
/*
* main.cpp
* clear - Clear terminal screen and framebuffer
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
// Clear the raw framebuffer (needed after graphical programs like DOOM)
Zenith::FbInfo fb;
zenith::fb_info(&fb);
uint8_t* pixels = (uint8_t*)zenith::fb_map();
if (pixels) {
for (uint64_t y = 0; y < fb.height; y++) {
uint32_t* row = (uint32_t*)(pixels + y * fb.pitch);
for (uint64_t x = 0; x < fb.width; x++) {
row[x] = 0x00000000;
}
}
}
// Reset the text console
zenith::print("\033[2J"); // Clear entire screen
zenith::print("\033[H"); // Move cursor to top-left
zenith::exit(0);
}
+56
View File
@@ -0,0 +1,56 @@
/*
* main.cpp
* date - Show current date and time
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static void print_int_padded(uint64_t n) {
if (n < 10) zenith::putchar('0');
print_int(n);
}
static const char* month_name(int m) {
static const char* months[] = {
"", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
if (m >= 1 && m <= 12) return months[m];
return "?";
}
extern "C" void _start() {
Zenith::DateTime dt;
zenith::gettime(&dt);
print_int(dt.Day);
zenith::putchar(' ');
zenith::print(month_name(dt.Month));
zenith::putchar(' ');
print_int(dt.Year);
zenith::print(", ");
print_int(dt.Hour);
zenith::putchar(':');
print_int_padded(dt.Minute);
zenith::putchar(':');
print_int_padded(dt.Second);
zenith::print(" UTC\n");
zenith::exit(0);
}
+495
View File
@@ -0,0 +1,495 @@
/*
* main.cpp
* DHCP client for ZenithOS
* Obtains network configuration automatically via DHCP (RFC 2131)
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::memcpy;
using zenith::memset;
// ---- Minimal snprintf (no libc available) ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState {
char* buf;
int pos;
int max;
};
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24];
int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState 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 = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long 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;
pf_putnum(&st, uval, 10, width, pad, neg);
break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*);
if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
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 st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
// ---- DHCP constants ----
static constexpr uint8_t BOOTREQUEST = 1;
static constexpr uint8_t BOOTREPLY = 2;
static constexpr uint8_t HTYPE_ETH = 1;
static constexpr uint8_t HLEN_ETH = 6;
static constexpr uint16_t DHCP_SERVER_PORT = 67;
static constexpr uint16_t DHCP_CLIENT_PORT = 68;
static constexpr uint32_t DHCP_MAGIC = 0x63825363; // network byte order
static constexpr uint16_t BROADCAST_FLAG = 0x8000;
// DHCP message types
static constexpr uint8_t DHCPDISCOVER = 1;
static constexpr uint8_t DHCPOFFER = 2;
static constexpr uint8_t DHCPREQUEST = 3;
static constexpr uint8_t DHCPACK = 5;
static constexpr uint8_t DHCPNAK = 6;
// DHCP option codes
static constexpr uint8_t OPT_SUBNET = 1;
static constexpr uint8_t OPT_ROUTER = 3;
static constexpr uint8_t OPT_DNS = 6;
static constexpr uint8_t OPT_REQUESTED_IP = 50;
static constexpr uint8_t OPT_LEASE_TIME = 51;
static constexpr uint8_t OPT_MSG_TYPE = 53;
static constexpr uint8_t OPT_SERVER_ID = 54;
static constexpr uint8_t OPT_PARAM_LIST = 55;
static constexpr uint8_t OPT_END = 255;
// ---- DHCP packet structure ----
struct DhcpPacket {
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint32_t ciaddr;
uint32_t yiaddr;
uint32_t siaddr;
uint32_t giaddr;
uint8_t chaddr[16];
uint8_t sname[64];
uint8_t file[128];
uint8_t options[312];
} __attribute__((packed));
// ---- Byte order helpers (DHCP uses network byte order) ----
static uint16_t htons(uint16_t v) {
return (uint16_t)((v >> 8) | (v << 8));
}
static uint32_t htonl(uint32_t v) {
return ((v >> 24) & 0xFF) | ((v >> 8) & 0xFF00) |
((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000u);
}
static uint32_t ntohl(uint32_t v) { return htonl(v); }
// ---- IP formatting ----
static void format_ip(char* buf, int size, uint32_t ip) {
const uint8_t* b = (const uint8_t*)&ip;
snprintf(buf, size, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
}
static void format_mac(char* buf, int size, const uint8_t* mac) {
snprintf(buf, size, "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
// ---- Build DHCP packets ----
static uint32_t g_xid = 0x5A454E49; // "ZENI"
static void build_base(DhcpPacket* pkt, const uint8_t* mac) {
memset(pkt, 0, sizeof(DhcpPacket));
pkt->op = BOOTREQUEST;
pkt->htype = HTYPE_ETH;
pkt->hlen = HLEN_ETH;
pkt->xid = g_xid;
pkt->flags = htons(BROADCAST_FLAG);
memcpy(pkt->chaddr, mac, 6);
// Magic cookie
pkt->options[0] = 0x63;
pkt->options[1] = 0x82;
pkt->options[2] = 0x53;
pkt->options[3] = 0x63;
}
static int build_discover(DhcpPacket* pkt, const uint8_t* mac) {
build_base(pkt, mac);
int off = 4; // after magic cookie
// Option 53: DHCP Message Type = DISCOVER
pkt->options[off++] = OPT_MSG_TYPE;
pkt->options[off++] = 1;
pkt->options[off++] = DHCPDISCOVER;
// Option 55: Parameter Request List
pkt->options[off++] = OPT_PARAM_LIST;
pkt->options[off++] = 4;
pkt->options[off++] = OPT_SUBNET;
pkt->options[off++] = OPT_ROUTER;
pkt->options[off++] = OPT_DNS;
pkt->options[off++] = OPT_LEASE_TIME;
pkt->options[off++] = OPT_END;
return (int)sizeof(DhcpPacket) - 312 + off;
}
static int build_request(DhcpPacket* pkt, const uint8_t* mac,
uint32_t requestedIp, uint32_t serverId) {
build_base(pkt, mac);
int off = 4;
// Option 53: DHCP Message Type = REQUEST
pkt->options[off++] = OPT_MSG_TYPE;
pkt->options[off++] = 1;
pkt->options[off++] = DHCPREQUEST;
// Option 50: Requested IP Address
pkt->options[off++] = OPT_REQUESTED_IP;
pkt->options[off++] = 4;
memcpy(&pkt->options[off], &requestedIp, 4);
off += 4;
// Option 54: Server Identifier
pkt->options[off++] = OPT_SERVER_ID;
pkt->options[off++] = 4;
memcpy(&pkt->options[off], &serverId, 4);
off += 4;
// Option 55: Parameter Request List
pkt->options[off++] = OPT_PARAM_LIST;
pkt->options[off++] = 4;
pkt->options[off++] = OPT_SUBNET;
pkt->options[off++] = OPT_ROUTER;
pkt->options[off++] = OPT_DNS;
pkt->options[off++] = OPT_LEASE_TIME;
pkt->options[off++] = OPT_END;
return (int)sizeof(DhcpPacket) - 312 + off;
}
// ---- Parse DHCP options ----
struct DhcpOffer {
uint32_t offeredIp;
uint32_t serverId;
uint32_t subnetMask;
uint32_t router;
uint32_t dns;
uint32_t leaseTime;
uint8_t msgType;
bool valid;
};
static void parse_options(const DhcpPacket* pkt, DhcpOffer* offer) {
offer->offeredIp = pkt->yiaddr;
offer->serverId = 0;
offer->subnetMask = 0;
offer->router = 0;
offer->dns = 0;
offer->leaseTime = 0;
offer->msgType = 0;
offer->valid = false;
// Verify magic cookie
if (pkt->options[0] != 0x63 || pkt->options[1] != 0x82 ||
pkt->options[2] != 0x53 || pkt->options[3] != 0x63) {
return;
}
int off = 4;
while (off < 312) {
uint8_t code = pkt->options[off++];
if (code == OPT_END) break;
if (code == 0) continue; // pad
if (off >= 312) break;
uint8_t len = pkt->options[off++];
if (off + len > 312) break;
switch (code) {
case OPT_MSG_TYPE:
if (len >= 1) offer->msgType = pkt->options[off];
break;
case OPT_SUBNET:
if (len >= 4) memcpy(&offer->subnetMask, &pkt->options[off], 4);
break;
case OPT_ROUTER:
if (len >= 4) memcpy(&offer->router, &pkt->options[off], 4);
break;
case OPT_DNS:
if (len >= 4) memcpy(&offer->dns, &pkt->options[off], 4);
break;
case OPT_SERVER_ID:
if (len >= 4) memcpy(&offer->serverId, &pkt->options[off], 4);
break;
case OPT_LEASE_TIME:
if (len >= 4) {
uint32_t raw;
memcpy(&raw, &pkt->options[off], 4);
offer->leaseTime = ntohl(raw);
}
break;
}
off += len;
}
offer->valid = (offer->msgType != 0);
}
// ---- Broadcast destination ----
static constexpr uint32_t BROADCAST_IP = 0xFFFFFFFF;
// ---- Main ----
extern "C" void _start() {
char msg[256];
zenith::print("ZenithOS DHCP Client\n");
// 1. Get MAC address
Zenith::NetCfg origCfg;
zenith::get_netcfg(&origCfg);
char macStr[32];
format_mac(macStr, sizeof(macStr), origCfg.macAddress);
snprintf(msg, sizeof(msg), "MAC address: %s\n", macStr);
zenith::print(msg);
// 2. Set IP to 0.0.0.0 to allow broadcast send/receive
Zenith::NetCfg zeroCfg;
zeroCfg.ipAddress = 0;
zeroCfg.subnetMask = 0;
zeroCfg.gateway = 0;
zenith::set_netcfg(&zeroCfg);
// 3. Create UDP socket and bind to port 68
int fd = zenith::socket(Zenith::SOCK_UDP);
if (fd < 0) {
zenith::print("Error: failed to create UDP socket\n");
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
if (zenith::bind(fd, DHCP_CLIENT_PORT) < 0) {
zenith::print("Error: failed to bind to port 68\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 4. Send DISCOVER
DhcpPacket pkt;
int pktLen = build_discover(&pkt, origCfg.macAddress);
zenith::print("Sending DHCPDISCOVER...\n");
if (zenith::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) {
zenith::print("Error: failed to send DISCOVER\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 5. Wait for OFFER
DhcpPacket resp;
DhcpOffer offer;
uint64_t startMs = zenith::get_milliseconds();
bool gotOffer = false;
zenith::print("Waiting for DHCPOFFER...\n");
while (zenith::get_milliseconds() - startMs < 10000) {
uint32_t srcIp;
uint16_t srcPort;
int r = zenith::recvfrom(fd, (void*)&resp, sizeof(resp), &srcIp, &srcPort);
if (r > 0) {
if (resp.op == BOOTREPLY && resp.xid == g_xid) {
parse_options(&resp, &offer);
if (offer.valid && offer.msgType == DHCPOFFER) {
gotOffer = true;
break;
}
}
}
zenith::yield();
}
if (!gotOffer) {
zenith::print("Error: no DHCPOFFER received (timeout)\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
char ipStr[32];
format_ip(ipStr, sizeof(ipStr), offer.offeredIp);
snprintf(msg, sizeof(msg), "Received OFFER: %s\n", ipStr);
zenith::print(msg);
// 6. Send REQUEST
pktLen = build_request(&pkt, origCfg.macAddress, offer.offeredIp, offer.serverId);
zenith::print("Sending DHCPREQUEST...\n");
if (zenith::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) {
zenith::print("Error: failed to send REQUEST\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 7. Wait for ACK
bool gotAck = false;
startMs = zenith::get_milliseconds();
zenith::print("Waiting for DHCPACK...\n");
while (zenith::get_milliseconds() - startMs < 10000) {
uint32_t srcIp;
uint16_t srcPort;
int r = zenith::recvfrom(fd, (void*)&resp, sizeof(resp), &srcIp, &srcPort);
if (r > 0) {
if (resp.op == BOOTREPLY && resp.xid == g_xid) {
parse_options(&resp, &offer);
if (offer.valid && offer.msgType == DHCPACK) {
gotAck = true;
break;
}
if (offer.valid && offer.msgType == DHCPNAK) {
zenith::print("Error: received DHCPNAK from server\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
}
}
zenith::yield();
}
zenith::closesocket(fd);
if (!gotAck) {
zenith::print("Error: no DHCPACK received (timeout)\n");
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 8. Apply configuration
Zenith::NetCfg newCfg;
newCfg.ipAddress = offer.offeredIp;
newCfg.subnetMask = offer.subnetMask;
newCfg.gateway = offer.router;
zenith::set_netcfg(&newCfg);
// 9. Print results
zenith::print("\nDHCP configuration applied:\n");
format_ip(ipStr, sizeof(ipStr), offer.offeredIp);
snprintf(msg, sizeof(msg), " IP Address: %s\n", ipStr);
zenith::print(msg);
format_ip(ipStr, sizeof(ipStr), offer.subnetMask);
snprintf(msg, sizeof(msg), " Subnet Mask: %s\n", ipStr);
zenith::print(msg);
format_ip(ipStr, sizeof(ipStr), offer.router);
snprintf(msg, sizeof(msg), " Gateway: %s\n", ipStr);
zenith::print(msg);
if (offer.dns != 0) {
format_ip(ipStr, sizeof(ipStr), offer.dns);
snprintf(msg, sizeof(msg), " DNS Server: %s\n", ipStr);
zenith::print(msg);
}
if (offer.leaseTime != 0) {
snprintf(msg, sizeof(msg), " Lease Time: %u seconds\n", offer.leaseTime);
zenith::print(msg);
}
zenith::exit(0);
}
+2 -2
View File
@@ -163,14 +163,14 @@ ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS)
# ---- Target ----
TARGET := $(BINDIR)/doom.elf
TARGET := $(BINDIR)/games/doom.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)
mkdir -p $(BINDIR)/games
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
# DOOM source files (from doomgeneric directory)
+1 -1
View File
@@ -222,7 +222,7 @@ void DG_SetWindowTitle(const char* title) {
/* ---- Entry point ---- */
void _start(void) {
char *argv[] = { "doom", "-iwad", "0:/doom1.wad", 0 };
char *argv[] = { "doom", "-iwad", "0:/games/doom1.wad", 0 };
doomgeneric_Create(3, argv);
for (;;) {
doomgeneric_Tick();
+832
View File
@@ -0,0 +1,832 @@
/*
* main.cpp
* edit - Text editor for ZenithOS
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/heap.h>
#include <zenith/string.h>
using zenith::slen;
using zenith::memcpy;
using zenith::memmove;
// ---- Integer to string ----
static int itoa(int val, char* buf) {
if (val == 0) { buf[0] = '0'; buf[1] = '\0'; return 1; }
char tmp[12];
int i = 0;
bool neg = false;
if (val < 0) { neg = true; val = -val; }
while (val > 0) { tmp[i++] = '0' + val % 10; val /= 10; }
int len = 0;
if (neg) buf[len++] = '-';
for (int j = i - 1; j >= 0; j--) buf[len++] = tmp[j];
buf[len] = '\0';
return len;
}
// ---- Terminal output helpers ----
static void print(const char* s) { zenith::print(s); }
static void putch(char c) { zenith::putchar(c); }
static void print_int(int v) {
char buf[12];
itoa(v, buf);
print(buf);
}
// ANSI escape helpers
static void esc(const char* seq) {
putch('\033');
putch('[');
print(seq);
}
static void cursor_to(int row, int col) {
putch('\033'); putch('[');
print_int(row); putch(';');
print_int(col); putch('H');
}
static void clear_line() { esc("2K"); }
static void hide_cursor() { esc("?25l"); }
static void show_cursor() { esc("?25h"); }
static void enter_alt_screen() { esc("?1049h"); }
static void exit_alt_screen() { esc("?1049l"); }
static void reset_attrs() { esc("0m"); }
static void reverse_video() { esc("7m"); }
static void dim_text() { esc("2m"); }
// ---- Line buffer ----
struct Line {
char* data;
int len;
int cap;
};
static constexpr int MAX_LINES = 10000;
static constexpr int INITIAL_LINE_CAP = 64;
static constexpr int TAB_WIDTH = 4;
static Line* lines = nullptr;
static int numLines = 0;
// Editor state
static int cursorRow = 0; // cursor position in document (0-indexed)
static int cursorCol = 0;
static int topLine = 0; // first visible line
static int leftCol = 0; // horizontal scroll offset
static int screenRows = 24;
static int screenCols = 80;
static int editorRows = 0; // screenRows - 2 (status + hint bars)
static int gutterWidth = 4; // line number gutter width
static bool modified = false;
static bool running = true;
static bool fullRedraw = true;
static char filename[256] = "";
static bool hasFilename = false;
// Search state
static char searchQuery[128] = "";
static int searchLen = 0;
// Status message
static char statusMsg[128] = "";
static uint64_t statusMsgTime = 0;
// ---- Line operations ----
static void line_init(Line* ln) {
ln->cap = INITIAL_LINE_CAP;
ln->data = (char*)zenith::malloc(ln->cap);
ln->len = 0;
ln->data[0] = '\0';
}
static void line_ensure(Line* ln, int needed) {
if (needed + 1 <= ln->cap) return;
int newCap = ln->cap;
while (newCap < needed + 1) newCap *= 2;
ln->data = (char*)zenith::realloc(ln->data, newCap);
ln->cap = newCap;
}
static void line_insert_char(Line* ln, int pos, char c) {
if (pos < 0) pos = 0;
if (pos > ln->len) pos = ln->len;
line_ensure(ln, ln->len + 1);
memmove(ln->data + pos + 1, ln->data + pos, ln->len - pos);
ln->data[pos] = c;
ln->len++;
ln->data[ln->len] = '\0';
}
static void line_delete_char(Line* ln, int pos) {
if (pos < 0 || pos >= ln->len) return;
memmove(ln->data + pos, ln->data + pos + 1, ln->len - pos - 1);
ln->len--;
ln->data[ln->len] = '\0';
}
static void line_append(Line* ln, const char* s, int slen) {
line_ensure(ln, ln->len + slen);
memcpy(ln->data + ln->len, s, slen);
ln->len += slen;
ln->data[ln->len] = '\0';
}
// ---- Document operations ----
static void insert_line(int at) {
if (numLines >= MAX_LINES) return;
if (at < 0) at = 0;
if (at > numLines) at = numLines;
// Shift lines down
for (int i = numLines; i > at; i--) {
lines[i] = lines[i - 1];
}
line_init(&lines[at]);
numLines++;
}
static void delete_line(int at) {
if (at < 0 || at >= numLines) return;
if (numLines <= 1) {
// Don't delete the last line, just clear it
lines[at].len = 0;
lines[at].data[0] = '\0';
return;
}
zenith::mfree(lines[at].data);
for (int i = at; i < numLines - 1; i++) {
lines[i] = lines[i + 1];
}
numLines--;
}
// ---- Compute gutter width from line count ----
static void update_gutter_width() {
int digits = 1;
int n = numLines;
while (n >= 10) { digits++; n /= 10; }
gutterWidth = digits + 2; // digits + space + separator
if (gutterWidth < 4) gutterWidth = 4;
}
// ---- File I/O ----
static void set_status(const char* msg) {
int i = 0;
while (msg[i] && i < 126) { statusMsg[i] = msg[i]; i++; }
statusMsg[i] = '\0';
statusMsgTime = zenith::get_milliseconds();
}
// Build VFS path from filename
static void build_path(const char* fname, char* out, int outMax) {
int i = 0;
// Check if already has drive prefix
bool hasPrefix = (fname[0] >= '0' && fname[0] <= '9' && fname[1] == ':');
if (!hasPrefix) {
out[i++] = '0'; out[i++] = ':'; out[i++] = '/';
}
int j = 0;
while (fname[j] && i < outMax - 1) {
out[i++] = fname[j++];
}
out[i] = '\0';
}
static void load_file(const char* fname) {
char path[256];
build_path(fname, path, sizeof(path));
int handle = zenith::open(path);
if (handle < 0) {
// New file
numLines = 1;
line_init(&lines[0]);
set_status("(New file)");
return;
}
uint64_t size = zenith::getsize(handle);
// Read entire file into a temp buffer
uint8_t* buf = nullptr;
if (size > 0) {
buf = (uint8_t*)zenith::malloc(size + 1);
uint64_t off = 0;
while (off < size) {
int r = zenith::read(handle, buf + off, off, size - off);
if (r <= 0) break;
off += r;
}
buf[size] = '\0';
}
zenith::close(handle);
// Parse into lines
numLines = 0;
if (buf && size > 0) {
uint64_t lineStart = 0;
for (uint64_t i = 0; i <= size; i++) {
if (i == size || buf[i] == '\n') {
if (numLines >= MAX_LINES) break;
line_init(&lines[numLines]);
int lineLen = (int)(i - lineStart);
if (lineLen > 0) {
line_append(&lines[numLines], (const char*)buf + lineStart, lineLen);
}
numLines++;
lineStart = i + 1;
}
}
}
if (buf) zenith::mfree(buf);
if (numLines == 0) {
numLines = 1;
line_init(&lines[0]);
}
set_status("File loaded");
}
static bool save_file() {
if (!hasFilename) {
set_status("No filename! Use Ctrl+S after setting a name");
return false;
}
char path[256];
build_path(filename, path, sizeof(path));
// Calculate total size
uint64_t totalSize = 0;
for (int i = 0; i < numLines; i++) {
totalSize += lines[i].len;
if (i < numLines - 1) totalSize++; // newline
}
// Try to open existing file first
int handle = zenith::open(path);
if (handle < 0) {
// Create new file
handle = zenith::fcreate(path);
if (handle < 0) {
set_status("Error: could not create file");
return false;
}
}
// Build content buffer and write
uint8_t* buf = (uint8_t*)zenith::malloc(totalSize + 1);
uint64_t off = 0;
for (int i = 0; i < numLines; i++) {
if (lines[i].len > 0) {
memcpy(buf + off, lines[i].data, lines[i].len);
off += lines[i].len;
}
if (i < numLines - 1) {
buf[off++] = '\n';
}
}
int result = zenith::fwrite(handle, buf, 0, totalSize);
zenith::close(handle);
zenith::mfree(buf);
if (result < 0) {
set_status("Error: write failed");
return false;
}
modified = false;
set_status("File saved");
return true;
}
// ---- Prompt for input in the hint bar area ----
static int prompt_input(const char* promptStr, char* out, int outMax) {
// Draw prompt on the last row
cursor_to(screenRows, 1);
reverse_video();
clear_line();
print(promptStr);
reset_attrs();
show_cursor();
int pos = 0;
out[0] = '\0';
while (true) {
if (!zenith::is_key_available()) {
zenith::yield();
continue;
}
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
if (ev.ascii == '\033' || (ev.ctrl && ev.ascii == 'q')) {
// Cancel
return -1;
}
if (ev.ascii == '\n') {
out[pos] = '\0';
return pos;
}
if (ev.ascii == '\b') {
if (pos > 0) {
pos--;
putch('\b'); putch(' '); putch('\b');
}
} else if (ev.ascii >= ' ' && pos < outMax - 1) {
out[pos++] = ev.ascii;
out[pos] = '\0';
putch(ev.ascii);
}
}
}
// ---- Rendering ----
static void draw_status_bar() {
cursor_to(1, 1);
reverse_video();
clear_line();
// Left side: "edit: filename [+]"
print(" edit: ");
if (hasFilename) {
print(filename);
} else {
print("[No Name]");
}
if (modified) print(" [+]");
// Right side: cursor position
// Calculate right-side text
char posBuf[32];
int p = 0;
posBuf[p++] = 'L';
posBuf[p++] = 'n';
posBuf[p++] = ' ';
p += itoa(cursorRow + 1, posBuf + p);
posBuf[p++] = ',';
posBuf[p++] = ' ';
posBuf[p++] = 'C';
posBuf[p++] = 'o';
posBuf[p++] = 'l';
posBuf[p++] = ' ';
p += itoa(cursorCol + 1, posBuf + p);
posBuf[p++] = ' ';
posBuf[p++] = ' ';
posBuf[p] = '\0';
// Pad to right-align
// We need to figure out how many chars we've printed on the left
int leftLen = 8 + (hasFilename ? slen(filename) : 9) + (modified ? 4 : 0);
int rightLen = p;
int padding = screenCols - leftLen - rightLen;
for (int i = 0; i < padding; i++) putch(' ');
print(posBuf);
reset_attrs();
}
static void draw_hint_bar() {
cursor_to(screenRows, 1);
reverse_video();
clear_line();
// Show status message if recent (within 3 seconds)
uint64_t now = zenith::get_milliseconds();
if (statusMsg[0] && (now - statusMsgTime) < 3000) {
print(" ");
print(statusMsg);
} else {
print(" ^S Save ^Q Quit ^F Find ^G Find Next");
}
// Pad rest of line
reset_attrs();
}
static void draw_line(int screenRow, int docLine) {
cursor_to(screenRow + 2, 1); // +2 because row 1 is status bar
clear_line();
if (docLine < numLines) {
// Line number gutter
dim_text();
char numBuf[12];
int numLen = itoa(docLine + 1, numBuf);
// Right-align the number in the gutter
int pad = gutterWidth - 2 - numLen; // -2 for trailing space+pipe
for (int i = 0; i < pad; i++) putch(' ');
print(numBuf);
putch(' ');
reset_attrs();
// Line content
Line* ln = &lines[docLine];
int startCol = leftCol;
int maxChars = screenCols - gutterWidth;
for (int c = 0; c < maxChars && startCol + c < ln->len; c++) {
putch(ln->data[startCol + c]);
}
} else {
// Past end of file
dim_text();
putch('~');
reset_attrs();
}
}
static void render() {
hide_cursor();
update_gutter_width();
draw_status_bar();
if (fullRedraw) {
for (int i = 0; i < editorRows; i++) {
draw_line(i, topLine + i);
}
fullRedraw = false;
}
draw_hint_bar();
// Position cursor
int screenY = cursorRow - topLine + 2; // +2 for status bar
int screenX = cursorCol - leftCol + gutterWidth;
cursor_to(screenY, screenX);
show_cursor();
}
// ---- Scrolling ----
static void scroll() {
// Vertical scrolling
if (cursorRow < topLine) {
topLine = cursorRow;
fullRedraw = true;
}
if (cursorRow >= topLine + editorRows) {
topLine = cursorRow - editorRows + 1;
fullRedraw = true;
}
// Horizontal scrolling
int textCols = screenCols - gutterWidth;
if (cursorCol < leftCol) {
leftCol = cursorCol;
fullRedraw = true;
}
if (cursorCol >= leftCol + textCols) {
leftCol = cursorCol - textCols + 1;
fullRedraw = true;
}
}
// ---- Editing operations ----
static void insert_char(char c) {
line_insert_char(&lines[cursorRow], cursorCol, c);
cursorCol++;
modified = true;
fullRedraw = true;
}
static void insert_tab() {
for (int i = 0; i < TAB_WIDTH; i++) {
insert_char(' ');
}
}
static void insert_newline() {
Line* current = &lines[cursorRow];
// Split current line at cursor position
insert_line(cursorRow + 1);
// Re-get pointer since insert_line shifted memory
current = &lines[cursorRow];
Line* newLine = &lines[cursorRow + 1];
// Move text after cursor to new line
if (cursorCol < current->len) {
line_append(newLine, current->data + cursorCol, current->len - cursorCol);
current->len = cursorCol;
current->data[current->len] = '\0';
}
cursorRow++;
cursorCol = 0;
modified = true;
fullRedraw = true;
}
static void delete_char_backspace() {
if (cursorCol > 0) {
line_delete_char(&lines[cursorRow], cursorCol - 1);
cursorCol--;
modified = true;
fullRedraw = true;
} else if (cursorRow > 0) {
// Join with previous line
int prevLen = lines[cursorRow - 1].len;
line_append(&lines[cursorRow - 1], lines[cursorRow].data, lines[cursorRow].len);
delete_line(cursorRow);
cursorRow--;
cursorCol = prevLen;
modified = true;
fullRedraw = true;
}
}
static void delete_char_forward() {
if (cursorCol < lines[cursorRow].len) {
line_delete_char(&lines[cursorRow], cursorCol);
modified = true;
fullRedraw = true;
} else if (cursorRow < numLines - 1) {
// Join with next line
line_append(&lines[cursorRow], lines[cursorRow + 1].data, lines[cursorRow + 1].len);
delete_line(cursorRow + 1);
modified = true;
fullRedraw = true;
}
}
// ---- Search ----
static void find_next(bool fromPrompt) {
if (searchLen == 0) return;
// Start search from cursor position + 1
int startRow = cursorRow;
int startCol = cursorCol + (fromPrompt ? 0 : 1);
for (int i = 0; i < numLines; i++) {
int row = (startRow + i) % numLines;
int colStart = (i == 0) ? startCol : 0;
Line* ln = &lines[row];
for (int c = colStart; c <= ln->len - searchLen; c++) {
bool match = true;
for (int k = 0; k < searchLen; k++) {
if (ln->data[c + k] != searchQuery[k]) {
match = false;
break;
}
}
if (match) {
cursorRow = row;
cursorCol = c;
fullRedraw = true;
set_status("Found");
return;
}
}
}
set_status("Not found");
}
static void do_search() {
char query[128];
int len = prompt_input("Search: ", query, sizeof(query));
if (len < 0) {
fullRedraw = true;
return;
}
if (len == 0) {
fullRedraw = true;
return;
}
// Copy query
for (int i = 0; i < len; i++) searchQuery[i] = query[i];
searchQuery[len] = '\0';
searchLen = len;
find_next(true);
fullRedraw = true;
}
// ---- Navigation scancodes ----
static constexpr uint8_t SC_UP = 0x48;
static constexpr uint8_t SC_DOWN = 0x50;
static constexpr uint8_t SC_LEFT = 0x4B;
static constexpr uint8_t SC_RIGHT = 0x4D;
static constexpr uint8_t SC_HOME = 0x47;
static constexpr uint8_t SC_END = 0x4F;
static constexpr uint8_t SC_PGUP = 0x49;
static constexpr uint8_t SC_PGDN = 0x51;
static constexpr uint8_t SC_DELETE = 0x53;
// ---- Input handling ----
static void handle_key(const Zenith::KeyEvent& ev) {
if (!ev.pressed) return;
// Ctrl key combinations
if (ev.ctrl) {
switch (ev.ascii) {
case 'q':
if (modified) {
set_status("Unsaved changes! Press Ctrl+Q again to quit");
// Set a flag so next Ctrl+Q quits
static bool warnedOnce = false;
if (warnedOnce) {
running = false;
}
warnedOnce = true;
return;
}
running = false;
return;
case 's':
if (!hasFilename) {
char nameBuf[256];
int len = prompt_input("Save as: ", nameBuf, sizeof(nameBuf));
if (len > 0) {
for (int i = 0; i <= len; i++) filename[i] = nameBuf[i];
hasFilename = true;
}
fullRedraw = true;
if (!hasFilename) return;
}
save_file();
fullRedraw = true;
return;
case 'f':
do_search();
return;
case 'g':
find_next(false);
return;
default:
break;
}
}
// Non-ASCII keys (scancode-based)
if (ev.ascii == 0) {
switch (ev.scancode) {
case SC_UP:
if (cursorRow > 0) {
cursorRow--;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
}
return;
case SC_DOWN:
if (cursorRow < numLines - 1) {
cursorRow++;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
}
return;
case SC_LEFT:
if (cursorCol > 0) {
cursorCol--;
} else if (cursorRow > 0) {
cursorRow--;
cursorCol = lines[cursorRow].len;
}
fullRedraw = true;
return;
case SC_RIGHT:
if (cursorCol < lines[cursorRow].len) {
cursorCol++;
} else if (cursorRow < numLines - 1) {
cursorRow++;
cursorCol = 0;
}
fullRedraw = true;
return;
case SC_HOME:
cursorCol = 0;
fullRedraw = true;
return;
case SC_END:
cursorCol = lines[cursorRow].len;
fullRedraw = true;
return;
case SC_PGUP:
cursorRow -= editorRows;
if (cursorRow < 0) cursorRow = 0;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
return;
case SC_PGDN:
cursorRow += editorRows;
if (cursorRow >= numLines) cursorRow = numLines - 1;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
return;
case SC_DELETE:
delete_char_forward();
return;
default:
return;
}
}
// Regular keys
switch (ev.ascii) {
case '\n':
insert_newline();
break;
case '\b':
delete_char_backspace();
break;
case '\t':
insert_tab();
break;
default:
if (ev.ascii >= ' ') {
insert_char(ev.ascii);
}
break;
}
}
// ---- Entry point ----
extern "C" void _start() {
// Allocate line buffer
lines = (Line*)zenith::malloc(sizeof(Line) * MAX_LINES);
// Get terminal size
zenith::termsize(&screenCols, &screenRows);
editorRows = screenRows - 2;
// Parse arguments
char args[256];
int argLen = zenith::getargs(args, sizeof(args));
if (argLen > 0 && args[0] != '\0') {
// Copy filename
int i = 0;
while (args[i] && i < 255) { filename[i] = args[i]; i++; }
filename[i] = '\0';
hasFilename = true;
load_file(filename);
} else {
// New empty buffer
numLines = 1;
line_init(&lines[0]);
}
// Enter alternate screen
enter_alt_screen();
fullRedraw = true;
// Main loop
while (running) {
scroll();
render();
// Wait for input
while (!zenith::is_key_available()) {
zenith::yield();
}
Zenith::KeyEvent ev;
zenith::getkey(&ev);
handle_key(ev);
}
// Exit alternate screen
exit_alt_screen();
show_cursor();
reset_attrs();
zenith::exit(0);
}
+359
View File
@@ -0,0 +1,359 @@
/*
* main.cpp
* HTTP/1.0 client for ZenithOS
* Usage: run fetch.elf <server_ip> <port> <path>
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::skip_spaces;
using zenith::memcpy;
// ---- Minimal snprintf (no libc available) ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState {
char* buf;
int pos;
int max;
};
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24];
int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState 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 = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long 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;
pf_putnum(&st, uval, 10, width, pad, neg);
break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*);
if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
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 st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
// ---- IP/port parsing ----
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0; hasDigit = false;
if (c == '\0') break;
} else return false;
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
// ---- HTTP response parser ----
// Find "\r\n\r\n" boundary between headers and body
static int find_header_end(const char* buf, int len) {
for (int i = 0; i + 3 < len; i++) {
if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n')
return i + 4;
}
return -1;
}
// Extract HTTP status code from first line: "HTTP/1.x NNN ..."
static int parse_status_code(const char* buf, int len) {
// Find first space
int i = 0;
while (i < len && buf[i] != ' ') i++;
if (i >= len) return -1;
i++; // skip space
// Parse 3-digit status
if (i + 2 >= len) return -1;
if (buf[i] < '0' || buf[i] > '9') return -1;
int code = (buf[i] - '0') * 100 + (buf[i+1] - '0') * 10 + (buf[i+2] - '0');
return code;
}
// Extract status text from first line: "HTTP/1.x NNN Status Text\r\n"
static void parse_status_text(const char* buf, int len, char* out, int outMax) {
// Skip "HTTP/1.x NNN "
int i = 0;
while (i < len && buf[i] != ' ') i++;
i++; // skip first space (after HTTP/1.x)
while (i < len && buf[i] != ' ') i++;
i++; // skip second space (after status code)
// Copy until \r or end
int j = 0;
while (i < len && buf[i] != '\r' && buf[i] != '\n' && j < outMax - 1) {
out[j++] = buf[i++];
}
out[j] = '\0';
}
// ---- Main ----
extern "C" void _start() {
// Parse arguments: <server_ip> <port> <path>
char argbuf[512];
zenith::getargs(argbuf, sizeof(argbuf));
const char* arg = skip_spaces(argbuf);
if (*arg == '\0') {
zenith::print("Usage: fetch.elf <server_ip> <port> <path>\n");
zenith::print("Example: run fetch.elf 10.0.68.1 80 /\n");
zenith::print(" run fetch.elf 93.184.216.34 80 /index.html\n");
zenith::exit(0);
}
// Parse IP
char ipStr[32];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 31) { ipStr[i] = arg[i]; i++; }
ipStr[i] = '\0';
arg = skip_spaces(arg + i);
uint32_t serverIp;
if (!parse_ip(ipStr, &serverIp)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Parse port
char portStr[16];
i = 0;
while (arg[i] && arg[i] != ' ' && i < 15) { portStr[i] = arg[i]; i++; }
portStr[i] = '\0';
arg = skip_spaces(arg + i);
uint16_t serverPort;
if (!parse_uint16(portStr, &serverPort)) {
zenith::print("Invalid port: ");
zenith::print(portStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Parse path (rest of args, default to "/" if empty)
char path[256];
if (*arg) {
i = 0;
while (arg[i] && i < 255) { path[i] = arg[i]; i++; }
path[i] = '\0';
} else {
path[0] = '/'; path[1] = '\0';
}
// Print connection info
char msg[256];
snprintf(msg, sizeof(msg), "Connecting to %s:%d...\n", ipStr, (int)serverPort);
zenith::print(msg);
// Create socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
// Connect
if (zenith::connect(fd, serverIp, serverPort) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
zenith::exit(1);
}
// Build and send HTTP request
char request[512];
int reqLen = snprintf(request, sizeof(request),
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"User-Agent: ZenithOS/1.0\r\n"
"Connection: close\r\n"
"\r\n",
path, ipStr);
snprintf(msg, sizeof(msg), "GET %s\n", path);
zenith::print(msg);
if (zenith::send(fd, request, reqLen) < 0) {
zenith::print("Error: failed to send request\n");
zenith::closesocket(fd);
zenith::exit(1);
}
// Receive response
// We accumulate the full response to parse headers, then print the body.
// Use a large static buffer (32 KB) since we can't save to files anyway.
static char respBuf[32768];
int respLen = 0;
bool aborted = false;
int idleCount = 0;
while (respLen < (int)sizeof(respBuf) - 1) {
// Check for Ctrl+Q to abort
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
aborted = true;
break;
}
}
int r = zenith::recv(fd, respBuf + respLen, sizeof(respBuf) - 1 - respLen);
if (r > 0) {
respLen += r;
idleCount = 0;
} else if (r == 0) {
// Connection closed by server — done
break;
} else {
// No data available or error
idleCount++;
if (idleCount > 2000) {
// Assume connection closed after extended idle
break;
}
zenith::yield();
}
}
respBuf[respLen] = '\0';
zenith::closesocket(fd);
if (aborted) {
zenith::print("\nAborted.\n");
zenith::exit(0);
}
if (respLen == 0) {
zenith::print("Error: empty response\n");
zenith::exit(1);
}
// Parse response headers
int headerEnd = find_header_end(respBuf, respLen);
if (headerEnd < 0) {
// No proper header/body separator — just print everything
zenith::print("Warning: malformed response (no header boundary)\n\n");
zenith::print(respBuf);
zenith::putchar('\n');
zenith::exit(0);
}
int statusCode = parse_status_code(respBuf, headerEnd);
char statusText[64];
parse_status_text(respBuf, headerEnd, statusText, sizeof(statusText));
int bodyLen = respLen - headerEnd;
// Print summary
snprintf(msg, sizeof(msg), "HTTP/1.0 %d %s (%d bytes)\n\n", statusCode, statusText, bodyLen);
zenith::print(msg);
// Print body — it may contain null bytes in binary content, but for text we
// can just print as a string. For binary, we print what we can.
if (bodyLen > 0) {
// Print body in chunks (print expects null-terminated)
const char* body = respBuf + headerEnd;
int printed = 0;
char chunk[512];
while (printed < bodyLen) {
int n = bodyLen - printed;
if (n > (int)sizeof(chunk) - 1) n = (int)sizeof(chunk) - 1;
memcpy(chunk, body + printed, n);
chunk[n] = '\0';
zenith::print(chunk);
printed += n;
}
zenith::putchar('\n');
}
zenith::exit(0);
}
+562
View File
@@ -0,0 +1,562 @@
/*
* main.cpp
* HTTP/1.0 server for ZenithOS
* Usage: run httpd.elf [port] (default: 80)
* Serves a built-in index page and files from the VFS
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::slen;
using zenith::streq;
using zenith::starts_with;
using zenith::skip_spaces;
// ---- Minimal snprintf (no libc available) ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState {
char* buf;
int pos;
int max;
};
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24];
int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState 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 = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long 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;
pf_putnum(&st, uval, 10, width, pad, neg);
break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*);
if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
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 st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
// ---- IP/port parsing ----
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
// ---- Content type detection ----
static bool ends_with(const char* str, const char* suffix) {
int sl = slen(str);
int xl = slen(suffix);
if (xl > sl) return false;
for (int i = 0; i < xl; i++) {
char a = str[sl - xl + i];
char b = suffix[i];
// Case-insensitive for file extensions
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return false;
}
return true;
}
static const char* content_type_for(const char* path) {
if (ends_with(path, ".html") || ends_with(path, ".htm"))
return "text/html";
if (ends_with(path, ".txt"))
return "text/plain";
if (ends_with(path, ".css"))
return "text/css";
if (ends_with(path, ".js"))
return "application/javascript";
return "application/octet-stream";
}
// ---- HTTP response helpers ----
// Send a complete HTTP response with headers and body
static void send_response(int clientFd, int statusCode, const char* statusText,
const char* contentType, const char* body, int bodyLen) {
char header[512];
int hlen = snprintf(header, sizeof(header),
"HTTP/1.0 %d %s\r\n"
"Content-Type: %s\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"Server: ZenithOS/1.0\r\n"
"\r\n",
statusCode, statusText, contentType, bodyLen);
zenith::send(clientFd, header, hlen);
if (bodyLen > 0) {
zenith::send(clientFd, body, bodyLen);
}
}
// Send a file from the VFS
static int send_file_response(int clientFd, const char* vfsPath, const char* urlPath) {
int handle = zenith::open(vfsPath);
if (handle < 0) return -1;
uint64_t size = zenith::getsize(handle);
const char* ctype = content_type_for(urlPath);
// Send header
char header[512];
int hlen = snprintf(header, sizeof(header),
"HTTP/1.0 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %u\r\n"
"Connection: close\r\n"
"Server: ZenithOS/1.0\r\n"
"\r\n",
ctype, (unsigned)size);
zenith::send(clientFd, header, hlen);
// Send file body in chunks
uint8_t buf[512];
uint64_t offset = 0;
while (offset < size) {
uint64_t chunk = size - offset;
if (chunk > sizeof(buf)) chunk = sizeof(buf);
int bytesRead = zenith::read(handle, buf, offset, chunk);
if (bytesRead <= 0) break;
zenith::send(clientFd, buf, bytesRead);
offset += bytesRead;
}
zenith::close(handle);
return (int)size;
}
// ---- Request parsing ----
// Extract the request path from "GET /path HTTP/1.x\r\n..."
// Returns length of path written, or -1 on parse error
static int parse_request_path(const char* req, int reqLen, char* pathOut, int pathMax) {
// Find "GET "
if (reqLen < 4) return -1;
if (req[0] != 'G' || req[1] != 'E' || req[2] != 'T' || req[3] != ' ')
return -1;
int i = 4;
int j = 0;
while (i < reqLen && req[i] != ' ' && req[i] != '\r' && req[i] != '\n') {
if (j < pathMax - 1) pathOut[j++] = req[i];
i++;
}
pathOut[j] = '\0';
return j;
}
// ---- Logging ----
static void log_request(const char* method, const char* path, int status, int bodyLen) {
// Get timestamp
Zenith::DateTime dt;
zenith::gettime(&dt);
char msg[256];
snprintf(msg, sizeof(msg), "[%02d:%02d:%02d] %s %s -> %d (%d bytes)\n",
(int)dt.Hour, (int)dt.Minute, (int)dt.Second,
method, path, status, bodyLen);
zenith::print(msg);
}
// ---- Page generators ----
static int generate_index_page(char* buf, int bufSize) {
Zenith::SysInfo info;
zenith::get_info(&info);
uint64_t ms = zenith::get_milliseconds();
uint64_t secs = ms / 1000;
uint64_t mins = secs / 60;
uint64_t hours = mins / 60;
secs %= 60;
mins %= 60;
return snprintf(buf, bufSize,
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>ZenithOS Web Server</title></head>\n"
"<body>\n"
"<h1>ZenithOS Web Server</h1>\n"
"<p>Welcome! This page is being served by <b>httpd</b> running on ZenithOS.</p>\n"
"<h2>System Information</h2>\n"
"<table>\n"
"<tr><td><b>OS:</b></td><td>%s</td></tr>\n"
"<tr><td><b>Version:</b></td><td>%s</td></tr>\n"
"<tr><td><b>Uptime:</b></td><td>%uh %um %us</td></tr>\n"
"</table>\n"
"<h2>Browse Files</h2>\n"
"<p><a href=\"/files/\">Browse VFS files</a></p>\n"
"</body>\n"
"</html>\n",
info.osName, info.osVersion,
(unsigned)hours, (unsigned)mins, (unsigned)secs);
}
static int generate_404_page(char* buf, int bufSize, const char* path) {
return snprintf(buf, bufSize,
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>404 Not Found</title></head>\n"
"<body>\n"
"<h1>404 Not Found</h1>\n"
"<p>The requested path <code>%s</code> was not found on this server.</p>\n"
"<p><a href=\"/\">Back to home</a></p>\n"
"</body>\n"
"</html>\n",
path);
}
static int generate_dir_listing(char* buf, int bufSize, const char* urlPath, const char* vfsDir) {
int pos = 0;
pos += snprintf(buf + pos, bufSize - pos,
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>Index of %s</title></head>\n"
"<body>\n"
"<h1>Index of %s</h1>\n"
"<hr>\n"
"<ul>\n",
urlPath, urlPath);
// Add parent directory link if not at /files/
if (!streq(urlPath, "/files/")) {
pos += snprintf(buf + pos, bufSize - pos,
"<li><a href=\"..\">..</a></li>\n");
}
// List directory entries
const char* entries[64];
int count = zenith::readdir(vfsDir, entries, 64);
// Find the prefix to strip from entry names
// vfsDir is like "0:/" or "0:/subdir"
// entries come back as "subdir/file" for "0:/subdir"
// or "file" for "0:/"
// The prefix in entry names is the part after "0:/"
const char* dirRel = vfsDir + 3; // skip "0:/"
int dirRelLen = slen(dirRel);
for (int i = 0; i < count && pos < bufSize - 128; i++) {
// Extract just the filename portion
const char* name = entries[i];
// If directory is not root, entries have "dir/name" format — strip the dir prefix
if (dirRelLen > 0 && starts_with(name, dirRel)) {
name = name + dirRelLen;
if (*name == '/') name++;
}
if (*name == '\0') continue;
// Build the URL for this entry
pos += snprintf(buf + pos, bufSize - pos,
"<li><a href=\"%s%s\">%s</a></li>\n",
urlPath, name, name);
}
pos += snprintf(buf + pos, bufSize - pos,
"</ul>\n"
"<hr>\n"
"<p><i>ZenithOS httpd</i></p>\n"
"</body>\n"
"</html>\n");
return pos;
}
// ---- Request handler ----
static void handle_client(int clientFd) {
// Read request (HTTP requests are small, 4 KB is plenty)
char reqBuf[4096];
int reqLen = 0;
int idleCount = 0;
// Read until we get the full header (ends with \r\n\r\n)
while (reqLen < (int)sizeof(reqBuf) - 1) {
int r = zenith::recv(clientFd, reqBuf + reqLen, sizeof(reqBuf) - 1 - reqLen);
if (r > 0) {
reqLen += r;
idleCount = 0;
// Check if we have the full header
bool done = false;
for (int i = 0; i + 3 < reqLen; i++) {
if (reqBuf[i] == '\r' && reqBuf[i+1] == '\n' &&
reqBuf[i+2] == '\r' && reqBuf[i+3] == '\n') {
done = true;
break;
}
}
if (done) break;
} else if (r == 0) {
break; // Connection closed
} else {
idleCount++;
if (idleCount > 500) break; // Timeout
zenith::yield();
}
}
reqBuf[reqLen] = '\0';
if (reqLen == 0) {
zenith::closesocket(clientFd);
return;
}
// Parse request path
char path[256];
if (parse_request_path(reqBuf, reqLen, path, sizeof(path)) < 0) {
// Bad request
static char body[] = "<!DOCTYPE html><html><body><h1>400 Bad Request</h1></body></html>";
send_response(clientFd, 400, "Bad Request", "text/html", body, slen(body));
log_request("???", "???", 400, slen(body));
zenith::closesocket(clientFd);
return;
}
// Route the request
static char pageBuf[16384];
if (streq(path, "/")) {
// Try to serve 0:/www/index.html from disk first
int handle = zenith::open("0:/www/index.html");
if (handle >= 0) {
zenith::close(handle);
int bodyLen = send_file_response(clientFd, "0:/www/index.html", "/index.html");
log_request("GET", path, 200, bodyLen);
} else {
// Fall back to built-in index page
int bodyLen = generate_index_page(pageBuf, sizeof(pageBuf));
send_response(clientFd, 200, "OK", "text/html", pageBuf, bodyLen);
log_request("GET", path, 200, bodyLen);
}
} else if (streq(path, "/files") || streq(path, "/files/")) {
// Root directory listing
int bodyLen = generate_dir_listing(pageBuf, sizeof(pageBuf), "/files/", "0:/");
send_response(clientFd, 200, "OK", "text/html", pageBuf, bodyLen);
log_request("GET", path, 200, bodyLen);
} else if (starts_with(path, "/files/")) {
// Serve file or directory from VFS
const char* relPath = path + 7; // skip "/files/"
// Build VFS path: "0:/<relPath>"
char vfsPath[256];
int pi = 0;
vfsPath[pi++] = '0'; vfsPath[pi++] = ':'; vfsPath[pi++] = '/';
int ri = 0;
while (relPath[ri] && pi < (int)sizeof(vfsPath) - 1) {
vfsPath[pi++] = relPath[ri++];
}
// Strip trailing slash for VFS lookup
if (pi > 3 && vfsPath[pi-1] == '/') pi--;
vfsPath[pi] = '\0';
// Try to open as file first
int handle = zenith::open(vfsPath);
if (handle >= 0) {
// It's a file — serve it
zenith::close(handle);
int bodyLen = send_file_response(clientFd, vfsPath, path);
if (bodyLen >= 0) {
log_request("GET", path, 200, bodyLen);
} else {
static char body[] = "<!DOCTYPE html><html><body><h1>500 Internal Server Error</h1></body></html>";
send_response(clientFd, 500, "Internal Server Error", "text/html", body, slen(body));
log_request("GET", path, 500, slen(body));
}
} else {
// Try as directory
const char* entries[64];
int count = zenith::readdir(vfsPath, entries, 64);
if (count >= 0) {
// Make sure urlPath ends with /
char urlPath[256];
int up = 0;
int pp = 0;
while (path[pp] && up < (int)sizeof(urlPath) - 2) urlPath[up++] = path[pp++];
if (up > 0 && urlPath[up-1] != '/') urlPath[up++] = '/';
urlPath[up] = '\0';
int bodyLen = generate_dir_listing(pageBuf, sizeof(pageBuf), urlPath, vfsPath);
send_response(clientFd, 200, "OK", "text/html", pageBuf, bodyLen);
log_request("GET", path, 200, bodyLen);
} else {
// Not found
int bodyLen = generate_404_page(pageBuf, sizeof(pageBuf), path);
send_response(clientFd, 404, "Not Found", "text/html", pageBuf, bodyLen);
log_request("GET", path, 404, bodyLen);
}
}
} else {
// 404 for anything else
int bodyLen = generate_404_page(pageBuf, sizeof(pageBuf), path);
send_response(clientFd, 404, "Not Found", "text/html", pageBuf, bodyLen);
log_request("GET", path, 404, bodyLen);
}
zenith::closesocket(clientFd);
}
// ---- Entry point ----
extern "C" void _start() {
// Parse arguments: [port]
char argbuf[64];
zenith::getargs(argbuf, sizeof(argbuf));
const char* arg = skip_spaces(argbuf);
uint16_t port = 80;
if (*arg) {
if (!parse_uint16(arg, &port)) {
zenith::print("Invalid port: ");
zenith::print(arg);
zenith::putchar('\n');
zenith::exit(1);
}
}
// Create server socket
int listenFd = zenith::socket(Zenith::SOCK_TCP);
if (listenFd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
// Bind
if (zenith::bind(listenFd, port) < 0) {
zenith::print("Error: failed to bind to port ");
char tmp[8];
snprintf(tmp, sizeof(tmp), "%d", (int)port);
zenith::print(tmp);
zenith::putchar('\n');
zenith::closesocket(listenFd);
zenith::exit(1);
}
// Listen
if (zenith::listen(listenFd) < 0) {
zenith::print("Error: failed to listen\n");
zenith::closesocket(listenFd);
zenith::exit(1);
}
char msg[128];
snprintf(msg, sizeof(msg), "ZenithOS httpd listening on port %d\n", (int)port);
zenith::print(msg);
zenith::print("Press Ctrl+Q between requests to stop.\n\n");
bool running = true;
while (running) {
// Check for Ctrl+Q before blocking on accept
while (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
running = false;
break;
}
}
if (!running) break;
// Accept next client (blocks until a connection arrives)
int clientFd = zenith::accept(listenFd);
if (clientFd < 0) {
zenith::print("Warning: accept failed\n");
zenith::yield();
continue;
}
handle_client(clientFd);
// After serving, check for Ctrl+Q
while (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
running = false;
break;
}
}
}
zenith::print("\nShutting down httpd...\n");
zenith::closesocket(listenFd);
zenith::exit(0);
}
+149
View File
@@ -0,0 +1,149 @@
/*
* main.cpp
* ifconfig - Show or set network configuration
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::starts_with;
using zenith::skip_spaces;
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
// Show current network configuration
Zenith::NetCfg cfg;
zenith::get_netcfg(&cfg);
zenith::print(" IP Address: ");
print_ip(cfg.ipAddress);
zenith::putchar('\n');
zenith::print(" Subnet Mask: ");
print_ip(cfg.subnetMask);
zenith::putchar('\n');
zenith::print(" Gateway: ");
print_ip(cfg.gateway);
zenith::putchar('\n');
zenith::exit(0);
}
if (!starts_with(args, "set ")) {
zenith::print("Usage: ifconfig Show network config\n");
zenith::print(" ifconfig set <ip> <mask> <gateway>\n");
zenith::exit(1);
}
// Parse: set <ip> <mask> <gateway>
const char* p = skip_spaces(args + 4);
// Parse IP
char tok[32];
int i = 0;
while (p[i] && p[i] != ' ' && i < 31) { tok[i] = p[i]; i++; }
tok[i] = '\0';
uint32_t ip;
if (!parse_ip(tok, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(tok);
zenith::putchar('\n');
zenith::exit(1);
}
p = skip_spaces(p + i);
// Parse subnet mask
i = 0;
while (p[i] && p[i] != ' ' && i < 31) { tok[i] = p[i]; i++; }
tok[i] = '\0';
uint32_t mask;
if (!parse_ip(tok, &mask)) {
zenith::print("Invalid subnet mask: ");
zenith::print(tok);
zenith::putchar('\n');
zenith::exit(1);
}
p = skip_spaces(p + i);
// Parse gateway
i = 0;
while (p[i] && p[i] != ' ' && i < 31) { tok[i] = p[i]; i++; }
tok[i] = '\0';
uint32_t gw;
if (!parse_ip(tok, &gw)) {
zenith::print("Invalid gateway: ");
zenith::print(tok);
zenith::putchar('\n');
zenith::exit(1);
}
Zenith::NetCfg cfg;
cfg.ipAddress = ip;
cfg.subnetMask = mask;
cfg.gateway = gw;
if (zenith::set_netcfg(&cfg) < 0) {
zenith::print("Error: failed to set network config\n");
zenith::exit(1);
}
zenith::print("Network config updated:\n");
zenith::print(" IP Address: "); print_ip(ip); zenith::putchar('\n');
zenith::print(" Subnet Mask: "); print_ip(mask); zenith::putchar('\n');
zenith::print(" Gateway: "); print_ip(gw); zenith::putchar('\n');
zenith::exit(0);
}
+36
View File
@@ -0,0 +1,36 @@
/*
* main.cpp
* info - Show system information
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
extern "C" void _start() {
Zenith::SysInfo info;
zenith::get_info(&info);
zenith::print(info.osName);
zenith::print(" v");
zenith::print(info.osVersion);
zenith::putchar('\n');
zenith::print("Syscall API version: ");
print_int(info.apiVersion);
zenith::putchar('\n');
zenith::exit(0);
}
+164
View File
@@ -0,0 +1,164 @@
/*
* main.cpp
* Init system for ZenithOS (PID 0)
* Chains system services then launches the shell.
* Copyright (c) 2026 Daniel Hammer
*/
#include <zenith/syscall.h>
// ---- Minimal snprintf ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState { char* buf; int pos; int max; };
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24]; int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState 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 = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long 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;
pf_putnum(&st, uval, 10, width, pad, neg); break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*); if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
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 st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap; va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap); return ret;
}
// ---- ANSI color codes ----
#define C_RESET "\033[0m"
#define C_BOLD "\033[1m"
#define C_DIM "\033[2m"
#define C_RED "\033[31m"
#define C_GREEN "\033[32m"
#define C_YELLOW "\033[33m"
#define C_BLUE "\033[34m"
#define C_CYAN "\033[36m"
#define C_WHITE "\033[37m"
// ---- Logging ----
static void log_timestamp(char* buf, int size) {
Zenith::DateTime dt;
zenith::gettime(&dt);
snprintf(buf, size, "%02d:%02d:%02d", dt.Hour, dt.Minute, dt.Second);
}
enum LogLevel { LOG_OK, LOG_INFO, LOG_WARN, LOG_ERR };
static void log(LogLevel level, const char* msg) {
char line[512];
char ts[16];
log_timestamp(ts, sizeof(ts));
const char* tag;
const char* color;
switch (level) {
case LOG_OK: tag = " OK "; color = C_GREEN; break;
case LOG_INFO: tag = " INFO "; color = C_CYAN; break;
case LOG_WARN: tag = " WARN "; color = C_YELLOW; break;
case LOG_ERR: tag = " FAIL "; color = C_RED; break;
}
snprintf(line, sizeof(line),
C_DIM "%s" C_RESET " %s%s" C_RESET " " C_BOLD "init" C_RESET " %s\n",
ts, color, tag, msg);
zenith::print(line);
}
static void log_ok(const char* msg) { log(LOG_OK, msg); }
static void log_info(const char* msg) { log(LOG_INFO, msg); }
static void log_warn(const char* msg) { log(LOG_WARN, msg); }
static void log_err(const char* msg) { log(LOG_ERR, msg); }
// ---- Service runner ----
static bool run_service(const char* path, const char* name) {
char msg[128];
snprintf(msg, sizeof(msg), "Starting %s", name);
log_info(msg);
int pid = zenith::spawn(path);
if (pid < 0) {
snprintf(msg, sizeof(msg), "Failed to start %s", name);
log_err(msg);
return false;
}
zenith::waitpid(pid);
snprintf(msg, sizeof(msg), "%s finished (pid %d)", name, pid);
log_ok(msg);
return true;
}
// ---- Main ----
extern "C" void _start() {
log_info("Init system starting (PID 0)");
// ---- Stage 1: Network configuration ----
run_service("0:/os/dhcp.elf", "dhcp");
// ---- Stage 2: Interactive shell ----
run_service("0:/os/shell.elf", "shell");
log_warn("All services exited");
for (;;) {
zenith::yield();
}
}
+10 -45
View File
@@ -6,6 +6,16 @@
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::slen;
using zenith::streq;
using zenith::starts_with;
using zenith::skip_spaces;
using zenith::strcpy;
using zenith::strncpy;
using zenith::memcpy;
using zenith::memmove;
// ---- Minimal snprintf (no libc available) ----
@@ -91,51 +101,6 @@ static int snprintf(char* buf, int size, const char* fmt, ...) {
return ret;
}
// ---- String utilities ----
static int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
}
static bool streq(const char* a, const char* b) {
while (*a && *b) { if (*a != *b) return false; a++; b++; }
return *a == *b;
}
static bool starts_with(const char* str, const char* prefix) {
while (*prefix) { if (*str != *prefix) return false; str++; prefix++; }
return true;
}
static const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
static void strcpy(char* dst, const char* src) {
while (*src) *dst++ = *src++;
*dst = '\0';
}
static void strncpy(char* dst, const char* src, int max) {
int i = 0;
while (src[i] && i < max - 1) { dst[i] = src[i]; i++; }
dst[i] = '\0';
}
static void memcpy(void* dst, const void* src, int n) {
auto d = (char*)dst; auto s = (const char*)src;
for (int i = 0; i < n; i++) d[i] = s[i];
}
static void memmove(void* dst, const void* src, int n) {
auto d = (char*)dst; auto s = (const char*)src;
if (d < s) { for (int i = 0; i < n; i++) d[i] = s[i]; }
else { for (int i = n - 1; i >= 0; i--) d[i] = s[i]; }
}
// Case-insensitive comparison for IRC commands
static bool streqi(const char* a, const char* b) {
while (*a && *b) {
+4 -20
View File
@@ -7,27 +7,11 @@
#include <zenith/syscall.h>
#include <zenith/heap.h>
#include <zenith/string.h>
// ---- Utility functions ----
static bool starts_with(const char* str, const char* prefix) {
while (*prefix) {
if (*str != *prefix) return false;
str++; prefix++;
}
return true;
}
static const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
static int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
}
using zenith::slen;
using zenith::starts_with;
using zenith::skip_spaces;
static void print_int(uint64_t n) {
if (n == 0) {
+101
View File
@@ -0,0 +1,101 @@
/*
* main.cpp
* ping - Send ICMP echo requests
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: ping <ip address>\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);
}
zenith::print("PING ");
print_ip(ip);
zenith::putchar('\n');
for (int i = 0; i < 4; i++) {
int32_t rtt = zenith::ping(ip, 3000);
if (rtt < 0) {
zenith::print(" Request timed out\n");
} else {
zenith::print(" Reply from ");
print_ip(ip);
zenith::print(": time=");
print_int((uint64_t)rtt);
zenith::print("ms\n");
}
if (i < 3) {
zenith::sleep_ms(1000);
}
}
zenith::exit(0);
}
+12
View File
@@ -0,0 +1,12 @@
/*
* main.cpp
* reset - Reboot the system
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
zenith::print("Rebooting...\n");
zenith::reset();
}
+285 -450
View File
@@ -5,52 +5,33 @@
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
static bool streq(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
a++; b++;
using zenith::slen;
using zenith::streq;
using zenith::starts_with;
using zenith::skip_spaces;
static void scopy(char* dst, const char* src, int maxLen) {
int i = 0;
while (src[i] && i < maxLen - 1) { dst[i] = src[i]; i++; }
dst[i] = '\0';
}
static void scat(char* dst, const char* src, int maxLen) {
int dLen = slen(dst);
int i = 0;
while (src[i] && dLen + i < maxLen - 1) {
dst[dLen + i] = src[i];
i++;
}
return *a == *b;
}
static bool starts_with(const char* str, const char* prefix) {
while (*prefix) {
if (*str != *prefix) return false;
str++; prefix++;
}
return true;
}
static const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
static int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
dst[dLen + i] = '\0';
}
// Current working directory (relative to 0:/).
// "" = root, "man" = 0:/man, "man/sub" = 0:/man/sub
static char cwd[128] = "";
// Build full VFS path: "0:/" + cwd + "/" + name
static void resolve_path(const char* name, char* out, int outMax) {
int i = 0;
out[i++] = '0'; out[i++] = ':'; out[i++] = '/';
if (cwd[0]) {
int j = 0;
while (cwd[j] && i < outMax - 2) out[i++] = cwd[j++];
out[i++] = '/';
}
int j = 0;
while (name[j] && i < outMax - 1) out[i++] = name[j++];
out[i] = '\0';
}
// Build VFS directory path: "0:/" or "0:/<dir>"
static void build_dir_path(const char* dir, char* out, int outMax) {
int i = 0;
@@ -62,58 +43,100 @@ static void build_dir_path(const char* dir, char* out, int outMax) {
out[i] = '\0';
}
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
// ---- Command history ----
static constexpr int HISTORY_MAX = 32;
static char history[HISTORY_MAX][256];
static int history_count = 0;
static int history_next = 0; // ring-buffer write index
static void history_add(const char* line) {
if (line[0] == '\0') return;
// Don't add duplicate of last entry
if (history_count > 0) {
int prev = (history_next + HISTORY_MAX - 1) % HISTORY_MAX;
if (streq(history[prev], line)) return;
}
scopy(history[history_next], line, 256);
history_next = (history_next + 1) % HISTORY_MAX;
if (history_count < HISTORY_MAX) history_count++;
}
// Get history entry by index (0 = most recent, 1 = one before that, ...)
static const char* history_get(int idx) {
if (idx < 0 || idx >= history_count) return nullptr;
int pos = (history_next + HISTORY_MAX - 1 - idx) % HISTORY_MAX;
return history[pos];
}
// ---- Prompt ----
static void prompt() {
zenith::print("0:/");
if (cwd[0]) zenith::print(cwd);
zenith::print("> ");
}
// ---- Erase current input line on screen ----
static void erase_input(int len) {
// Move cursor to start of input and overwrite with spaces
for (int i = 0; i < len; i++) zenith::putchar('\b');
for (int i = 0; i < len; i++) zenith::putchar(' ');
for (int i = 0; i < len; i++) zenith::putchar('\b');
}
// ---- Replace visible line with new content ----
static void replace_line(char* line, int* pos, const char* newContent) {
int oldLen = *pos;
erase_input(oldLen);
int newLen = slen(newContent);
if (newLen > 255) newLen = 255;
for (int i = 0; i < newLen; i++) {
line[i] = newContent[i];
zenith::putchar(newContent[i]);
}
line[newLen] = '\0';
*pos = newLen;
}
// ---- Builtin: help ----
static void cmd_help() {
zenith::print("Available commands:\n");
zenith::print("Shell builtins:\n");
zenith::print(" help Show this help message\n");
zenith::print(" info Show system information\n");
zenith::print(" man <topic> View manual pages\n");
zenith::print(" ls [dir] List files in directory\n");
zenith::print(" cd [dir] Change working directory\n");
zenith::print(" exit Exit the shell\n");
zenith::print("\n");
zenith::print("System commands:\n");
zenith::print(" man <topic> View manual pages\n");
zenith::print(" cat <file> Display file contents\n");
zenith::print(" run <file> Spawn a new process from an ELF file\n");
zenith::print(" ping <ip> Send ICMP echo requests\n");
zenith::print(" tcpconnect <ip> <port> Connect to a TCP server\n");
zenith::print(" edit [file] Text editor\n");
zenith::print(" info Show system information\n");
zenith::print(" date Show current date and time\n");
zenith::print(" uptime Show uptime in milliseconds\n");
zenith::print(" uptime Show uptime\n");
zenith::print(" clear Clear the screen\n");
zenith::print(" reset Reboot the system\n");
zenith::print(" shutdown Shut down the system\n");
zenith::print(" exit Exit the shell\n");
zenith::print("\n");
zenith::print("Network commands:\n");
zenith::print(" ping <ip> Send ICMP echo requests\n");
zenith::print(" ifconfig Show/set network configuration\n");
zenith::print(" tcpconnect Connect to a TCP server\n");
zenith::print(" irc IRC client\n");
zenith::print(" dhcp DHCP client\n");
zenith::print(" fetch <url> HTTP client\n");
zenith::print(" httpd HTTP server\n");
zenith::print("\n");
zenith::print("Games:\n");
zenith::print(" doom DOOM\n");
zenith::print("\n");
zenith::print("Any .elf on the ramdisk is executable.\n");
}
static void cmd_info() {
Zenith::SysInfo info;
zenith::get_info(&info);
zenith::print(info.osName);
zenith::print(" v");
zenith::print(info.osVersion);
zenith::print("\n");
zenith::print("Syscall API version: ");
print_int(info.apiVersion);
zenith::putchar('\n');
}
// ---- Builtin: ls ----
static void cmd_ls(const char* arg) {
arg = skip_spaces(arg);
@@ -121,7 +144,7 @@ static void cmd_ls(const char* arg) {
// Build the target directory (relative path from root)
char dir[128];
if (*arg) {
// ls <dir> combine cwd and arg
// ls <dir> -- combine cwd and arg
if (cwd[0]) {
int i = 0, j = 0;
while (cwd[j] && i < 126) dir[i++] = cwd[j++];
@@ -135,7 +158,7 @@ static void cmd_ls(const char* arg) {
dir[i] = '\0';
}
} else {
// ls with no arg use cwd
// ls with no arg -- use cwd
int i = 0;
while (cwd[i] && i < 126) { dir[i] = cwd[i]; i++; }
dir[i] = '\0';
@@ -166,310 +189,26 @@ static void cmd_ls(const char* arg) {
}
}
static void cmd_cat(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: cat <filename>\n");
return;
}
char path[128];
resolve_path(arg, path, sizeof(path));
int handle = zenith::open(path);
if (handle < 0) {
zenith::print("Error: cannot open '");
zenith::print(arg);
zenith::print("'\n");
return;
}
uint64_t size = zenith::getsize(handle);
if (size == 0) {
zenith::close(handle);
return;
}
// Read in chunks
uint8_t buf[512];
uint64_t offset = 0;
while (offset < size) {
uint64_t chunk = size - offset;
if (chunk > sizeof(buf) - 1) chunk = sizeof(buf) - 1;
int bytesRead = zenith::read(handle, buf, offset, chunk);
if (bytesRead <= 0) break;
buf[bytesRead] = '\0';
zenith::print((const char*)buf);
offset += bytesRead;
}
zenith::close(handle);
zenith::putchar('\n');
}
static void cmd_uptime() {
uint64_t ms = zenith::get_milliseconds();
uint64_t secs = ms / 1000;
uint64_t mins = secs / 60;
secs %= 60;
ms %= 1000;
zenith::print("Uptime: ");
print_int(mins);
zenith::print("m ");
print_int(secs);
zenith::print("s ");
print_int(ms);
zenith::print("ms\n");
}
static bool parse_ip(const char* s, uint32_t* out) {
// Parse "a.b.c.d" into a uint32_t in network byte order (little-endian stored)
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
static void cmd_ping(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: ping <ip address>\n");
return;
}
uint32_t ip;
if (!parse_ip(arg, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(arg);
zenith::putchar('\n');
return;
}
zenith::print("PING ");
print_ip(ip);
zenith::putchar('\n');
for (int i = 0; i < 4; i++) {
int32_t rtt = zenith::ping(ip, 3000);
if (rtt < 0) {
zenith::print(" Request timed out\n");
} else {
zenith::print(" Reply from ");
print_ip(ip);
zenith::print(": time=");
print_int((uint64_t)rtt);
zenith::print("ms\n");
}
if (i < 3) {
zenith::sleep_ms(1000);
}
}
}
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
static void cmd_tcpconnect(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
return;
}
// Parse IP address (up to first space)
char ipStr[32];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 31) {
ipStr[i] = arg[i];
i++;
}
ipStr[i] = '\0';
uint32_t ip;
if (!parse_ip(ipStr, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
return;
}
// Parse port
const char* portStr = skip_spaces(arg + i);
if (*portStr == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
return;
}
uint16_t port;
if (!parse_uint16(portStr, &port)) {
zenith::print("Invalid port: ");
zenith::print(portStr);
zenith::putchar('\n');
return;
}
// Create socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
return;
}
zenith::print("Connecting to ");
print_ip(ip);
zenith::putchar(':');
print_int(port);
zenith::print("...\n");
if (zenith::connect(fd, ip, port) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
return;
}
zenith::print("Connected! Type to send, Ctrl+Q to disconnect.\n");
// Interactive send/receive loop
char sendBuf[256];
int sendPos = 0;
uint8_t recvBuf[512];
while (true) {
// Poll for received data (non-blocking)
int r = zenith::recv(fd, recvBuf, sizeof(recvBuf) - 1);
if (r < 0) {
zenith::print("\nConnection closed by remote.\n");
break;
}
if (r > 0) {
recvBuf[r] = '\0';
zenith::print((const char*)recvBuf);
}
// Poll keyboard
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
// Ctrl+Q to quit
if (ev.ctrl && (ev.ascii == 'q' || ev.ascii == 'Q')) {
zenith::print("\nDisconnecting...\n");
break;
}
if (ev.ascii == '\n') {
sendBuf[sendPos++] = '\n';
zenith::putchar('\n');
zenith::send(fd, sendBuf, sendPos);
sendPos = 0;
} else if (ev.ascii == '\b') {
if (sendPos > 0) {
sendPos--;
zenith::putchar('\b');
zenith::putchar(' ');
zenith::putchar('\b');
}
} else if (ev.ascii >= ' ' && sendPos < 254) {
sendBuf[sendPos++] = ev.ascii;
zenith::putchar(ev.ascii);
}
} else {
// No key and no data — yield to avoid busy-spinning
zenith::yield();
}
}
zenith::closesocket(fd);
}
static void cmd_run(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: run <filename> [args...]\n");
return;
}
// Split filename from arguments at first space
char filename[128];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 127) {
filename[i] = arg[i];
i++;
}
filename[i] = '\0';
const char* args = nullptr;
if (arg[i] == ' ') {
args = skip_spaces(arg + i);
if (*args == '\0') args = nullptr;
}
char path[128];
resolve_path(filename, path, sizeof(path));
int pid = zenith::spawn(path, args);
if (pid < 0) {
zenith::print("Error: failed to spawn '");
zenith::print(filename);
zenith::print("'\n");
} else {
zenith::waitpid(pid);
}
}
// ---- Builtin: cd ----
static void cmd_cd(const char* arg) {
arg = skip_spaces(arg);
// cd or cd / → go to root
// Strip trailing slashes from argument (ls shows dirs as "www/", user may type that)
static char argBuf[128];
int aLen = 0;
while (arg[aLen] && aLen < 127) { argBuf[aLen] = arg[aLen]; aLen++; }
argBuf[aLen] = '\0';
while (aLen > 0 && argBuf[aLen - 1] == '/') argBuf[--aLen] = '\0';
arg = argBuf;
// cd or cd / -> go to root
if (*arg == '\0' || streq(arg, "/")) {
cwd[0] = '\0';
return;
}
// cd .. go up one level
// cd .. -> go up one level
if (streq(arg, "..")) {
int len = slen(cwd);
int last = -1;
@@ -517,6 +256,8 @@ static void cmd_cd(const char* arg) {
cwd[i] = '\0';
}
// ---- Builtin: man ----
static void cmd_man(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
@@ -526,7 +267,7 @@ static void cmd_man(const char* arg) {
return;
}
int pid = zenith::spawn("0:/man.elf", arg);
int pid = zenith::spawn("0:/os/man.elf", arg);
if (pid < 0) {
zenith::print("Error: failed to start man viewer\n");
} else {
@@ -534,102 +275,155 @@ static void cmd_man(const char* arg) {
}
}
static void print_int_padded(uint64_t n) {
if (n < 10) zenith::putchar('0');
print_int(n);
// ---- External command execution ----
// Try to spawn an ELF at the given path. Returns true on success.
static bool try_exec(const char* path, const char* args) {
// Check if the file exists before asking the kernel to load it
int h = zenith::open(path);
if (h < 0) return false;
zenith::close(h);
int pid = zenith::spawn(path, args);
if (pid < 0) return false;
zenith::waitpid(pid);
return true;
}
static const char* month_name(int m) {
static const char* months[] = {
"", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
if (m >= 1 && m <= 12) return months[m];
return "?";
// Check if a string already has a VFS drive prefix (e.g. "0:/")
static bool has_drive_prefix(const char* s) {
return s[0] >= '0' && s[0] <= '9' && s[1] == ':';
}
static void cmd_date() {
Zenith::DateTime dt;
zenith::gettime(&dt);
// Resolve arguments: expand relative file paths against CWD.
// Tokens that already have a drive prefix (e.g. "0:/foo") are left as-is.
// Everything before the first space-delimited token that looks like a path
// option (starts with '-') is also left as-is.
static void resolve_args(const char* args, char* out, int outMax) {
if (!args || !args[0]) { out[0] = '\0'; return; }
print_int(dt.Day);
zenith::putchar(' ');
zenith::print(month_name(dt.Month));
zenith::putchar(' ');
print_int(dt.Year);
zenith::print(", ");
print_int(dt.Hour);
zenith::putchar(':');
print_int_padded(dt.Minute);
zenith::putchar(':');
print_int_padded(dt.Second);
zenith::print(" UTC\n");
int o = 0;
const char* p = args;
while (*p && o < outMax - 1) {
// Skip spaces, copy them through
while (*p == ' ' && o < outMax - 1) { out[o++] = *p++; }
if (!*p) break;
// Extract the token
const char* tokStart = p;
int tokLen = 0;
while (p[tokLen] && p[tokLen] != ' ') tokLen++;
// Decide whether to resolve this token as a path.
// Don't resolve if it already has a drive prefix, or starts with '-'
bool resolve = cwd[0] && !has_drive_prefix(tokStart) && tokStart[0] != '-';
if (resolve) {
// Write "0:/<cwd>/<token>"
if (o + 3 < outMax) { out[o++] = '0'; out[o++] = ':'; out[o++] = '/'; }
int j = 0;
while (cwd[j] && o < outMax - 1) out[o++] = cwd[j++];
if (o < outMax - 1) out[o++] = '/';
for (int k = 0; k < tokLen && o < outMax - 1; k++) out[o++] = tokStart[k];
} else {
for (int k = 0; k < tokLen && o < outMax - 1; k++) out[o++] = tokStart[k];
}
p = tokStart + tokLen;
}
out[o] = '\0';
}
static void cmd_clear() {
zenith::print("\033[2J"); // Clear entire screen
zenith::print("\033[H"); // Move cursor to top-left
static void exec_external(const char* cmd, const char* args) {
char path[256];
// Resolve arguments against CWD so external programs get full VFS paths
char resolvedArgs[512];
resolve_args(args, resolvedArgs, sizeof(resolvedArgs));
const char* finalArgs = resolvedArgs[0] ? resolvedArgs : nullptr;
// 1. Try 0:/os/<cmd>.elf
scopy(path, "0:/os/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
// 2. Try 0:/games/<cmd>.elf
scopy(path, "0:/games/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
// 3. Try 0:/<cwd>/<cmd>.elf (if cwd is set)
if (cwd[0]) {
scopy(path, "0:/", sizeof(path));
scat(path, cwd, sizeof(path));
scat(path, "/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
}
// 4. Try 0:/<cmd>.elf
scopy(path, "0:/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
// Not found
zenith::print(cmd);
zenith::print(": command not found\n");
}
// ---- Command dispatch ----
static void process_command(const char* line) {
// Skip leading spaces
line = skip_spaces(line);
if (*line == '\0') return;
if (streq(line, "help")) {
// Parse command name and arguments
char cmd[128];
int i = 0;
while (line[i] && line[i] != ' ' && i < 127) {
cmd[i] = line[i];
i++;
}
cmd[i] = '\0';
const char* args = nullptr;
if (line[i] == ' ') {
args = skip_spaces(line + i);
if (*args == '\0') args = nullptr;
}
// Builtins
if (streq(cmd, "help")) {
cmd_help();
} else if (streq(line, "info")) {
cmd_info();
} else if (starts_with(line, "ls ")) {
cmd_ls(line + 3);
} else if (streq(line, "ls")) {
cmd_ls("");
} else if (starts_with(line, "cd ")) {
cmd_cd(line + 3);
} else if (streq(line, "cd")) {
cmd_cd("");
} else if (starts_with(line, "man ")) {
cmd_man(line + 4);
} else if (streq(line, "man")) {
cmd_man("");
} else if (starts_with(line, "cat ")) {
cmd_cat(line + 4);
} else if (streq(line, "cat")) {
cmd_cat("");
} else if (starts_with(line, "run ")) {
cmd_run(line + 4);
} else if (streq(line, "run")) {
cmd_run("");
} else if (starts_with(line, "ping ")) {
cmd_ping(line + 5);
} else if (streq(line, "ping")) {
cmd_ping("");
} else if (starts_with(line, "tcpconnect ")) {
cmd_tcpconnect(line + 11);
} else if (streq(line, "tcpconnect")) {
cmd_tcpconnect("");
} else if (streq(line, "date")) {
cmd_date();
} else if (streq(line, "uptime")) {
cmd_uptime();
} else if (streq(line, "clear")) {
cmd_clear();
} else if (streq(line, "reset")) {
zenith::print("Rebooting...\n");
zenith::reset();
} else if (streq(line, "shutdown")) {
zenith::print("Shutting down...\n");
zenith::shutdown();
} else if (streq(line, "exit")) {
} else if (streq(cmd, "ls")) {
cmd_ls(args ? args : "");
} else if (streq(cmd, "cd")) {
cmd_cd(args ? args : "");
} else if (streq(cmd, "man")) {
cmd_man(args ? args : "");
} else if (streq(cmd, "exit")) {
zenith::print("Goodbye.\n");
zenith::exit(0);
} else {
zenith::print("Unknown command: ");
zenith::print(line);
zenith::print("\nType 'help' for available commands.\n");
// External command -- pass full argument string
exec_external(cmd, args);
}
}
// ---- Arrow key scancodes ----
static constexpr uint8_t SC_UP = 0x48;
static constexpr uint8_t SC_DOWN = 0x50;
static constexpr uint8_t SC_LEFT = 0x4B;
static constexpr uint8_t SC_RIGHT = 0x4D;
// ---- Entry point ----
extern "C" void _start() {
zenith::print("\n");
zenith::print(" ZenithOS\n");
@@ -641,28 +435,69 @@ extern "C" void _start() {
char line[256];
int pos = 0;
int hist_nav = -1; // -1 = not navigating history
prompt();
while (true) {
char c = zenith::getchar();
if (!zenith::is_key_available()) {
zenith::yield();
continue;
}
if (c == '\n') {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
// Arrow keys: ascii == 0, check scancode
if (ev.ascii == 0) {
if (ev.scancode == SC_UP) {
// Navigate to older history entry
int next = hist_nav + 1;
const char* entry = history_get(next);
if (entry) {
hist_nav = next;
replace_line(line, &pos, entry);
}
} else if (ev.scancode == SC_DOWN) {
// Navigate to newer history entry
if (hist_nav > 0) {
hist_nav--;
const char* entry = history_get(hist_nav);
if (entry) {
replace_line(line, &pos, entry);
}
} else if (hist_nav == 0) {
// Back to empty line
hist_nav = -1;
erase_input(pos);
pos = 0;
line[0] = '\0';
}
}
// Left/Right arrows: ignore for now (no cursor movement within line)
continue;
}
if (ev.ascii == '\n') {
zenith::putchar('\n');
line[pos] = '\0';
history_add(line);
process_command(line);
pos = 0;
hist_nav = -1;
prompt();
} else if (c == '\b') {
} else if (ev.ascii == '\b') {
if (pos > 0) {
pos--;
zenith::putchar('\b');
zenith::putchar(' ');
zenith::putchar('\b');
}
} else if (c >= ' ' && pos < 255) {
line[pos++] = c;
zenith::putchar(c);
} else if (ev.ascii >= ' ' && pos < 255) {
line[pos++] = ev.ascii;
zenith::putchar(ev.ascii);
}
}
}
+12
View File
@@ -0,0 +1,12 @@
/*
* main.cpp
* shutdown - Shut down the system
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
zenith::print("Shutting down...\n");
zenith::shutdown();
}
+194
View File
@@ -0,0 +1,194 @@
/*
* main.cpp
* tcpconnect - Interactive TCP client
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::skip_spaces;
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
zenith::exit(1);
}
// Parse IP address (up to first space)
char ipStr[32];
int i = 0;
while (args[i] && args[i] != ' ' && i < 31) {
ipStr[i] = args[i];
i++;
}
ipStr[i] = '\0';
uint32_t ip;
if (!parse_ip(ipStr, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Parse port
const char* portStr = skip_spaces(args + i);
if (*portStr == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
zenith::exit(1);
}
uint16_t port;
if (!parse_uint16(portStr, &port)) {
zenith::print("Invalid port: ");
zenith::print(portStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Create socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
zenith::print("Connecting to ");
print_ip(ip);
zenith::putchar(':');
print_int(port);
zenith::print("...\n");
if (zenith::connect(fd, ip, port) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
zenith::exit(1);
}
zenith::print("Connected! Type to send, Ctrl+Q to disconnect.\n");
// Interactive send/receive loop
char sendBuf[256];
int sendPos = 0;
uint8_t recvBuf[512];
while (true) {
// Poll for received data (non-blocking)
int r = zenith::recv(fd, recvBuf, sizeof(recvBuf) - 1);
if (r < 0) {
zenith::print("\nConnection closed by remote.\n");
break;
}
if (r > 0) {
recvBuf[r] = '\0';
zenith::print((const char*)recvBuf);
}
// Poll keyboard
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
// Ctrl+Q to quit
if (ev.ctrl && (ev.ascii == 'q' || ev.ascii == 'Q')) {
zenith::print("\nDisconnecting...\n");
break;
}
if (ev.ascii == '\n') {
sendBuf[sendPos++] = '\n';
zenith::putchar('\n');
zenith::send(fd, sendBuf, sendPos);
sendPos = 0;
} else if (ev.ascii == '\b') {
if (sendPos > 0) {
sendPos--;
zenith::putchar('\b');
zenith::putchar(' ');
zenith::putchar('\b');
}
} else if (ev.ascii >= ' ' && sendPos < 254) {
sendBuf[sendPos++] = ev.ascii;
zenith::putchar(ev.ascii);
}
} else {
// No key and no data -- yield to avoid busy-spinning
zenith::yield();
}
}
zenith::closesocket(fd);
zenith::exit(0);
}
+40
View File
@@ -0,0 +1,40 @@
/*
* main.cpp
* uptime - Show system uptime
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
extern "C" void _start() {
uint64_t ms = zenith::get_milliseconds();
uint64_t secs = ms / 1000;
uint64_t mins = secs / 60;
secs %= 60;
ms %= 1000;
zenith::print("Uptime: ");
print_int(mins);
zenith::print("m ");
print_int(secs);
zenith::print("s ");
print_int(ms);
zenith::print("ms\n");
zenith::exit(0);
}
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ZenithOS</title>
</head>
<body style="margin:0;padding:0;font-family:Georgia,'Times New Roman',serif;color:#222;background:#fff">
<div style="max-width:38em;margin:4em auto;padding:0 1.5em">
<h1 style="font-size:2.4em;font-weight:normal;letter-spacing:-0.02em;margin:0 0 0.15em">
ZenithOS
</h1>
<hr style="border:none;border-top:1px solid #ccc;margin:0 0 2em">
<h2 style="font-size:1.2em;font-weight:normal;margin:0 0 0.8em">Browse</h2>
<ul style="line-height:1.8;padding-left:1.2em;margin:0 0 2.5em">
<li><a href="/files/" style="color:#1a5c9e">Filesystem</a> &mdash; browse the VFS</li>
</ul>
<hr style="border:none;border-top:1px solid #ccc;margin:0 0 1.5em">
<p style="font-size:0.85em;color:#999;margin:0">
ZenithOS &middot; httpd/1.0
</p>
</div>
</body>
</html>
BIN
View File
Binary file not shown.