feat: e100e Ethernet driver, ramdisk reorganization, shell rewrite, web server/client, and more
This commit is contained in:
@@ -20,6 +20,9 @@
|
|||||||
#include <Net/Icmp.hpp>
|
#include <Net/Icmp.hpp>
|
||||||
#include <Net/Socket.hpp>
|
#include <Net/Socket.hpp>
|
||||||
#include <Net/ByteOrder.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/MSR.hpp>
|
||||||
#include <Hal/GDT.hpp>
|
#include <Hal/GDT.hpp>
|
||||||
#include <Graphics/Cursor.hpp>
|
#include <Graphics/Cursor.hpp>
|
||||||
@@ -303,6 +306,45 @@ namespace Zenith {
|
|||||||
Net::Socket::Close(fd, Sched::GetCurrentPid());
|
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() {
|
static void Sys_Reset() {
|
||||||
/*
|
/*
|
||||||
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
|
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
|
||||||
@@ -320,6 +362,16 @@ namespace Zenith {
|
|||||||
__builtin_unreachable();
|
__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 ----
|
// ---- Dispatch ----
|
||||||
|
|
||||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||||
@@ -416,6 +468,24 @@ namespace Zenith {
|
|||||||
case SYS_CLOSESOCK:
|
case SYS_CLOSESOCK:
|
||||||
Sys_CloseSock((int)frame->arg1);
|
Sys_CloseSock((int)frame->arg1);
|
||||||
return 0;
|
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:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -442,7 +512,7 @@ namespace Zenith {
|
|||||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 37 syscalls)";
|
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 43 syscalls)";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,15 @@ namespace Zenith {
|
|||||||
static constexpr uint64_t SYS_SEND = 34;
|
static constexpr uint64_t SYS_SEND = 34;
|
||||||
static constexpr uint64_t SYS_RECV = 35;
|
static constexpr uint64_t SYS_RECV = 35;
|
||||||
static constexpr uint64_t SYS_CLOSESOCK = 36;
|
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_TCP = 1;
|
||||||
|
static constexpr int SOCK_UDP = 2;
|
||||||
|
|
||||||
struct DateTime {
|
struct DateTime {
|
||||||
uint16_t Year;
|
uint16_t Year;
|
||||||
@@ -75,6 +82,14 @@ namespace Zenith {
|
|||||||
uint32_t maxProcesses;
|
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 {
|
struct KeyEvent {
|
||||||
uint8_t scancode;
|
uint8_t scancode;
|
||||||
char ascii;
|
char ascii;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <Libraries/String.hpp>
|
#include <Libraries/String.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
|
#include <Memory/Heap.hpp>
|
||||||
|
|
||||||
namespace Fs::Ramdisk {
|
namespace Fs::Ramdisk {
|
||||||
|
|
||||||
@@ -105,6 +106,8 @@ namespace Fs::Ramdisk {
|
|||||||
|
|
||||||
entry.isDirectory = (typeFlag == '5');
|
entry.isDirectory = (typeFlag == '5');
|
||||||
entry.size = size;
|
entry.size = size;
|
||||||
|
entry.capacity = size;
|
||||||
|
entry.heapAllocated = false;
|
||||||
|
|
||||||
// Data starts at next 512-byte block
|
// Data starts at next 512-byte block
|
||||||
entry.data = ptr + 512;
|
entry.data = ptr + 512;
|
||||||
@@ -235,6 +238,110 @@ namespace Fs::Ramdisk {
|
|||||||
return count;
|
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() {
|
int GetFileCount() {
|
||||||
return fileCount;
|
return fileCount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,17 @@ namespace Fs::Ramdisk {
|
|||||||
char name[MaxNameLen];
|
char name[MaxNameLen];
|
||||||
uint8_t* data;
|
uint8_t* data;
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
|
uint64_t capacity;
|
||||||
bool isDirectory;
|
bool isDirectory;
|
||||||
|
bool heapAllocated;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Initialize(void* moduleData, uint64_t moduleSize);
|
void Initialize(void* moduleData, uint64_t moduleSize);
|
||||||
|
|
||||||
int Open(const char* path);
|
int Open(const char* path);
|
||||||
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
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);
|
uint64_t GetSize(int handle);
|
||||||
void Close(int handle);
|
void Close(int handle);
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,46 @@ namespace Fs::Vfs {
|
|||||||
entry.inUse = false;
|
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 VfsReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||||
int drive;
|
int drive;
|
||||||
const char* localPath;
|
const char* localPath;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ namespace Fs::Vfs {
|
|||||||
uint64_t (*GetSize)(int handle);
|
uint64_t (*GetSize)(int handle);
|
||||||
void (*Close)(int handle);
|
void (*Close)(int handle);
|
||||||
int (*ReadDir)(const char* path, const char** outNames, int maxEntries);
|
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();
|
void Initialize();
|
||||||
@@ -26,6 +28,8 @@ namespace Fs::Vfs {
|
|||||||
|
|
||||||
int VfsOpen(const char* path);
|
int VfsOpen(const char* path);
|
||||||
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
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);
|
uint64_t VfsGetSize(int handle);
|
||||||
void VfsClose(int handle);
|
void VfsClose(int handle);
|
||||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
|
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Hal {
|
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);
|
using IrqHandler = void(*)(uint8_t irq);
|
||||||
|
|
||||||
// Number of IRQ lines supported (IOAPIC inputs)
|
// Number of IRQ slots supported (0-23: legacy ISA via IOAPIC, 24-47: MSI)
|
||||||
constexpr int IRQ_COUNT = 24;
|
constexpr int IRQ_COUNT = 48;
|
||||||
|
|
||||||
// IRQ vector base: hardware IRQs start at IDT vector 32
|
// IRQ vector base: hardware IRQs start at IDT vector 32
|
||||||
constexpr uint8_t IRQ_VECTOR_BASE = 32;
|
constexpr uint8_t IRQ_VECTOR_BASE = 32;
|
||||||
@@ -29,7 +29,7 @@ namespace Hal {
|
|||||||
constexpr uint8_t IRQ_ATA1 = 14;
|
constexpr uint8_t IRQ_ATA1 = 14;
|
||||||
constexpr uint8_t IRQ_ATA2 = 15;
|
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);
|
void RegisterIrqHandler(uint8_t irq, IrqHandler handler);
|
||||||
|
|
||||||
// Install IRQ stubs into the IDT and set up the dispatch table
|
// Install IRQ stubs into the IDT and set up the dispatch table
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ IrqCommon:
|
|||||||
|
|
||||||
iretq
|
iretq
|
||||||
|
|
||||||
; Define stubs for IRQs 0..23 (vectors 32..55)
|
; Define stubs for IRQs 0..47 (vectors 32..79)
|
||||||
; This covers all standard ISA IRQs plus some extra IOAPIC inputs
|
; 0-23: legacy ISA IRQs via IOAPIC, 24-47: MSI vectors
|
||||||
IRQ_STUB 0
|
IRQ_STUB 0
|
||||||
IRQ_STUB 1
|
IRQ_STUB 1
|
||||||
IRQ_STUB 2
|
IRQ_STUB 2
|
||||||
@@ -91,6 +91,30 @@ IRQ_STUB 20
|
|||||||
IRQ_STUB 21
|
IRQ_STUB 21
|
||||||
IRQ_STUB 22
|
IRQ_STUB 22
|
||||||
IRQ_STUB 23
|
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
|
; Spurious interrupt handler (vector 0xFF) - do nothing, no EOI
|
||||||
global IrqStubSpurious
|
global IrqStubSpurious
|
||||||
@@ -125,3 +149,27 @@ IrqStubTable:
|
|||||||
dq IrqStub21
|
dq IrqStub21
|
||||||
dq IrqStub22
|
dq IrqStub22
|
||||||
dq IrqStub23
|
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
@@ -34,6 +34,7 @@
|
|||||||
#include <Drivers/PS2/Keyboard.hpp>
|
#include <Drivers/PS2/Keyboard.hpp>
|
||||||
#include <Drivers/PS2/Mouse.hpp>
|
#include <Drivers/PS2/Mouse.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
#include <Net/Net.hpp>
|
#include <Net/Net.hpp>
|
||||||
#include <CppLib/BoxUI.hpp>
|
#include <CppLib/BoxUI.hpp>
|
||||||
#include <Graphics/Cursor.hpp>
|
#include <Graphics/Cursor.hpp>
|
||||||
@@ -153,6 +154,10 @@ extern "C" void kmain() {
|
|||||||
Drivers::PS2::Mouse::Initialize();
|
Drivers::PS2::Mouse::Initialize();
|
||||||
|
|
||||||
Drivers::Net::E1000::Initialize();
|
Drivers::Net::E1000::Initialize();
|
||||||
|
if (!Drivers::Net::E1000::IsInitialized()) {
|
||||||
|
KernelLogStream(INFO, "Init") << "E1000 not found, trying E1000E...";
|
||||||
|
Drivers::Net::E1000E::Initialize();
|
||||||
|
}
|
||||||
Net::Initialize();
|
Net::Initialize();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -188,7 +193,9 @@ extern "C" void kmain() {
|
|||||||
Fs::Ramdisk::Read,
|
Fs::Ramdisk::Read,
|
||||||
Fs::Ramdisk::GetSize,
|
Fs::Ramdisk::GetSize,
|
||||||
Fs::Ramdisk::Close,
|
Fs::Ramdisk::Close,
|
||||||
Fs::Ramdisk::ReadDir
|
Fs::Ramdisk::ReadDir,
|
||||||
|
Fs::Ramdisk::Write,
|
||||||
|
Fs::Ramdisk::Create
|
||||||
};
|
};
|
||||||
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
|
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
|
||||||
|
|
||||||
@@ -198,7 +205,7 @@ extern "C" void kmain() {
|
|||||||
Zenith::InitializeSyscalls();
|
Zenith::InitializeSyscalls();
|
||||||
|
|
||||||
Sched::Initialize();
|
Sched::Initialize();
|
||||||
Sched::Spawn("0:/shell.elf");
|
Sched::Spawn("0:/os/init.elf");
|
||||||
|
|
||||||
// Enable preemptive scheduling via the APIC timer
|
// Enable preemptive scheduling via the APIC timer
|
||||||
Timekeeping::EnableSchedulerTick();
|
Timekeeping::EnableSchedulerTick();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <Net/Ipv4.hpp>
|
#include <Net/Ipv4.hpp>
|
||||||
#include <Net/NetConfig.hpp>
|
#include <Net/NetConfig.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
@@ -19,6 +20,12 @@ using namespace Kt;
|
|||||||
|
|
||||||
namespace Net::Arp {
|
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
|
// ARP cache entry
|
||||||
struct CacheEntry {
|
struct CacheEntry {
|
||||||
uint32_t Ip;
|
uint32_t Ip;
|
||||||
@@ -107,7 +114,7 @@ namespace Net::Arp {
|
|||||||
reply.ProtocolAddrLen = 4;
|
reply.ProtocolAddrLen = 4;
|
||||||
reply.Operation = Htons(OP_REPLY);
|
reply.Operation = Htons(OP_REPLY);
|
||||||
|
|
||||||
memcpy(reply.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6);
|
memcpy(reply.SenderMac, GetActiveNicMac(), 6);
|
||||||
reply.SenderIp = GetIpAddress();
|
reply.SenderIp = GetIpAddress();
|
||||||
memcpy(reply.TargetMac, pkt->SenderMac, 6);
|
memcpy(reply.TargetMac, pkt->SenderMac, 6);
|
||||||
reply.TargetIp = senderIp;
|
reply.TargetIp = senderIp;
|
||||||
@@ -141,7 +148,7 @@ namespace Net::Arp {
|
|||||||
req.ProtocolAddrLen = 4;
|
req.ProtocolAddrLen = 4;
|
||||||
req.Operation = Htons(OP_REQUEST);
|
req.Operation = Htons(OP_REQUEST);
|
||||||
|
|
||||||
memcpy(req.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6);
|
memcpy(req.SenderMac, GetActiveNicMac(), 6);
|
||||||
req.SenderIp = GetIpAddress();
|
req.SenderIp = GetIpAddress();
|
||||||
memset(req.TargetMac, 0, 6);
|
memset(req.TargetMac, 0, 6);
|
||||||
req.TargetIp = targetIp;
|
req.TargetIp = targetIp;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <Net/Arp.hpp>
|
#include <Net/Arp.hpp>
|
||||||
#include <Net/Ipv4.hpp>
|
#include <Net/Ipv4.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
@@ -17,6 +18,18 @@ using namespace Kt;
|
|||||||
|
|
||||||
namespace Net::Ethernet {
|
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() {
|
void Initialize() {
|
||||||
KernelLogStream(OK, "Net") << "Ethernet layer initialized";
|
KernelLogStream(OK, "Net") << "Ethernet layer initialized";
|
||||||
}
|
}
|
||||||
@@ -30,14 +43,14 @@ namespace Net::Ethernet {
|
|||||||
Header* hdr = (Header*)frame;
|
Header* hdr = (Header*)frame;
|
||||||
|
|
||||||
memcpy(hdr->DestMac, destMac, 6);
|
memcpy(hdr->DestMac, destMac, 6);
|
||||||
memcpy(hdr->SrcMac, Drivers::Net::E1000::GetMacAddress(), 6);
|
memcpy(hdr->SrcMac, GetActiveNicMac(), 6);
|
||||||
hdr->EtherType = Htons(etherType);
|
hdr->EtherType = Htons(etherType);
|
||||||
|
|
||||||
memcpy(frame + HEADER_SIZE, payload, payloadLen);
|
memcpy(frame + HEADER_SIZE, payload, payloadLen);
|
||||||
|
|
||||||
uint16_t totalLen = HEADER_SIZE + 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) {
|
void OnFrameReceived(const uint8_t* data, uint16_t length) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <Net/Socket.hpp>
|
#include <Net/Socket.hpp>
|
||||||
#include <Net/NetConfig.hpp>
|
#include <Net/NetConfig.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@ using namespace Kt;
|
|||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
void Initialize() {
|
void Initialize() {
|
||||||
if (!Drivers::Net::E1000::IsInitialized()) {
|
if (!Drivers::Net::E1000::IsInitialized() && !Drivers::Net::E1000E::IsInitialized()) {
|
||||||
KernelLogStream(WARNING, "Net") << "E1000 not initialized, skipping network stack";
|
KernelLogStream(WARNING, "Net") << "No NIC initialized, skipping network stack";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +37,12 @@ namespace Net {
|
|||||||
Tcp::Initialize();
|
Tcp::Initialize();
|
||||||
Socket::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);
|
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
|
||||||
|
} else {
|
||||||
|
Drivers::Net::E1000E::SetRxCallback(Ethernet::OnFrameReceived);
|
||||||
|
}
|
||||||
|
|
||||||
// Send a gratuitous ARP to announce ourselves on the network
|
// Send a gratuitous ARP to announce ourselves on the network
|
||||||
Arp::SendRequest(GetIpAddress());
|
Arp::SendRequest(GetIpAddress());
|
||||||
|
|||||||
+185
-2
@@ -1,19 +1,42 @@
|
|||||||
/*
|
/*
|
||||||
* Socket.cpp
|
* Socket.cpp
|
||||||
* Socket descriptor table wrapping kernel TCP layer
|
* Socket descriptor table wrapping kernel TCP and UDP layers
|
||||||
* Copyright (c) 2025 Daniel Hammer
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Socket.hpp"
|
#include "Socket.hpp"
|
||||||
#include <Net/Tcp.hpp>
|
#include <Net/Tcp.hpp>
|
||||||
|
#include <Net/Udp.hpp>
|
||||||
#include <Net/NetConfig.hpp>
|
#include <Net/NetConfig.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
namespace Net::Socket {
|
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 SocketEntry g_sockets[MAX_SOCKETS] = {};
|
||||||
static uint16_t g_nextEphemeralPort = 49152;
|
static uint16_t g_nextEphemeralPort = 49152;
|
||||||
|
|
||||||
@@ -30,15 +53,78 @@ namespace Net::Socket {
|
|||||||
return true;
|
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() {
|
void Initialize() {
|
||||||
for (int i = 0; i < MAX_SOCKETS; i++) {
|
for (int i = 0; i < MAX_SOCKETS; i++) {
|
||||||
g_sockets[i].Active = false;
|
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";
|
KernelLogStream(OK, "Net") << "Socket table initialized";
|
||||||
}
|
}
|
||||||
|
|
||||||
int Create(int type, int pid) {
|
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++) {
|
for (int i = 0; i < MAX_SOCKETS; i++) {
|
||||||
if (!g_sockets[i].Active) {
|
if (!g_sockets[i].Active) {
|
||||||
@@ -46,7 +132,18 @@ namespace Net::Socket {
|
|||||||
g_sockets[i].Type = type;
|
g_sockets[i].Type = type;
|
||||||
g_sockets[i].OwnerPid = pid;
|
g_sockets[i].OwnerPid = pid;
|
||||||
g_sockets[i].TcpConn = nullptr;
|
g_sockets[i].TcpConn = nullptr;
|
||||||
|
g_sockets[i].UdpState = nullptr;
|
||||||
g_sockets[i].LocalPort = 0;
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,6 +152,7 @@ namespace Net::Socket {
|
|||||||
|
|
||||||
int Connect(int fd, uint32_t ip, uint16_t port, int pid) {
|
int Connect(int fd, uint32_t ip, uint16_t port, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return -1;
|
if (!ValidFd(fd, pid)) return -1;
|
||||||
|
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||||
if (g_sockets[fd].TcpConn != nullptr) return -1;
|
if (g_sockets[fd].TcpConn != nullptr) return -1;
|
||||||
|
|
||||||
uint16_t srcPort = AllocEphemeralPort();
|
uint16_t srcPort = AllocEphemeralPort();
|
||||||
@@ -70,11 +168,20 @@ namespace Net::Socket {
|
|||||||
int Bind(int fd, uint16_t port, int pid) {
|
int Bind(int fd, uint16_t port, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return -1;
|
if (!ValidFd(fd, pid)) return -1;
|
||||||
g_sockets[fd].LocalPort = port;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Listen(int fd, int pid) {
|
int Listen(int fd, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return -1;
|
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].LocalPort == 0) return -1;
|
||||||
if (g_sockets[fd].TcpConn != nullptr) return -1;
|
if (g_sockets[fd].TcpConn != nullptr) return -1;
|
||||||
|
|
||||||
@@ -87,6 +194,7 @@ namespace Net::Socket {
|
|||||||
|
|
||||||
int Accept(int fd, int pid) {
|
int Accept(int fd, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return -1;
|
if (!ValidFd(fd, pid)) return -1;
|
||||||
|
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||||
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
||||||
|
|
||||||
Tcp::Connection* clientConn = Tcp::Accept(g_sockets[fd].TcpConn);
|
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].Type = SOCK_TCP;
|
||||||
g_sockets[i].OwnerPid = pid;
|
g_sockets[i].OwnerPid = pid;
|
||||||
g_sockets[i].TcpConn = clientConn;
|
g_sockets[i].TcpConn = clientConn;
|
||||||
|
g_sockets[i].UdpState = nullptr;
|
||||||
g_sockets[i].LocalPort = g_sockets[fd].LocalPort;
|
g_sockets[i].LocalPort = g_sockets[fd].LocalPort;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -111,22 +220,92 @@ namespace Net::Socket {
|
|||||||
|
|
||||||
int Send(int fd, const uint8_t* data, uint32_t len, int pid) {
|
int Send(int fd, const uint8_t* data, uint32_t len, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return -1;
|
if (!ValidFd(fd, pid)) return -1;
|
||||||
|
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||||
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
||||||
return Tcp::Send(g_sockets[fd].TcpConn, data, (uint16_t)len);
|
return Tcp::Send(g_sockets[fd].TcpConn, data, (uint16_t)len);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid) {
|
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return -1;
|
if (!ValidFd(fd, pid)) return -1;
|
||||||
|
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||||
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
||||||
return Tcp::ReceiveNonBlocking(g_sockets[fd].TcpConn, buf, (uint16_t)maxLen);
|
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) {
|
void Close(int fd, int pid) {
|
||||||
if (!ValidFd(fd, pid)) return;
|
if (!ValidFd(fd, pid)) return;
|
||||||
if (g_sockets[fd].TcpConn != nullptr) {
|
if (g_sockets[fd].TcpConn != nullptr) {
|
||||||
Tcp::Close(g_sockets[fd].TcpConn);
|
Tcp::Close(g_sockets[fd].TcpConn);
|
||||||
g_sockets[fd].TcpConn = nullptr;
|
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;
|
g_sockets[fd].Active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +316,10 @@ namespace Net::Socket {
|
|||||||
Tcp::Close(g_sockets[i].TcpConn);
|
Tcp::Close(g_sockets[i].TcpConn);
|
||||||
g_sockets[i].TcpConn = nullptr;
|
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;
|
g_sockets[i].Active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Socket.hpp
|
* Socket.hpp
|
||||||
* Socket descriptor table for userspace TCP access
|
* Socket descriptor table for userspace TCP/UDP access
|
||||||
* Copyright (c) 2025 Daniel Hammer
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -14,11 +14,14 @@ namespace Net::Socket {
|
|||||||
static constexpr int SOCK_UDP = 2;
|
static constexpr int SOCK_UDP = 2;
|
||||||
static constexpr int MAX_SOCKETS = 64;
|
static constexpr int MAX_SOCKETS = 64;
|
||||||
|
|
||||||
|
struct UdpSocketState;
|
||||||
|
|
||||||
struct SocketEntry {
|
struct SocketEntry {
|
||||||
bool Active;
|
bool Active;
|
||||||
int Type;
|
int Type;
|
||||||
int OwnerPid;
|
int OwnerPid;
|
||||||
Tcp::Connection* TcpConn;
|
Tcp::Connection* TcpConn;
|
||||||
|
UdpSocketState* UdpState;
|
||||||
uint16_t LocalPort;
|
uint16_t LocalPort;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,6 +48,14 @@ namespace Net::Socket {
|
|||||||
// Receive data from a connected socket. Returns bytes received, 0 on close, or -1.
|
// 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);
|
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.
|
// Close a socket.
|
||||||
void Close(int fd, int pid);
|
void Close(int fd, int pid);
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Net::Udp {
|
|||||||
// Dispatch to bound callback
|
// Dispatch to bound callback
|
||||||
for (uint32_t i = 0; i < MAX_BINDINGS; i++) {
|
for (uint32_t i = 0; i < MAX_BINDINGS; i++) {
|
||||||
if (g_bindings[i].Active && g_bindings[i].Port == dstPort) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ namespace Net::Udp {
|
|||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// Callback type for receiving UDP data
|
// 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
|
// Initialize the UDP subsystem
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
|||||||
@@ -223,6 +223,32 @@ namespace Pci {
|
|||||||
return LegacyRead8(bus, device, function, (uint8_t)offset);
|
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
|
// PCI class code names
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ namespace Pci {
|
|||||||
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value);
|
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);
|
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
|
// Class code name lookup
|
||||||
const char* GetClassName(uint8_t classCode, uint8_t subClass);
|
const char* GetClassName(uint8_t classCode, uint8_t subClass);
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ namespace Sched {
|
|||||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
||||||
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
||||||
if (handle < 0) {
|
if (handle < 0) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ namespace Sched {
|
|||||||
// Load ELF into the process's address space
|
// Load ELF into the process's address space
|
||||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||||
if (entry == 0) {
|
if (entry == 0) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +203,13 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Schedule() {
|
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 next = -1;
|
||||||
int start = (currentPid >= 0) ? currentPid + 1 : 0;
|
int start = (currentPid >= 0) ? currentPid + 1 : 0;
|
||||||
|
|
||||||
@@ -311,9 +317,13 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsAlive(int pid) {
|
bool IsAlive(int pid) {
|
||||||
if (pid < 0 || pid >= MaxProcesses) return false;
|
for (int i = 0; i < MaxProcesses; i++) {
|
||||||
return processTable[pid].state == ProcessState::Ready
|
if (processTable[i].pid == pid) {
|
||||||
|| processTable[pid].state == ProcessState::Running;
|
return processTable[i].state == ProcessState::Ready
|
||||||
|
|| processTable[i].state == ProcessState::Running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
@@ -37,10 +38,14 @@ namespace Timekeeping {
|
|||||||
|
|
||||||
static bool g_schedEnabled = false;
|
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) {
|
static void TimerHandler(uint8_t) {
|
||||||
g_tickCount = g_tickCount + 1;
|
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) {
|
if (g_schedEnabled) {
|
||||||
Sched::Tick();
|
Sched::Tick();
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-8
@@ -56,29 +56,63 @@ BINDIR := bin
|
|||||||
# Discover all programs (each subdirectory under src/ is a program).
|
# Discover all programs (each subdirectory under src/ is a program).
|
||||||
PROGRAMS := $(notdir $(wildcard src/*))
|
PROGRAMS := $(notdir $(wildcard src/*))
|
||||||
|
|
||||||
# Build targets: one ELF per program.
|
# Games are built separately (doom has its own Makefile).
|
||||||
TARGETS := $(addprefix $(BINDIR)/,$(addsuffix .elf,$(PROGRAMS)))
|
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.
|
# Man pages source directory.
|
||||||
MANDIR := man
|
MANDIR := man
|
||||||
MANSRC := $(wildcard $(MANDIR)/*.*)
|
MANSRC := $(wildcard $(MANDIR)/*.*)
|
||||||
MANDST := $(patsubst $(MANDIR)/%,$(BINDIR)/man/%,$(MANSRC))
|
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.
|
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
||||||
$(BINDIR)/man/%: $(MANDIR)/%
|
$(BINDIR)/man/%: $(MANDIR)/%
|
||||||
mkdir -p $(BINDIR)/man
|
mkdir -p $(BINDIR)/man
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
# Build each program from its source files.
|
# Copy web content into bin/www/ so mkramdisk.sh picks them up.
|
||||||
# For now each program is a single .cpp file compiled and linked directly.
|
$(BINDIR)/www/%: $(WWWDIR)/%
|
||||||
$(BINDIR)/%.elf: src/%/main.cpp link.ld GNUmakefile
|
mkdir -p $(BINDIR)/www
|
||||||
mkdir -p $(BINDIR) obj/$*
|
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) -c src/$*/main.cpp -o obj/$*/main.o
|
||||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) obj/$*/main.o -o $@
|
$(CXX) $(CXXFLAGS) $(LDFLAGS) obj/$*/main.o -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BINDIR) obj
|
rm -rf $(BINDIR) obj
|
||||||
|
$(MAKE) -C src/doom clean
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -48,8 +48,23 @@ namespace Zenith {
|
|||||||
static constexpr uint64_t SYS_SEND = 34;
|
static constexpr uint64_t SYS_SEND = 34;
|
||||||
static constexpr uint64_t SYS_RECV = 35;
|
static constexpr uint64_t SYS_RECV = 35;
|
||||||
static constexpr uint64_t SYS_CLOSESOCK = 36;
|
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_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 {
|
struct DateTime {
|
||||||
uint16_t Year;
|
uint16_t Year;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -136,6 +136,14 @@ namespace zenith {
|
|||||||
return (int)syscall3(Zenith::SYS_READDIR, (uint64_t)path, (uint64_t)names, (uint64_t)max);
|
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
|
// Memory
|
||||||
inline void* alloc(uint64_t size) { return (void*)syscall1(Zenith::SYS_ALLOC, size); }
|
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); }
|
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);
|
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
|
// Sockets
|
||||||
inline int socket(int type) {
|
inline int socket(int type) {
|
||||||
return (int)syscall1(Zenith::SYS_SOCKET, (uint64_t)type);
|
return (int)syscall1(Zenith::SYS_SOCKET, (uint64_t)type);
|
||||||
@@ -182,6 +194,14 @@ namespace zenith {
|
|||||||
inline int closesocket(int fd) {
|
inline int closesocket(int fd) {
|
||||||
return (int)syscall1(Zenith::SYS_CLOSESOCK, (uint64_t)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
|
// Process management
|
||||||
inline void waitpid(int pid) { syscall1(Zenith::SYS_WAITPID, (uint64_t)pid); }
|
inline void waitpid(int pid) { syscall1(Zenith::SYS_WAITPID, (uint64_t)pid); }
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
@@ -12,13 +12,13 @@
|
|||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
ZenithOS provides a simple read-only Virtual File System (VFS)
|
ZenithOS provides a simple read-only Virtual File System (VFS)
|
||||||
backed by the boot ramdisk. Files are accessed via paths in the
|
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
|
.SS open
|
||||||
Opens a file and returns a non-negative handle on success, or a
|
Opens a file and returns a non-negative handle on success, or a
|
||||||
negative value on error (file not found, no free handles).
|
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
|
.SS read
|
||||||
Reads up to 'size' bytes starting at 'offset' into 'buf'.
|
Reads up to 'size' bytes starting at 'offset' into 'buf'.
|
||||||
@@ -42,15 +42,17 @@
|
|||||||
.SS readdir
|
.SS readdir
|
||||||
Lists entries in a directory. Up to 'max' entry names (max 64)
|
Lists entries in a directory. Up to 'max' entry names (max 64)
|
||||||
are written to the 'names' array. The kernel allocates a user-
|
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];
|
const char* entries[64];
|
||||||
int count = zenith::readdir("0:/", entries, 64);
|
int count = zenith::readdir("0:/", entries, 64);
|
||||||
|
// entries: "os/", "games/", "man/", "www/", "home/"
|
||||||
|
|
||||||
.SH READING PATTERN
|
.SH READING PATTERN
|
||||||
The standard pattern for reading a file:
|
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);
|
uint64_t size = zenith::getsize(h);
|
||||||
uint8_t buf[512];
|
uint8_t buf[512];
|
||||||
uint64_t off = 0;
|
uint64_t off = 0;
|
||||||
|
|||||||
@@ -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
@@ -19,26 +19,42 @@
|
|||||||
|
|
||||||
extern "C" void _start() { ... }
|
extern "C" void _start() { ... }
|
||||||
|
|
||||||
There is no argc/argv. Include <zenith/syscall.h> for the full
|
There is no argc/argv. Use zenith::getargs() to retrieve any
|
||||||
typed syscall API. Include <zenith/heap.h> for malloc/mfree.
|
arguments passed by the parent process. Include <zenith/syscall.h>
|
||||||
|
for the full typed syscall API.
|
||||||
|
|
||||||
Build with:
|
Build with:
|
||||||
|
|
||||||
cd programs && make
|
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
|
.SH SHELL
|
||||||
The built-in shell is the primary way to interact with ZenithOS.
|
The interactive shell is the primary way to interact with
|
||||||
Type 'help' at the shell prompt for a list of commands. Use
|
ZenithOS. Commands are resolved by searching the PATH
|
||||||
'man shell' for detailed shell documentation.
|
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
|
.SH MAN PAGES
|
||||||
The following man pages are available:
|
The following man pages are available:
|
||||||
|
|
||||||
intro(1) This page
|
intro(1) This page
|
||||||
shell(1) Shell commands reference
|
shell(1) Shell commands reference
|
||||||
|
init(1) Init system
|
||||||
|
dhcp(1) DHCP client
|
||||||
man(1) The man command itself
|
man(1) The man command itself
|
||||||
|
legal(7) Copyright and legal information
|
||||||
syscalls(2) Overview of all syscalls
|
syscalls(2) Overview of all syscalls
|
||||||
spawn(2) Process spawning
|
spawn(2) Process spawning
|
||||||
file(2) File I/O syscalls
|
file(2) File I/O syscalls
|
||||||
|
|||||||
+4
-2
@@ -11,7 +11,7 @@
|
|||||||
fullscreen pager. Pages are stored as plain text files with
|
fullscreen pager. Pages are stored as plain text files with
|
||||||
simple formatting directives.
|
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
|
in order. If a section number is given, only that section is
|
||||||
checked.
|
checked.
|
||||||
|
|
||||||
@@ -27,9 +27,10 @@
|
|||||||
q Quit
|
q Quit
|
||||||
|
|
||||||
.SH SECTIONS
|
.SH SECTIONS
|
||||||
1 User commands (shell built-ins)
|
1 User commands and programs
|
||||||
2 System calls (kernel interface)
|
2 System calls (kernel interface)
|
||||||
3 Library functions (userspace libraries)
|
3 Library functions (userspace libraries)
|
||||||
|
7 Miscellaneous (legal, conventions)
|
||||||
|
|
||||||
.SH FILES
|
.SH FILES
|
||||||
Man pages are stored on the ramdisk at:
|
Man pages are stored on the ramdisk at:
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
man intro View the introduction
|
man intro View the introduction
|
||||||
man 2 syscalls View syscall overview (section 2)
|
man 2 syscalls View syscall overview (section 2)
|
||||||
man malloc View malloc documentation
|
man malloc View malloc documentation
|
||||||
|
man legal View copyright information
|
||||||
|
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
intro(1), shell(1), syscalls(2)
|
intro(1), shell(1), syscalls(2)
|
||||||
|
|||||||
+77
-42
@@ -3,66 +3,101 @@
|
|||||||
shell - ZenithOS interactive command shell
|
shell - ZenithOS interactive command shell
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
The ZenithOS shell is a simple command interpreter that runs as
|
The ZenithOS shell is a command interpreter launched by init
|
||||||
the first userspace process. It provides basic file inspection,
|
after system services have started. It provides command
|
||||||
process management, networking, and documentation access.
|
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
|
.SS help
|
||||||
Display a list of available commands.
|
Display a categorized list of available commands.
|
||||||
|
|
||||||
.SS info
|
|
||||||
Show the OS name, version, and syscall API version number.
|
|
||||||
|
|
||||||
.SS man <topic>
|
|
||||||
Open a manual page in the fullscreen pager. See man(1).
|
|
||||||
|
|
||||||
.SS ls [dir]
|
.SS ls [dir]
|
||||||
List files in the current directory, or in the specified
|
List files in the current directory, or in the specified
|
||||||
directory. When a directory argument is given, the output
|
directory. When a directory argument is given, the output
|
||||||
shows only the entries inside that directory with the
|
shows only the entries inside that directory with the
|
||||||
directory prefix stripped.
|
directory prefix stripped. Directory entries are shown
|
||||||
Examples: ls, ls man
|
with a trailing slash.
|
||||||
|
Examples: ls, ls man, ls os
|
||||||
|
|
||||||
.SS cd [dir]
|
.SS cd [dir]
|
||||||
Change the working directory. With no argument or with /,
|
Change the working directory. With no argument or with /,
|
||||||
returns to the root (0:/). Use cd .. to go up one level.
|
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.
|
The shell prompt reflects the current directory.
|
||||||
Examples: cd man, cd .., cd
|
Examples: cd os, 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.
|
|
||||||
|
|
||||||
.SS exit
|
.SS exit
|
||||||
Terminate the shell process.
|
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
|
.SH INPUT
|
||||||
The shell reads input character by character using SYS_GETCHAR.
|
The shell uses non-blocking keyboard input via SYS_GETKEY to
|
||||||
Backspace is supported. Lines are limited to 255 characters.
|
support arrow key detection. Lines are limited to 255
|
||||||
There is no command history or tab completion.
|
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
|
.SH SEE ALSO
|
||||||
man(1), intro(1), syscalls(2)
|
man(1), intro(1), syscalls(2)
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
Loads the ELF64 binary at the given VFS path and creates a new
|
Loads the ELF64 binary at the given VFS path and creates a new
|
||||||
process. The path must include the drive prefix, for example:
|
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:
|
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
|
The new process gets its own PML4 page table, a 16 KiB stack
|
||||||
(at 0x7FFFFEF000-0x7FFFFFF000), and begins executing at the
|
(at 0x7FFFFEF000-0x7FFFFFF000), and begins executing at the
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
Spawn a program and wait for it:
|
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) {
|
if (pid < 0) {
|
||||||
zenith::print("spawn failed\n");
|
zenith::print("spawn failed\n");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+23
-3
@@ -3,7 +3,7 @@
|
|||||||
syscalls - overview of ZenithOS system calls
|
syscalls - overview of ZenithOS system calls
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.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
|
programs. Syscalls use the x86-64 SYSCALL instruction with the
|
||||||
following register convention:
|
following register convention:
|
||||||
|
|
||||||
@@ -120,8 +120,18 @@
|
|||||||
Send an ICMP echo request and wait for reply.
|
Send an ICMP echo request and wait for reply.
|
||||||
int32_t zenith::ping(uint32_t ip, uint32_t timeoutMs);
|
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)
|
.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);
|
int zenith::socket(int type);
|
||||||
|
|
||||||
.B SYS_CONNECT (30)
|
.B SYS_CONNECT (30)
|
||||||
@@ -147,13 +157,23 @@
|
|||||||
|
|
||||||
.B SYS_RECV (35)
|
.B SYS_RECV (35)
|
||||||
Receive data from a connected socket. Returns bytes
|
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);
|
int zenith::recv(int fd, void* buf, uint32_t maxLen);
|
||||||
|
|
||||||
.B SYS_CLOSESOCK (36)
|
.B SYS_CLOSESOCK (36)
|
||||||
Close a socket and release its resources.
|
Close a socket and release its resources.
|
||||||
int zenith::closesocket(int fd);
|
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
|
.SH FRAMEBUFFER
|
||||||
.B SYS_FBINFO (21)
|
.B SYS_FBINFO (21)
|
||||||
Get framebuffer dimensions and format.
|
Get framebuffer dimensions and format.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -163,14 +163,14 @@ ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS)
|
|||||||
|
|
||||||
# ---- Target ----
|
# ---- Target ----
|
||||||
|
|
||||||
TARGET := $(BINDIR)/doom.elf
|
TARGET := $(BINDIR)/games/doom.elf
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
|
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
|
||||||
mkdir -p $(BINDIR)
|
mkdir -p $(BINDIR)/games
|
||||||
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
|
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
|
||||||
|
|
||||||
# DOOM source files (from doomgeneric directory)
|
# DOOM source files (from doomgeneric directory)
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ void DG_SetWindowTitle(const char* title) {
|
|||||||
/* ---- Entry point ---- */
|
/* ---- Entry point ---- */
|
||||||
|
|
||||||
void _start(void) {
|
void _start(void) {
|
||||||
char *argv[] = { "doom", "-iwad", "0:/doom1.wad", 0 };
|
char *argv[] = { "doom", "-iwad", "0:/games/doom1.wad", 0 };
|
||||||
doomgeneric_Create(3, argv);
|
doomgeneric_Create(3, argv);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
doomgeneric_Tick();
|
doomgeneric_Tick();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
@@ -6,6 +6,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <zenith/syscall.h>
|
#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) ----
|
// ---- Minimal snprintf (no libc available) ----
|
||||||
|
|
||||||
@@ -91,51 +101,6 @@ static int snprintf(char* buf, int size, const char* fmt, ...) {
|
|||||||
return ret;
|
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
|
// Case-insensitive comparison for IRC commands
|
||||||
static bool streqi(const char* a, const char* b) {
|
static bool streqi(const char* a, const char* b) {
|
||||||
while (*a && *b) {
|
while (*a && *b) {
|
||||||
|
|||||||
@@ -7,27 +7,11 @@
|
|||||||
|
|
||||||
#include <zenith/syscall.h>
|
#include <zenith/syscall.h>
|
||||||
#include <zenith/heap.h>
|
#include <zenith/heap.h>
|
||||||
|
#include <zenith/string.h>
|
||||||
|
|
||||||
// ---- Utility functions ----
|
using zenith::slen;
|
||||||
|
using zenith::starts_with;
|
||||||
static bool starts_with(const char* str, const char* prefix) {
|
using zenith::skip_spaces;
|
||||||
while (*prefix) {
|
|
||||||
if (*str != *prefix) return false;
|
|
||||||
str++; prefix++;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* skip_spaces(const char* s) {
|
|
||||||
while (*s == ' ') s++;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int slen(const char* s) {
|
|
||||||
int n = 0;
|
|
||||||
while (s[n]) n++;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_int(uint64_t n) {
|
static void print_int(uint64_t n) {
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
+282
-447
@@ -5,52 +5,33 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <zenith/syscall.h>
|
#include <zenith/syscall.h>
|
||||||
|
#include <zenith/string.h>
|
||||||
|
|
||||||
static bool streq(const char* a, const char* b) {
|
using zenith::slen;
|
||||||
while (*a && *b) {
|
using zenith::streq;
|
||||||
if (*a != *b) return false;
|
using zenith::starts_with;
|
||||||
a++; b++;
|
using zenith::skip_spaces;
|
||||||
}
|
|
||||||
return *a == *b;
|
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 bool starts_with(const char* str, const char* prefix) {
|
static void scat(char* dst, const char* src, int maxLen) {
|
||||||
while (*prefix) {
|
int dLen = slen(dst);
|
||||||
if (*str != *prefix) return false;
|
int i = 0;
|
||||||
str++; prefix++;
|
while (src[i] && dLen + i < maxLen - 1) {
|
||||||
|
dst[dLen + i] = src[i];
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
return true;
|
dst[dLen + i] = '\0';
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current working directory (relative to 0:/).
|
// Current working directory (relative to 0:/).
|
||||||
// "" = root, "man" = 0:/man, "man/sub" = 0:/man/sub
|
// "" = root, "man" = 0:/man, "man/sub" = 0:/man/sub
|
||||||
static char cwd[128] = "";
|
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>"
|
// Build VFS directory path: "0:/" or "0:/<dir>"
|
||||||
static void build_dir_path(const char* dir, char* out, int outMax) {
|
static void build_dir_path(const char* dir, char* out, int outMax) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -62,58 +43,100 @@ static void build_dir_path(const char* dir, char* out, int outMax) {
|
|||||||
out[i] = '\0';
|
out[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_int(uint64_t n) {
|
// ---- Command history ----
|
||||||
if (n == 0) {
|
|
||||||
zenith::putchar('0');
|
static constexpr int HISTORY_MAX = 32;
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
char buf[20];
|
scopy(history[history_next], line, 256);
|
||||||
int i = 0;
|
history_next = (history_next + 1) % HISTORY_MAX;
|
||||||
while (n > 0) {
|
if (history_count < HISTORY_MAX) history_count++;
|
||||||
buf[i++] = '0' + (n % 10);
|
|
||||||
n /= 10;
|
|
||||||
}
|
|
||||||
for (int j = i - 1; j >= 0; j--) {
|
|
||||||
zenith::putchar(buf[j]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
static void prompt() {
|
||||||
zenith::print("0:/");
|
zenith::print("0:/");
|
||||||
if (cwd[0]) zenith::print(cwd);
|
if (cwd[0]) zenith::print(cwd);
|
||||||
zenith::print("> ");
|
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() {
|
static void cmd_help() {
|
||||||
zenith::print("Available commands:\n");
|
zenith::print("Shell builtins:\n");
|
||||||
zenith::print(" help Show this help message\n");
|
zenith::print(" help Show this help message\n");
|
||||||
zenith::print(" info Show system information\n");
|
|
||||||
zenith::print(" man <topic> View manual pages\n");
|
|
||||||
zenith::print(" ls [dir] List files in directory\n");
|
zenith::print(" ls [dir] List files in directory\n");
|
||||||
zenith::print(" cd [dir] Change working 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(" cat <file> Display file contents\n");
|
||||||
zenith::print(" run <file> Spawn a new process from an ELF file\n");
|
zenith::print(" edit [file] Text editor\n");
|
||||||
zenith::print(" ping <ip> Send ICMP echo requests\n");
|
zenith::print(" info Show system information\n");
|
||||||
zenith::print(" tcpconnect <ip> <port> Connect to a TCP server\n");
|
|
||||||
zenith::print(" date Show current date and time\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(" clear Clear the screen\n");
|
||||||
zenith::print(" reset Reboot the system\n");
|
zenith::print(" reset Reboot the system\n");
|
||||||
zenith::print(" shutdown Shut down 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() {
|
// ---- Builtin: ls ----
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cmd_ls(const char* arg) {
|
static void cmd_ls(const char* arg) {
|
||||||
arg = skip_spaces(arg);
|
arg = skip_spaces(arg);
|
||||||
@@ -121,7 +144,7 @@ static void cmd_ls(const char* arg) {
|
|||||||
// Build the target directory (relative path from root)
|
// Build the target directory (relative path from root)
|
||||||
char dir[128];
|
char dir[128];
|
||||||
if (*arg) {
|
if (*arg) {
|
||||||
// ls <dir> — combine cwd and arg
|
// ls <dir> -- combine cwd and arg
|
||||||
if (cwd[0]) {
|
if (cwd[0]) {
|
||||||
int i = 0, j = 0;
|
int i = 0, j = 0;
|
||||||
while (cwd[j] && i < 126) dir[i++] = cwd[j++];
|
while (cwd[j] && i < 126) dir[i++] = cwd[j++];
|
||||||
@@ -135,7 +158,7 @@ static void cmd_ls(const char* arg) {
|
|||||||
dir[i] = '\0';
|
dir[i] = '\0';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ls with no arg — use cwd
|
// ls with no arg -- use cwd
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (cwd[i] && i < 126) { dir[i] = cwd[i]; i++; }
|
while (cwd[i] && i < 126) { dir[i] = cwd[i]; i++; }
|
||||||
dir[i] = '\0';
|
dir[i] = '\0';
|
||||||
@@ -166,310 +189,26 @@ static void cmd_ls(const char* arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_cat(const char* arg) {
|
// ---- Builtin: cd ----
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cmd_cd(const char* arg) {
|
static void cmd_cd(const char* arg) {
|
||||||
arg = skip_spaces(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, "/")) {
|
if (*arg == '\0' || streq(arg, "/")) {
|
||||||
cwd[0] = '\0';
|
cwd[0] = '\0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cd .. → go up one level
|
// cd .. -> go up one level
|
||||||
if (streq(arg, "..")) {
|
if (streq(arg, "..")) {
|
||||||
int len = slen(cwd);
|
int len = slen(cwd);
|
||||||
int last = -1;
|
int last = -1;
|
||||||
@@ -517,6 +256,8 @@ static void cmd_cd(const char* arg) {
|
|||||||
cwd[i] = '\0';
|
cwd[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Builtin: man ----
|
||||||
|
|
||||||
static void cmd_man(const char* arg) {
|
static void cmd_man(const char* arg) {
|
||||||
arg = skip_spaces(arg);
|
arg = skip_spaces(arg);
|
||||||
if (*arg == '\0') {
|
if (*arg == '\0') {
|
||||||
@@ -526,7 +267,7 @@ static void cmd_man(const char* arg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pid = zenith::spawn("0:/man.elf", arg);
|
int pid = zenith::spawn("0:/os/man.elf", arg);
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
zenith::print("Error: failed to start man viewer\n");
|
zenith::print("Error: failed to start man viewer\n");
|
||||||
} else {
|
} else {
|
||||||
@@ -534,102 +275,155 @@ static void cmd_man(const char* arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_int_padded(uint64_t n) {
|
// ---- External command execution ----
|
||||||
if (n < 10) zenith::putchar('0');
|
|
||||||
print_int(n);
|
// 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) {
|
// Check if a string already has a VFS drive prefix (e.g. "0:/")
|
||||||
static const char* months[] = {
|
static bool has_drive_prefix(const char* s) {
|
||||||
"", "January", "February", "March", "April", "May", "June",
|
return s[0] >= '0' && s[0] <= '9' && s[1] == ':';
|
||||||
"July", "August", "September", "October", "November", "December"
|
|
||||||
};
|
|
||||||
if (m >= 1 && m <= 12) return months[m];
|
|
||||||
return "?";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_date() {
|
// Resolve arguments: expand relative file paths against CWD.
|
||||||
Zenith::DateTime dt;
|
// Tokens that already have a drive prefix (e.g. "0:/foo") are left as-is.
|
||||||
zenith::gettime(&dt);
|
// 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);
|
int o = 0;
|
||||||
zenith::putchar(' ');
|
const char* p = args;
|
||||||
zenith::print(month_name(dt.Month));
|
|
||||||
zenith::putchar(' ');
|
while (*p && o < outMax - 1) {
|
||||||
print_int(dt.Year);
|
// Skip spaces, copy them through
|
||||||
zenith::print(", ");
|
while (*p == ' ' && o < outMax - 1) { out[o++] = *p++; }
|
||||||
print_int(dt.Hour);
|
if (!*p) break;
|
||||||
zenith::putchar(':');
|
|
||||||
print_int_padded(dt.Minute);
|
// Extract the token
|
||||||
zenith::putchar(':');
|
const char* tokStart = p;
|
||||||
print_int_padded(dt.Second);
|
int tokLen = 0;
|
||||||
zenith::print(" UTC\n");
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_clear() {
|
p = tokStart + tokLen;
|
||||||
zenith::print("\033[2J"); // Clear entire screen
|
|
||||||
zenith::print("\033[H"); // Move cursor to top-left
|
|
||||||
}
|
}
|
||||||
|
out[o] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
static void process_command(const char* line) {
|
||||||
// Skip leading spaces
|
|
||||||
line = skip_spaces(line);
|
line = skip_spaces(line);
|
||||||
if (*line == '\0') return;
|
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();
|
cmd_help();
|
||||||
} else if (streq(line, "info")) {
|
} else if (streq(cmd, "ls")) {
|
||||||
cmd_info();
|
cmd_ls(args ? args : "");
|
||||||
} else if (starts_with(line, "ls ")) {
|
} else if (streq(cmd, "cd")) {
|
||||||
cmd_ls(line + 3);
|
cmd_cd(args ? args : "");
|
||||||
} else if (streq(line, "ls")) {
|
} else if (streq(cmd, "man")) {
|
||||||
cmd_ls("");
|
cmd_man(args ? args : "");
|
||||||
} else if (starts_with(line, "cd ")) {
|
} else if (streq(cmd, "exit")) {
|
||||||
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")) {
|
|
||||||
zenith::print("Goodbye.\n");
|
zenith::print("Goodbye.\n");
|
||||||
zenith::exit(0);
|
zenith::exit(0);
|
||||||
} else {
|
} else {
|
||||||
zenith::print("Unknown command: ");
|
// External command -- pass full argument string
|
||||||
zenith::print(line);
|
exec_external(cmd, args);
|
||||||
zenith::print("\nType 'help' for available commands.\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 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() {
|
extern "C" void _start() {
|
||||||
zenith::print("\n");
|
zenith::print("\n");
|
||||||
zenith::print(" ZenithOS\n");
|
zenith::print(" ZenithOS\n");
|
||||||
@@ -641,28 +435,69 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
char line[256];
|
char line[256];
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
int hist_nav = -1; // -1 = not navigating history
|
||||||
|
|
||||||
prompt();
|
prompt();
|
||||||
|
|
||||||
while (true) {
|
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');
|
zenith::putchar('\n');
|
||||||
line[pos] = '\0';
|
line[pos] = '\0';
|
||||||
|
history_add(line);
|
||||||
process_command(line);
|
process_command(line);
|
||||||
pos = 0;
|
pos = 0;
|
||||||
|
hist_nav = -1;
|
||||||
prompt();
|
prompt();
|
||||||
} else if (c == '\b') {
|
} else if (ev.ascii == '\b') {
|
||||||
if (pos > 0) {
|
if (pos > 0) {
|
||||||
pos--;
|
pos--;
|
||||||
zenith::putchar('\b');
|
zenith::putchar('\b');
|
||||||
zenith::putchar(' ');
|
zenith::putchar(' ');
|
||||||
zenith::putchar('\b');
|
zenith::putchar('\b');
|
||||||
}
|
}
|
||||||
} else if (c >= ' ' && pos < 255) {
|
} else if (ev.ascii >= ' ' && pos < 255) {
|
||||||
line[pos++] = c;
|
line[pos++] = ev.ascii;
|
||||||
zenith::putchar(c);
|
zenith::putchar(ev.ascii);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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> — 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 · httpd/1.0
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user