feat: split syscalls into files, change max process ceiling
This commit is contained in:
@@ -1,2 +1,23 @@
|
|||||||
Copyright (c) 2025-2026 Daniel Hammer, et al.
|
Copyright (c) 2025-2026 Daniel Hammer, et al. All rights reserved.
|
||||||
(including contributors to other projects, i.e. The Limine Bootloader. Please refer to any other project's own license.)
|
(includes contributors to other projects, i.e. The Limine Bootloader. Please refer to any other project's own license.)
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
== License for the Limine C++ template (certain portions derive therefrom) ==
|
||||||
|
Copyright (C) 2023-2026 Mintsuki and contributors.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
// Find the process that owns the I/O ring buffers for a redirected process.
|
||||||
|
// If proc owns buffers itself (spawned via spawn_redir), returns proc.
|
||||||
|
// If proc inherited redirection (spawned via spawn from a redirected parent),
|
||||||
|
// follows parentPid to find the buffer owner.
|
||||||
|
static Sched::Process* GetRedirTarget(Sched::Process* proc) {
|
||||||
|
if (!proc || !proc->redirected) return nullptr;
|
||||||
|
if (proc->outBuf) return proc; // owns buffers
|
||||||
|
return Sched::GetProcessByPid(proc->parentPid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RingWrite(uint8_t* buf, uint32_t& head, uint32_t /*tail*/, uint32_t size, uint8_t byte) {
|
||||||
|
buf[head] = byte;
|
||||||
|
head = (head + 1) % size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int RingRead(uint8_t* buf, uint32_t& head, uint32_t& tail, uint32_t size, uint8_t* out, int maxLen) {
|
||||||
|
int count = 0;
|
||||||
|
while (tail != head && count < maxLen) {
|
||||||
|
out[count++] = buf[tail];
|
||||||
|
tail = (tail + 1) % size;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Device.hpp
|
||||||
|
* SYS_DEVLIST syscall
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Hal/Apic/ApicInit.hpp>
|
||||||
|
#include <Drivers/PS2/PS2Controller.hpp>
|
||||||
|
#include <Drivers/USB/Xhci.hpp>
|
||||||
|
#include <Drivers/Net/E1000.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
|
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||||
|
#include <Pci/Pci.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void dl_strcpy(char* dst, const char* src, int max) {
|
||||||
|
int i = 0;
|
||||||
|
for (; i < max - 1 && src[i]; i++) dst[i] = src[i];
|
||||||
|
dst[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dl_append(char* dst, int pos, const char* src, int max) {
|
||||||
|
for (int i = 0; src[i] && pos < max - 1; i++) dst[pos++] = src[i];
|
||||||
|
dst[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dl_append_hex(char* dst, int pos, unsigned val, int digits, int max) {
|
||||||
|
const char* hex = "0123456789abcdef";
|
||||||
|
char tmp[8];
|
||||||
|
for (int i = digits - 1; i >= 0; i--) { tmp[i] = hex[val & 0xF]; val >>= 4; }
|
||||||
|
for (int i = 0; i < digits && pos < max - 1; i++) dst[pos++] = tmp[i];
|
||||||
|
dst[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dl_append_dec(char* dst, int pos, int val, int max) {
|
||||||
|
if (val == 0) { if (pos < max - 1) dst[pos++] = '0'; dst[pos] = '\0'; return pos; }
|
||||||
|
char tmp[12]; int i = 0;
|
||||||
|
while (val > 0) { tmp[i++] = '0' + (val % 10); val /= 10; }
|
||||||
|
while (i > 0 && pos < max - 1) dst[pos++] = tmp[--i];
|
||||||
|
dst[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_DevList(DevInfo* buf, int maxCount) {
|
||||||
|
if (buf == nullptr || maxCount <= 0) return 0;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
auto add = [&](uint8_t cat, const char* name, const char* detail) {
|
||||||
|
if (count >= maxCount) return;
|
||||||
|
buf[count].category = cat;
|
||||||
|
buf[count]._pad[0] = 0; buf[count]._pad[1] = 0; buf[count]._pad[2] = 0;
|
||||||
|
dl_strcpy(buf[count].name, name, 48);
|
||||||
|
dl_strcpy(buf[count].detail, detail, 48);
|
||||||
|
count++;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CPU cores (category 0)
|
||||||
|
int cpuCount = Hal::GetDetectedCpuCount();
|
||||||
|
if (cpuCount > 0) {
|
||||||
|
char detail[48];
|
||||||
|
int p = 0;
|
||||||
|
p = dl_append(detail, p, "x86_64, ", 48);
|
||||||
|
p = dl_append_dec(detail, p, cpuCount, 48);
|
||||||
|
p = dl_append(detail, p, " core(s)", 48);
|
||||||
|
add(0, "Processor", detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt controllers (category 1)
|
||||||
|
add(1, "Local APIC", "");
|
||||||
|
add(1, "I/O APIC", "");
|
||||||
|
|
||||||
|
// Timer (category 2)
|
||||||
|
add(2, "LAPIC Timer", "Local APIC periodic timer");
|
||||||
|
|
||||||
|
// PS/2 Input (category 3)
|
||||||
|
add(3, "PS/2 Keyboard", "IRQ 1");
|
||||||
|
if (Drivers::PS2::IsDualChannel()) {
|
||||||
|
add(3, "PS/2 Mouse", "IRQ 12");
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB devices (category 4)
|
||||||
|
if (Drivers::USB::Xhci::IsInitialized()) {
|
||||||
|
for (uint8_t slot = 1; slot <= Drivers::USB::Xhci::MAX_SLOTS && count < maxCount; slot++) {
|
||||||
|
auto* dev = Drivers::USB::Xhci::GetDevice(slot);
|
||||||
|
if (!dev || !dev->Active) continue;
|
||||||
|
const char* devName = "USB Device";
|
||||||
|
if (dev->InterfaceClass == 3) {
|
||||||
|
if (dev->InterfaceProtocol == 1) devName = "USB HID Keyboard";
|
||||||
|
else if (dev->InterfaceProtocol == 2) devName = "USB HID Mouse";
|
||||||
|
else devName = "USB HID Device";
|
||||||
|
} else if (dev->InterfaceClass == 8) {
|
||||||
|
devName = "USB Mass Storage";
|
||||||
|
} else if (dev->InterfaceClass == 9) {
|
||||||
|
devName = "USB Hub";
|
||||||
|
}
|
||||||
|
char detail[48];
|
||||||
|
int p = 0;
|
||||||
|
p = dl_append(detail, p, "Port ", 48);
|
||||||
|
p = dl_append_dec(detail, p, dev->PortId, 48);
|
||||||
|
p = dl_append(detail, p, ", VID:", 48);
|
||||||
|
p = dl_append_hex(detail, p, dev->VendorId, 4, 48);
|
||||||
|
p = dl_append(detail, p, " PID:", 48);
|
||||||
|
p = dl_append_hex(detail, p, dev->ProductId, 4, 48);
|
||||||
|
add(4, devName, detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network (category 5)
|
||||||
|
if (Drivers::Net::E1000::IsInitialized()) {
|
||||||
|
add(5, "Intel E1000", "Gigabit Ethernet (82540EM)");
|
||||||
|
}
|
||||||
|
if (Drivers::Net::E1000E::IsInitialized()) {
|
||||||
|
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display (category 6)
|
||||||
|
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
||||||
|
auto* gpu = Drivers::Graphics::IntelGPU::GetGpuInfo();
|
||||||
|
if (gpu) {
|
||||||
|
add(6, gpu->name, "Intel Integrated Graphics");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PCI devices (category 7)
|
||||||
|
auto& pciDevs = Pci::GetDevices();
|
||||||
|
for (int i = 0; i < (int)pciDevs.size() && count < maxCount; i++) {
|
||||||
|
auto& d = pciDevs[i];
|
||||||
|
const char* className = Pci::GetClassName(d.ClassCode, d.SubClass);
|
||||||
|
char detail[48];
|
||||||
|
int p = 0;
|
||||||
|
p = dl_append_hex(detail, p, d.Bus, 2, 48);
|
||||||
|
p = dl_append(detail, p, ":", 48);
|
||||||
|
p = dl_append_hex(detail, p, d.Device, 2, 48);
|
||||||
|
p = dl_append(detail, p, ".", 48);
|
||||||
|
p = dl_append_dec(detail, p, d.Function, 48);
|
||||||
|
p = dl_append(detail, p, " ", 48);
|
||||||
|
p = dl_append_hex(detail, p, d.VendorId, 4, 48);
|
||||||
|
p = dl_append(detail, p, ":", 48);
|
||||||
|
p = dl_append_hex(detail, p, d.DeviceId, 4, 48);
|
||||||
|
add(7, className, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Filesystem.hpp
|
||||||
|
* SYS_OPEN, SYS_READ, SYS_GETSIZE, SYS_CLOSE, SYS_READDIR,
|
||||||
|
* SYS_FWRITE, SYS_FCREATE syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Fs/Vfs.hpp>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
static int Sys_Open(const char* path) {
|
||||||
|
return Fs::Vfs::VfsOpen(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||||
|
return Fs::Vfs::VfsRead(handle, buffer, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_GetSize(int handle) {
|
||||||
|
return Fs::Vfs::VfsGetSize(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_Close(int handle) {
|
||||||
|
Fs::Vfs::VfsClose(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||||
|
// Get entries from VFS into a kernel-local array
|
||||||
|
const char* kernelNames[64];
|
||||||
|
int max = maxEntries;
|
||||||
|
if (max > 64) max = 64;
|
||||||
|
int count = Fs::Vfs::VfsReadDir(path, kernelNames, max);
|
||||||
|
if (count <= 0) return count;
|
||||||
|
|
||||||
|
// Allocate a user-accessible page for string data via process heap
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return -1;
|
||||||
|
|
||||||
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (page == nullptr) return -1;
|
||||||
|
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||||
|
uint64_t userVa = proc->heapNext;
|
||||||
|
proc->heapNext += 0x1000;
|
||||||
|
Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa);
|
||||||
|
|
||||||
|
// Copy strings into the user page and write pointers to outNames
|
||||||
|
uint64_t offset = 0;
|
||||||
|
uint8_t* pageBuf = (uint8_t*)Memory::HHDM(physAddr);
|
||||||
|
int copied = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int len = Lib::strlen(kernelNames[i]) + 1;
|
||||||
|
if (offset + len > 0x1000) break;
|
||||||
|
memcpy(pageBuf + offset, kernelNames[i], len);
|
||||||
|
outNames[i] = (const char*)(userVa + offset);
|
||||||
|
offset += len;
|
||||||
|
copied++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Graphics.hpp
|
||||||
|
* SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Graphics/Cursor.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
#include "Common.hpp"
|
||||||
|
#include "../Libraries/flanterm/src/flanterm.h"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void Sys_FbInfo(FbInfo* out) {
|
||||||
|
if (out == nullptr) return;
|
||||||
|
out->width = Graphics::Cursor::GetFramebufferWidth();
|
||||||
|
out->height = Graphics::Cursor::GetFramebufferHeight();
|
||||||
|
out->pitch = Graphics::Cursor::GetFramebufferPitch();
|
||||||
|
out->bpp = 32;
|
||||||
|
out->userAddr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_FbMap() {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return 0;
|
||||||
|
|
||||||
|
uint32_t* fbBase = Graphics::Cursor::GetFramebufferBase();
|
||||||
|
if (fbBase == nullptr) return 0;
|
||||||
|
|
||||||
|
uint64_t fbPhys = Memory::SubHHDM((uint64_t)fbBase);
|
||||||
|
uint64_t fbSize = Graphics::Cursor::GetFramebufferHeight()
|
||||||
|
* Graphics::Cursor::GetFramebufferPitch();
|
||||||
|
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
|
||||||
|
|
||||||
|
Kt::KernelLogStream(Kt::INFO, "FbMap") << "fbPhys=" << kcp::hex << fbPhys
|
||||||
|
<< " size=" << kcp::dec << fbSize
|
||||||
|
<< " pages=" << numPages
|
||||||
|
<< " (" << Graphics::Cursor::GetFramebufferWidth()
|
||||||
|
<< "x" << Graphics::Cursor::GetFramebufferHeight()
|
||||||
|
<< " pitch=" << Graphics::Cursor::GetFramebufferPitch() << ")";
|
||||||
|
|
||||||
|
// Map at a fixed user VA
|
||||||
|
constexpr uint64_t userVa = 0x50000000ULL;
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < numPages; i++) {
|
||||||
|
Memory::VMM::Paging::MapUserInWC(
|
||||||
|
proc->pml4Phys,
|
||||||
|
fbPhys + i * 0x1000,
|
||||||
|
userVa + i * 0x1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userVa;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_TermSize() {
|
||||||
|
// If the process is redirected to a GUI terminal, return those dimensions
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc && proc->redirected) {
|
||||||
|
auto* target = GetRedirTarget(proc);
|
||||||
|
if (target && target->termCols > 0 && target->termRows > 0) {
|
||||||
|
return ((uint64_t)target->termRows << 32) | ((uint64_t)target->termCols & 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t cols = 0, rows = 0;
|
||||||
|
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
|
||||||
|
return (rows << 32) | (cols & 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t Sys_TermScale(uint64_t scale_x, uint64_t scale_y) {
|
||||||
|
if (scale_x == 0) {
|
||||||
|
return (int64_t)((Kt::GetFontScaleY() << 32) | (Kt::GetFontScaleX() & 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
Kt::Rescale((size_t)scale_x, (size_t)scale_y);
|
||||||
|
size_t cols = 0, rows = 0;
|
||||||
|
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
|
||||||
|
return (int64_t)((rows << 32) | (cols & 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Heap.hpp
|
||||||
|
* SYS_ALLOC, SYS_FREE syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
static uint64_t Sys_Alloc(uint64_t size) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return 0;
|
||||||
|
|
||||||
|
// Round up to page boundary
|
||||||
|
size = (size + 0xFFF) & ~0xFFFULL;
|
||||||
|
if (size == 0) size = 0x1000;
|
||||||
|
|
||||||
|
uint64_t userVa = proc->heapNext;
|
||||||
|
uint64_t numPages = size / 0x1000;
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < numPages; i++) {
|
||||||
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (page == nullptr) return 0;
|
||||||
|
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||||
|
Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa + i * 0x1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
proc->heapNext += size;
|
||||||
|
return userVa;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_Free(uint64_t) {
|
||||||
|
// TODO No-op for now (pages leak)
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Info.hpp
|
||||||
|
* SYS_GETINFO syscall
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void Sys_GetInfo(SysInfo* outInfo) {
|
||||||
|
if (outInfo == nullptr) return;
|
||||||
|
|
||||||
|
// Copy strings into fixed-size arrays (user-accessible)
|
||||||
|
const char* name = "ZenithOS";
|
||||||
|
const char* ver = "0.1.0";
|
||||||
|
for (int i = 0; name[i]; i++) outInfo->osName[i] = name[i];
|
||||||
|
outInfo->osName[8] = '\0';
|
||||||
|
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
|
||||||
|
outInfo->osVersion[5] = '\0';
|
||||||
|
|
||||||
|
outInfo->apiVersion = 2;
|
||||||
|
outInfo->maxProcesses = Sched::MaxProcesses;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* IoRedir.hpp
|
||||||
|
* SYS_SPAWN_REDIR, SYS_CHILDIO_READ, SYS_CHILDIO_WRITE,
|
||||||
|
* SYS_CHILDIO_WRITEKEY, SYS_CHILDIO_SETTERMSZ syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static int Sys_SpawnRedir(const char* path, const char* args) {
|
||||||
|
int childPid = Sched::Spawn(path, args);
|
||||||
|
if (childPid < 0) return -1;
|
||||||
|
|
||||||
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
|
if (child == nullptr) return -1;
|
||||||
|
|
||||||
|
// Allocate ring buffers
|
||||||
|
void* outPage = Memory::g_pfa->AllocateZeroed();
|
||||||
|
void* inPage = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (!outPage || !inPage) return -1;
|
||||||
|
|
||||||
|
child->outBuf = (uint8_t*)outPage;
|
||||||
|
child->inBuf = (uint8_t*)inPage;
|
||||||
|
child->outHead = 0;
|
||||||
|
child->outTail = 0;
|
||||||
|
child->inHead = 0;
|
||||||
|
child->inTail = 0;
|
||||||
|
child->keyHead = 0;
|
||||||
|
child->keyTail = 0;
|
||||||
|
child->redirected = true;
|
||||||
|
child->parentPid = Sched::GetCurrentPid();
|
||||||
|
|
||||||
|
return childPid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
|
||||||
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
|
if (child == nullptr || !child->redirected || !child->outBuf) return -1;
|
||||||
|
return RingRead(child->outBuf, child->outHead, child->outTail, Sched::Process::IoBufSize, (uint8_t*)buf, maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
|
||||||
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
|
if (child == nullptr || !child->redirected || !child->inBuf) return -1;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
RingWrite(child->inBuf, child->inHead, child->inTail, Sched::Process::IoBufSize, (uint8_t)data[i]);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
|
||||||
|
if (key == nullptr) return -1;
|
||||||
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
|
if (child == nullptr || !child->redirected) return -1;
|
||||||
|
child->keyBuf[child->keyHead] = *key;
|
||||||
|
child->keyHead = (child->keyHead + 1) % 64;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
|
||||||
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
|
if (child == nullptr || !child->redirected) return -1;
|
||||||
|
child->termCols = cols;
|
||||||
|
child->termRows = rows;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Keyboard.hpp
|
||||||
|
* SYS_ISKEYAVAILABLE, SYS_GETKEY, SYS_GETCHAR syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Drivers/PS2/Keyboard.hpp>
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
static bool Sys_IsKeyAvailable() {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc && proc->redirected) {
|
||||||
|
auto* target = GetRedirTarget(proc);
|
||||||
|
if (target) return target->keyHead != target->keyTail;
|
||||||
|
}
|
||||||
|
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_GetKey(KeyEvent* outEvent) {
|
||||||
|
if (outEvent == nullptr) return;
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc && proc->redirected) {
|
||||||
|
auto* target = GetRedirTarget(proc);
|
||||||
|
if (target) {
|
||||||
|
// Wait for key in target's keyBuf ring
|
||||||
|
while (target->keyHead == target->keyTail) {
|
||||||
|
Sched::Schedule();
|
||||||
|
}
|
||||||
|
*outEvent = target->keyBuf[target->keyTail];
|
||||||
|
target->keyTail = (target->keyTail + 1) % 64;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto k = Drivers::PS2::Keyboard::GetKey();
|
||||||
|
outEvent->scancode = k.Scancode;
|
||||||
|
outEvent->ascii = k.Ascii;
|
||||||
|
outEvent->pressed = k.Pressed;
|
||||||
|
outEvent->shift = k.Shift;
|
||||||
|
outEvent->ctrl = k.Ctrl;
|
||||||
|
outEvent->alt = k.Alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char Sys_GetChar() {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc && proc->redirected) {
|
||||||
|
auto* target = GetRedirTarget(proc);
|
||||||
|
if (target && target->inBuf) {
|
||||||
|
// Wait for data in target's inBuf ring
|
||||||
|
while (target->inTail == target->inHead) {
|
||||||
|
Sched::Schedule(); // yield until parent writes
|
||||||
|
}
|
||||||
|
uint8_t c = target->inBuf[target->inTail];
|
||||||
|
target->inTail = (target->inTail + 1) % Sched::Process::IoBufSize;
|
||||||
|
return (char)c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Drivers::PS2::Keyboard::GetChar();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* MemInfo.hpp
|
||||||
|
* SYS_MEMSTATS syscall
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void Sys_MemStats(MemStats* out) {
|
||||||
|
if (out == nullptr) return;
|
||||||
|
Memory::g_pfa->GetStats(out);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Mouse.hpp
|
||||||
|
* SYS_MOUSESTATE, SYS_SETMOUSEBOUNDS syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Drivers/PS2/Mouse.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void Sys_MouseState(MouseState* out) {
|
||||||
|
if (out == nullptr) return;
|
||||||
|
auto state = Drivers::PS2::Mouse::GetMouseState();
|
||||||
|
out->x = state.X;
|
||||||
|
out->y = state.Y;
|
||||||
|
out->scrollDelta = state.ScrollDelta;
|
||||||
|
out->buttons = state.Buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_SetMouseBounds(int32_t maxX, int32_t maxY) {
|
||||||
|
Drivers::PS2::Mouse::SetBounds(maxX, maxY);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Net.hpp
|
||||||
|
* Networking syscalls: SYS_PING, SYS_SOCKET, SYS_CONNECT, SYS_BIND,
|
||||||
|
* SYS_LISTEN, SYS_ACCEPT, SYS_SEND, SYS_RECV, SYS_CLOSESOCK,
|
||||||
|
* SYS_SENDTO, SYS_RECVFROM, SYS_GETNETCFG, SYS_SETNETCFG, SYS_RESOLVE
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Timekeeping/ApicTimer.hpp>
|
||||||
|
#include <Net/Icmp.hpp>
|
||||||
|
#include <Net/Dns.hpp>
|
||||||
|
#include <Net/Socket.hpp>
|
||||||
|
#include <Net/NetConfig.hpp>
|
||||||
|
#include <Drivers/Net/E1000.hpp>
|
||||||
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static uint16_t g_pingSeq = 0;
|
||||||
|
static constexpr uint16_t PING_ID = 0x2E01; // "ZE"
|
||||||
|
|
||||||
|
static int32_t Sys_Ping(uint32_t ipAddr, uint32_t timeoutMs) {
|
||||||
|
uint16_t seq = g_pingSeq++;
|
||||||
|
|
||||||
|
Net::Icmp::ResetReply();
|
||||||
|
Net::Icmp::SendEchoRequest(ipAddr, PING_ID, seq);
|
||||||
|
|
||||||
|
uint64_t start = Timekeeping::GetMilliseconds();
|
||||||
|
while (!Net::Icmp::HasReply(PING_ID, seq)) {
|
||||||
|
if (Timekeeping::GetMilliseconds() - start >= timeoutMs) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Sched::Schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int32_t)(Timekeeping::GetMilliseconds() - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Socket syscalls ----
|
||||||
|
|
||||||
|
static int Sys_Socket(int type) {
|
||||||
|
return Net::Socket::Create(type, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Connect(int fd, uint32_t ip, uint16_t port) {
|
||||||
|
return Net::Socket::Connect(fd, ip, port, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Bind(int fd, uint16_t port) {
|
||||||
|
return Net::Socket::Bind(fd, port, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Listen(int fd) {
|
||||||
|
return Net::Socket::Listen(fd, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Accept(int fd) {
|
||||||
|
return Net::Socket::Accept(fd, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Send(int fd, const uint8_t* data, uint32_t len) {
|
||||||
|
return Net::Socket::Send(fd, data, len, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Recv(int fd, uint8_t* buf, uint32_t maxLen) {
|
||||||
|
return Net::Socket::Recv(fd, buf, maxLen, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_CloseSock(int fd) {
|
||||||
|
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;
|
||||||
|
out->dnsServer = Net::GetDnsServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_SetNetCfg(const NetCfg* in) {
|
||||||
|
if (in == nullptr) return -1;
|
||||||
|
Net::SetIpAddress(in->ipAddress);
|
||||||
|
Net::SetSubnetMask(in->subnetMask);
|
||||||
|
Net::SetGateway(in->gateway);
|
||||||
|
Net::SetDnsServer(in->dnsServer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- DNS resolve ----
|
||||||
|
|
||||||
|
static int64_t Sys_Resolve(const char* hostname) {
|
||||||
|
uint32_t ip = Net::Dns::Resolve(hostname);
|
||||||
|
return (int64_t)ip;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Power.hpp
|
||||||
|
* SYS_RESET, SYS_SHUTDOWN syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void Sys_Reset() {
|
||||||
|
/*
|
||||||
|
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
|
||||||
|
|
||||||
|
We implement the triple fault by loading a null IDT into the IDT register,
|
||||||
|
and then immediately triggering an interrupt.
|
||||||
|
|
||||||
|
This technique should pretty much work across the board but it's of course
|
||||||
|
better to use the UEFI runtime API as it has a method for this purpose,
|
||||||
|
along with shutdown.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct [[gnu::packed]] { uint16_t limit; uint64_t base; } nullIdt = {0, 0};
|
||||||
|
asm volatile("lidt %0; int $0x03" :: "m"(nullIdt));
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Process.hpp
|
||||||
|
* SYS_EXIT, SYS_YIELD, SYS_SLEEP_MS, SYS_GETPID,
|
||||||
|
* SYS_WAITPID, SYS_SPAWN, SYS_GETARGS, SYS_PROCLIST, SYS_KILL syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Timekeeping/ApicTimer.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
#include "WinServer.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
static void Sys_Exit(int exitCode) {
|
||||||
|
(void)exitCode;
|
||||||
|
Sched::ExitProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_Yield() {
|
||||||
|
Sched::Schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_SleepMs(uint64_t ms) {
|
||||||
|
Timekeeping::Sleep(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_GetPid() {
|
||||||
|
return Sched::GetCurrentPid();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_WaitPid(int pid) {
|
||||||
|
while (Sched::IsAlive(pid)) {
|
||||||
|
Sched::Schedule(); // yield until the process exits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Spawn(const char* path, const char* args) {
|
||||||
|
auto* parent = Sched::GetCurrentProcessPtr();
|
||||||
|
int childPid = Sched::Spawn(path, args);
|
||||||
|
if (childPid < 0) return childPid;
|
||||||
|
|
||||||
|
// Inherit I/O redirection: if the parent is redirected, the child
|
||||||
|
// is marked redirected too. It stores a parentPid pointing to the
|
||||||
|
// process that owns the actual ring buffers (the one spawned via
|
||||||
|
// spawn_redir). The child does NOT get its own buffers — Sys_Print
|
||||||
|
// et al. look up the buffer owner at write time.
|
||||||
|
if (parent && parent->redirected) {
|
||||||
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
|
if (child) {
|
||||||
|
child->redirected = true;
|
||||||
|
// Point to the buffer owner: if parent owns buffers, target parent;
|
||||||
|
// if parent itself inherited, follow the chain.
|
||||||
|
child->parentPid = parent->outBuf ? parent->pid : parent->parentPid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return childPid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_GetArgs(char* buf, uint64_t maxLen) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
||||||
|
int i = 0;
|
||||||
|
for (; i < (int)maxLen - 1 && proc->args[i]; i++) {
|
||||||
|
buf[i] = proc->args[i];
|
||||||
|
}
|
||||||
|
buf[i] = '\0';
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ProcList(ProcInfo* buf, int maxCount) {
|
||||||
|
if (buf == nullptr || maxCount <= 0) return 0;
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < Sched::MaxProcesses && count < maxCount; i++) {
|
||||||
|
auto* proc = Sched::GetProcessSlot(i);
|
||||||
|
if (!proc || proc->state == Sched::ProcessState::Free) continue;
|
||||||
|
|
||||||
|
buf[count].pid = (int32_t)proc->pid;
|
||||||
|
buf[count].parentPid = (int32_t)proc->parentPid;
|
||||||
|
buf[count].state = (uint8_t)proc->state;
|
||||||
|
buf[count]._pad[0] = 0;
|
||||||
|
buf[count]._pad[1] = 0;
|
||||||
|
buf[count]._pad[2] = 0;
|
||||||
|
{
|
||||||
|
int j = 0;
|
||||||
|
for (; j < 63 && proc->name[j]; j++)
|
||||||
|
buf[count].name[j] = proc->name[j];
|
||||||
|
buf[count].name[j] = '\0';
|
||||||
|
}
|
||||||
|
buf[count].heapUsed = (proc->heapNext > Sched::UserHeapBase)
|
||||||
|
? proc->heapNext - Sched::UserHeapBase : 0;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_Kill(int pid) {
|
||||||
|
// Refuse to kill PID 0 (init)
|
||||||
|
if (pid == 0) return -1;
|
||||||
|
// Refuse to kill the caller's own process
|
||||||
|
if (pid == Sched::GetCurrentPid()) return -1;
|
||||||
|
|
||||||
|
auto* proc = Sched::GetProcessByPid(pid);
|
||||||
|
if (!proc) return -1;
|
||||||
|
|
||||||
|
// Clean up any windows owned by this process
|
||||||
|
WinServer::CleanupProcess(pid);
|
||||||
|
|
||||||
|
proc->state = Sched::ProcessState::Terminated;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Random.hpp
|
||||||
|
* SYS_GETRANDOM syscall
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
// ---- Random number generation ----
|
||||||
|
// Uses RDTSC mixed with xorshift64* PRNG for entropy.
|
||||||
|
// RDRAND is intentionally avoided: some firmware disables the RDRAND
|
||||||
|
// hardware unit while CPUID still advertises support (bit 30 of ECX),
|
||||||
|
// causing #UD on real hardware. RDTSC-based entropy is sufficient for
|
||||||
|
// seeding BearSSL's PRNG for TLS session keys.
|
||||||
|
|
||||||
|
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
|
||||||
|
uint64_t tsc;
|
||||||
|
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
||||||
|
uint64_t state = tsc;
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < len; i += 8) {
|
||||||
|
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
||||||
|
state ^= tsc;
|
||||||
|
state ^= state >> 12;
|
||||||
|
state ^= state << 25;
|
||||||
|
state ^= state >> 27;
|
||||||
|
uint64_t val = state * 0x2545F4914F6CDD1DULL;
|
||||||
|
|
||||||
|
uint64_t remaining = len - i;
|
||||||
|
uint64_t toCopy = remaining < 8 ? remaining : 8;
|
||||||
|
for (uint64_t j = 0; j < toCopy; j++)
|
||||||
|
buf[i + j] = (uint8_t)(val >> (j * 8));
|
||||||
|
}
|
||||||
|
return (int64_t)len;
|
||||||
|
}
|
||||||
|
};
|
||||||
+21
-845
@@ -5,860 +5,36 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
#include <Timekeeping/Time.hpp>
|
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <Fs/Vfs.hpp>
|
|
||||||
#include <Memory/Heap.hpp>
|
|
||||||
#include <Memory/PageFrameAllocator.hpp>
|
|
||||||
#include <Memory/Paging.hpp>
|
|
||||||
#include <Memory/HHDM.hpp>
|
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
|
||||||
#include <Sched/Scheduler.hpp>
|
|
||||||
#include <Libraries/Memory.hpp>
|
|
||||||
#include <Libraries/String.hpp>
|
|
||||||
#include <Drivers/PS2/Keyboard.hpp>
|
|
||||||
#include <Drivers/PS2/Mouse.hpp>
|
|
||||||
#include <Net/Icmp.hpp>
|
|
||||||
#include <Net/Dns.hpp>
|
|
||||||
#include <Net/Socket.hpp>
|
|
||||||
#include <Net/ByteOrder.hpp>
|
|
||||||
#include <Net/NetConfig.hpp>
|
|
||||||
#include <Drivers/Net/E1000.hpp>
|
|
||||||
#include <Drivers/Net/E1000E.hpp>
|
|
||||||
#include <Hal/MSR.hpp>
|
#include <Hal/MSR.hpp>
|
||||||
#include <Hal/GDT.hpp>
|
#include <Hal/GDT.hpp>
|
||||||
#include <Graphics/Cursor.hpp>
|
|
||||||
#include "../Libraries/flanterm/src/flanterm.h"
|
/* For common functions used by multiple syscall implementations*/
|
||||||
#include "WinServer.hpp"
|
#include "Common.hpp"
|
||||||
#include <Pci/Pci.hpp>
|
|
||||||
#include <Drivers/USB/Xhci.hpp>
|
/* Syscall impl. includes */
|
||||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
#include "Process.hpp" // SYS_EXIT, SYS_YIELD, SYS_SLEEP_MS, SYS_GETPID, SYS_WAITPID, SYS_SPAWN, SYS_GETARGS, SYS_PROCLIST, SYS_KILL
|
||||||
#include <Drivers/PS2/PS2Controller.hpp>
|
#include "Terminal.hpp" // SYS_PRINT, SYS_PUTCHAR
|
||||||
#include <Hal/Apic/ApicInit.hpp>
|
#include "Filesystem.hpp" // SYS_OPEN, SYS_READ, SYS_GETSIZE, SYS_CLOSE, SYS_READDIR, SYS_FWRITE, SYS_FCREATE
|
||||||
|
#include "Heap.hpp" // SYS_ALLOC, SYS_FREE
|
||||||
|
#include "Time.hpp" // SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME
|
||||||
|
#include "Keyboard.hpp" // SYS_ISKEYAVAILABLE, SYS_GETKEY, SYS_GETCHAR
|
||||||
|
#include "Info.hpp" // SYS_GETINFO
|
||||||
|
#include "Graphics.hpp" // SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE
|
||||||
|
#include "Net.hpp" // SYS_PING, SYS_SOCKET, SYS_CONNECT, SYS_BIND, SYS_LISTEN, SYS_ACCEPT, SYS_SEND, SYS_RECV, SYS_CLOSESOCK, SYS_SENDTO, SYS_RECVFROM, SYS_GETNETCFG, SYS_SETNETCFG, SYS_RESOLVE
|
||||||
|
#include "Power.hpp" // SYS_RESET, SYS_SHUTDOWN
|
||||||
|
#include "Mouse.hpp" // SYS_MOUSESTATE, SYS_SETMOUSEBOUNDS
|
||||||
|
#include "IoRedir.hpp" // SYS_SPAWN_REDIR, SYS_CHILDIO_READ, SYS_CHILDIO_WRITE, SYS_CHILDIO_WRITEKEY, SYS_CHILDIO_SETTERMSZ
|
||||||
|
#include "Random.hpp" // SYS_GETRANDOM
|
||||||
|
#include "MemInfo.hpp" // SYS_MEMSTATS
|
||||||
|
#include "Device.hpp" // SYS_DEVLIST
|
||||||
|
#include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETSCALE, SYS_WINGETSCALE
|
||||||
|
|
||||||
// Assembly entry point
|
// Assembly entry point
|
||||||
extern "C" void SyscallEntry();
|
extern "C" void SyscallEntry();
|
||||||
|
|
||||||
namespace Zenith {
|
namespace Zenith {
|
||||||
|
|
||||||
// ---- Syscall implementations ----
|
|
||||||
|
|
||||||
static void Sys_Exit(int exitCode) {
|
|
||||||
(void)exitCode;
|
|
||||||
Sched::ExitProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_Yield() {
|
|
||||||
Sched::Schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_SleepMs(uint64_t ms) {
|
|
||||||
Timekeeping::Sleep(ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_GetPid() {
|
|
||||||
return Sched::GetCurrentPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RingWrite(uint8_t* buf, uint32_t& head, uint32_t /*tail*/, uint32_t size, uint8_t byte) {
|
|
||||||
buf[head] = byte;
|
|
||||||
head = (head + 1) % size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int RingRead(uint8_t* buf, uint32_t& head, uint32_t& tail, uint32_t size, uint8_t* out, int maxLen) {
|
|
||||||
int count = 0;
|
|
||||||
while (tail != head && count < maxLen) {
|
|
||||||
out[count++] = buf[tail];
|
|
||||||
tail = (tail + 1) % size;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the process that owns the I/O ring buffers for a redirected process.
|
|
||||||
// If proc owns buffers itself (spawned via spawn_redir), returns proc.
|
|
||||||
// If proc inherited redirection (spawned via spawn from a redirected parent),
|
|
||||||
// follows parentPid to find the buffer owner.
|
|
||||||
static Sched::Process* GetRedirTarget(Sched::Process* proc) {
|
|
||||||
if (!proc || !proc->redirected) return nullptr;
|
|
||||||
if (proc->outBuf) return proc; // owns buffers
|
|
||||||
return Sched::GetProcessByPid(proc->parentPid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_Print(const char* text) {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc && proc->redirected) {
|
|
||||||
auto* target = GetRedirTarget(proc);
|
|
||||||
if (target && target->outBuf) {
|
|
||||||
for (int i = 0; text[i]; i++) {
|
|
||||||
RingWrite(target->outBuf, target->outHead, target->outTail, Sched::Process::IoBufSize, (uint8_t)text[i]);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kt::Print(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_Putchar(char c) {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc && proc->redirected) {
|
|
||||||
auto* target = GetRedirTarget(proc);
|
|
||||||
if (target && target->outBuf) {
|
|
||||||
RingWrite(target->outBuf, target->outHead, target->outTail, Sched::Process::IoBufSize, (uint8_t)c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kt::Putchar(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Open(const char* path) {
|
|
||||||
return Fs::Vfs::VfsOpen(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
|
||||||
return Fs::Vfs::VfsRead(handle, buffer, offset, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_GetSize(int handle) {
|
|
||||||
return Fs::Vfs::VfsGetSize(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_Close(int handle) {
|
|
||||||
Fs::Vfs::VfsClose(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_ReadDir(const char* path, const char** outNames, int maxEntries) {
|
|
||||||
// Get entries from VFS into a kernel-local array
|
|
||||||
const char* kernelNames[64];
|
|
||||||
int max = maxEntries;
|
|
||||||
if (max > 64) max = 64;
|
|
||||||
int count = Fs::Vfs::VfsReadDir(path, kernelNames, max);
|
|
||||||
if (count <= 0) return count;
|
|
||||||
|
|
||||||
// Allocate a user-accessible page for string data via process heap
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr) return -1;
|
|
||||||
|
|
||||||
void* page = Memory::g_pfa->AllocateZeroed();
|
|
||||||
if (page == nullptr) return -1;
|
|
||||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
|
||||||
uint64_t userVa = proc->heapNext;
|
|
||||||
proc->heapNext += 0x1000;
|
|
||||||
Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa);
|
|
||||||
|
|
||||||
// Copy strings into the user page and write pointers to outNames
|
|
||||||
uint64_t offset = 0;
|
|
||||||
uint8_t* pageBuf = (uint8_t*)Memory::HHDM(physAddr);
|
|
||||||
int copied = 0;
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
int len = Lib::strlen(kernelNames[i]) + 1;
|
|
||||||
if (offset + len > 0x1000) break;
|
|
||||||
memcpy(pageBuf + offset, kernelNames[i], len);
|
|
||||||
outNames[i] = (const char*)(userVa + offset);
|
|
||||||
offset += len;
|
|
||||||
copied++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_Alloc(uint64_t size) {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr) return 0;
|
|
||||||
|
|
||||||
// Round up to page boundary
|
|
||||||
size = (size + 0xFFF) & ~0xFFFULL;
|
|
||||||
if (size == 0) size = 0x1000;
|
|
||||||
|
|
||||||
uint64_t userVa = proc->heapNext;
|
|
||||||
uint64_t numPages = size / 0x1000;
|
|
||||||
|
|
||||||
for (uint64_t i = 0; i < numPages; i++) {
|
|
||||||
void* page = Memory::g_pfa->AllocateZeroed();
|
|
||||||
if (page == nullptr) return 0;
|
|
||||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
|
||||||
Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa + i * 0x1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
proc->heapNext += size;
|
|
||||||
return userVa;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_Free(uint64_t) {
|
|
||||||
// No-op for now (pages leak). Proper freeing can come later.
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_GetTicks() {
|
|
||||||
return Timekeeping::GetTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_GetMilliseconds() {
|
|
||||||
return Timekeeping::GetMilliseconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_GetInfo(SysInfo* outInfo) {
|
|
||||||
if (outInfo == nullptr) return;
|
|
||||||
|
|
||||||
// Copy strings into fixed-size arrays (user-accessible)
|
|
||||||
const char* name = "ZenithOS";
|
|
||||||
const char* ver = "0.1.0";
|
|
||||||
for (int i = 0; name[i]; i++) outInfo->osName[i] = name[i];
|
|
||||||
outInfo->osName[8] = '\0';
|
|
||||||
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
|
|
||||||
outInfo->osVersion[5] = '\0';
|
|
||||||
|
|
||||||
outInfo->apiVersion = 2;
|
|
||||||
outInfo->maxProcesses = Sched::MaxProcesses;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool Sys_IsKeyAvailable() {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc && proc->redirected) {
|
|
||||||
auto* target = GetRedirTarget(proc);
|
|
||||||
if (target) return target->keyHead != target->keyTail;
|
|
||||||
}
|
|
||||||
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_GetKey(KeyEvent* outEvent) {
|
|
||||||
if (outEvent == nullptr) return;
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc && proc->redirected) {
|
|
||||||
auto* target = GetRedirTarget(proc);
|
|
||||||
if (target) {
|
|
||||||
// Wait for key in target's keyBuf ring
|
|
||||||
while (target->keyHead == target->keyTail) {
|
|
||||||
Sched::Schedule();
|
|
||||||
}
|
|
||||||
*outEvent = target->keyBuf[target->keyTail];
|
|
||||||
target->keyTail = (target->keyTail + 1) % 64;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto k = Drivers::PS2::Keyboard::GetKey();
|
|
||||||
outEvent->scancode = k.Scancode;
|
|
||||||
outEvent->ascii = k.Ascii;
|
|
||||||
outEvent->pressed = k.Pressed;
|
|
||||||
outEvent->shift = k.Shift;
|
|
||||||
outEvent->ctrl = k.Ctrl;
|
|
||||||
outEvent->alt = k.Alt;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char Sys_GetChar() {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc && proc->redirected) {
|
|
||||||
auto* target = GetRedirTarget(proc);
|
|
||||||
if (target && target->inBuf) {
|
|
||||||
// Wait for data in target's inBuf ring
|
|
||||||
while (target->inTail == target->inHead) {
|
|
||||||
Sched::Schedule(); // yield until parent writes
|
|
||||||
}
|
|
||||||
uint8_t c = target->inBuf[target->inTail];
|
|
||||||
target->inTail = (target->inTail + 1) % Sched::Process::IoBufSize;
|
|
||||||
return (char)c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Drivers::PS2::Keyboard::GetChar();
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint16_t g_pingSeq = 0;
|
|
||||||
static constexpr uint16_t PING_ID = 0x2E01; // "ZE"
|
|
||||||
|
|
||||||
static void Sys_FbInfo(FbInfo* out) {
|
|
||||||
if (out == nullptr) return;
|
|
||||||
out->width = Graphics::Cursor::GetFramebufferWidth();
|
|
||||||
out->height = Graphics::Cursor::GetFramebufferHeight();
|
|
||||||
out->pitch = Graphics::Cursor::GetFramebufferPitch();
|
|
||||||
out->bpp = 32;
|
|
||||||
out->userAddr = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_WaitPid(int pid) {
|
|
||||||
while (Sched::IsAlive(pid)) {
|
|
||||||
Sched::Schedule(); // yield until the process exits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_FbMap() {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr) return 0;
|
|
||||||
|
|
||||||
uint32_t* fbBase = Graphics::Cursor::GetFramebufferBase();
|
|
||||||
if (fbBase == nullptr) return 0;
|
|
||||||
|
|
||||||
uint64_t fbPhys = Memory::SubHHDM((uint64_t)fbBase);
|
|
||||||
uint64_t fbSize = Graphics::Cursor::GetFramebufferHeight()
|
|
||||||
* Graphics::Cursor::GetFramebufferPitch();
|
|
||||||
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
|
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::INFO, "FbMap") << "fbPhys=" << kcp::hex << fbPhys
|
|
||||||
<< " size=" << kcp::dec << fbSize
|
|
||||||
<< " pages=" << numPages
|
|
||||||
<< " (" << Graphics::Cursor::GetFramebufferWidth()
|
|
||||||
<< "x" << Graphics::Cursor::GetFramebufferHeight()
|
|
||||||
<< " pitch=" << Graphics::Cursor::GetFramebufferPitch() << ")";
|
|
||||||
|
|
||||||
// Map at a fixed user VA
|
|
||||||
constexpr uint64_t userVa = 0x50000000ULL;
|
|
||||||
|
|
||||||
for (uint64_t i = 0; i < numPages; i++) {
|
|
||||||
Memory::VMM::Paging::MapUserInWC(
|
|
||||||
proc->pml4Phys,
|
|
||||||
fbPhys + i * 0x1000,
|
|
||||||
userVa + i * 0x1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userVa;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t Sys_Ping(uint32_t ipAddr, uint32_t timeoutMs) {
|
|
||||||
uint16_t seq = g_pingSeq++;
|
|
||||||
|
|
||||||
Net::Icmp::ResetReply();
|
|
||||||
Net::Icmp::SendEchoRequest(ipAddr, PING_ID, seq);
|
|
||||||
|
|
||||||
uint64_t start = Timekeeping::GetMilliseconds();
|
|
||||||
while (!Net::Icmp::HasReply(PING_ID, seq)) {
|
|
||||||
if (Timekeeping::GetMilliseconds() - start >= timeoutMs) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
Sched::Schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int32_t)(Timekeeping::GetMilliseconds() - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Spawn(const char* path, const char* args) {
|
|
||||||
auto* parent = Sched::GetCurrentProcessPtr();
|
|
||||||
int childPid = Sched::Spawn(path, args);
|
|
||||||
if (childPid < 0) return childPid;
|
|
||||||
|
|
||||||
// Inherit I/O redirection: if the parent is redirected, the child
|
|
||||||
// is marked redirected too. It stores a parentPid pointing to the
|
|
||||||
// process that owns the actual ring buffers (the one spawned via
|
|
||||||
// spawn_redir). The child does NOT get its own buffers — Sys_Print
|
|
||||||
// et al. look up the buffer owner at write time.
|
|
||||||
if (parent && parent->redirected) {
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
|
||||||
if (child) {
|
|
||||||
child->redirected = true;
|
|
||||||
// Point to the buffer owner: if parent owns buffers, target parent;
|
|
||||||
// if parent itself inherited, follow the chain.
|
|
||||||
child->parentPid = parent->outBuf ? parent->pid : parent->parentPid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return childPid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_GetArgs(char* buf, uint64_t maxLen) {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
|
||||||
int i = 0;
|
|
||||||
for (; i < (int)maxLen - 1 && proc->args[i]; i++) {
|
|
||||||
buf[i] = proc->args[i];
|
|
||||||
}
|
|
||||||
buf[i] = '\0';
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_TermSize() {
|
|
||||||
// If the process is redirected to a GUI terminal, return those dimensions
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc && proc->redirected) {
|
|
||||||
auto* target = GetRedirTarget(proc);
|
|
||||||
if (target && target->termCols > 0 && target->termRows > 0) {
|
|
||||||
return ((uint64_t)target->termRows << 32) | ((uint64_t)target->termCols & 0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size_t cols = 0, rows = 0;
|
|
||||||
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
|
|
||||||
return (rows << 32) | (cols & 0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_GetTime(DateTime* out) {
|
|
||||||
if (out == nullptr) return;
|
|
||||||
Timekeeping::DateTime dt = Timekeeping::GetDateTime();
|
|
||||||
out->Year = dt.Year;
|
|
||||||
out->Month = dt.Month;
|
|
||||||
out->Day = dt.Day;
|
|
||||||
out->Hour = dt.Hour;
|
|
||||||
out->Minute = dt.Minute;
|
|
||||||
out->Second = dt.Second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Socket syscalls ----
|
|
||||||
|
|
||||||
static int Sys_Socket(int type) {
|
|
||||||
return Net::Socket::Create(type, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Connect(int fd, uint32_t ip, uint16_t port) {
|
|
||||||
return Net::Socket::Connect(fd, ip, port, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Bind(int fd, uint16_t port) {
|
|
||||||
return Net::Socket::Bind(fd, port, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Listen(int fd) {
|
|
||||||
return Net::Socket::Listen(fd, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Accept(int fd) {
|
|
||||||
return Net::Socket::Accept(fd, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Send(int fd, const uint8_t* data, uint32_t len) {
|
|
||||||
return Net::Socket::Send(fd, data, len, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Recv(int fd, uint8_t* buf, uint32_t maxLen) {
|
|
||||||
return Net::Socket::Recv(fd, buf, maxLen, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_CloseSock(int fd) {
|
|
||||||
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;
|
|
||||||
out->dnsServer = Net::GetDnsServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_SetNetCfg(const NetCfg* in) {
|
|
||||||
if (in == nullptr) return -1;
|
|
||||||
Net::SetIpAddress(in->ipAddress);
|
|
||||||
Net::SetSubnetMask(in->subnetMask);
|
|
||||||
Net::SetGateway(in->gateway);
|
|
||||||
Net::SetDnsServer(in->dnsServer);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_Reset() {
|
|
||||||
/*
|
|
||||||
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
|
|
||||||
|
|
||||||
We implement the triple fault by loading a null IDT into the IDT register,
|
|
||||||
and then immediately triggering an interrupt.
|
|
||||||
|
|
||||||
This technique should pretty much work across the board but it's of course
|
|
||||||
better to use the UEFI runtime API as it has a method for this purpose,
|
|
||||||
along with shutdown.
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct [[gnu::packed]] { uint16_t limit; uint64_t base; } nullIdt = {0, 0};
|
|
||||||
asm volatile("lidt %0; int $0x03" :: "m"(nullIdt));
|
|
||||||
__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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Terminal scaling ----
|
|
||||||
|
|
||||||
static int64_t Sys_TermScale(uint64_t scale_x, uint64_t scale_y) {
|
|
||||||
if (scale_x == 0) {
|
|
||||||
return (int64_t)((Kt::GetFontScaleY() << 32) | (Kt::GetFontScaleX() & 0xFFFFFFFF));
|
|
||||||
}
|
|
||||||
Kt::Rescale((size_t)scale_x, (size_t)scale_y);
|
|
||||||
size_t cols = 0, rows = 0;
|
|
||||||
flanterm_get_dimensions(Kt::ctx, &cols, &rows);
|
|
||||||
return (int64_t)((rows << 32) | (cols & 0xFFFFFFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- DNS resolve ----
|
|
||||||
|
|
||||||
static int64_t Sys_Resolve(const char* hostname) {
|
|
||||||
uint32_t ip = Net::Dns::Resolve(hostname);
|
|
||||||
return (int64_t)ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Random number generation ----
|
|
||||||
// Uses RDTSC mixed with xorshift64* PRNG for entropy.
|
|
||||||
// RDRAND is intentionally avoided: some firmware disables the RDRAND
|
|
||||||
// hardware unit while CPUID still advertises support (bit 30 of ECX),
|
|
||||||
// causing #UD on real hardware. RDTSC-based entropy is sufficient for
|
|
||||||
// seeding BearSSL's PRNG for TLS session keys.
|
|
||||||
|
|
||||||
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
|
|
||||||
uint64_t tsc;
|
|
||||||
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
|
||||||
uint64_t state = tsc;
|
|
||||||
|
|
||||||
for (uint64_t i = 0; i < len; i += 8) {
|
|
||||||
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
|
||||||
state ^= tsc;
|
|
||||||
state ^= state >> 12;
|
|
||||||
state ^= state << 25;
|
|
||||||
state ^= state >> 27;
|
|
||||||
uint64_t val = state * 0x2545F4914F6CDD1DULL;
|
|
||||||
|
|
||||||
uint64_t remaining = len - i;
|
|
||||||
uint64_t toCopy = remaining < 8 ? remaining : 8;
|
|
||||||
for (uint64_t j = 0; j < toCopy; j++)
|
|
||||||
buf[i + j] = (uint8_t)(val >> (j * 8));
|
|
||||||
}
|
|
||||||
return (int64_t)len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Mouse syscalls ----
|
|
||||||
|
|
||||||
static void Sys_MouseState(MouseState* out) {
|
|
||||||
if (out == nullptr) return;
|
|
||||||
auto state = Drivers::PS2::Mouse::GetMouseState();
|
|
||||||
out->x = state.X;
|
|
||||||
out->y = state.Y;
|
|
||||||
out->scrollDelta = state.ScrollDelta;
|
|
||||||
out->buttons = state.Buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Sys_SetMouseBounds(int32_t maxX, int32_t maxY) {
|
|
||||||
Drivers::PS2::Mouse::SetBounds(maxX, maxY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- I/O redirection syscalls ----
|
|
||||||
|
|
||||||
static int Sys_SpawnRedir(const char* path, const char* args) {
|
|
||||||
int childPid = Sched::Spawn(path, args);
|
|
||||||
if (childPid < 0) return -1;
|
|
||||||
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
|
||||||
if (child == nullptr) return -1;
|
|
||||||
|
|
||||||
// Allocate ring buffers
|
|
||||||
void* outPage = Memory::g_pfa->AllocateZeroed();
|
|
||||||
void* inPage = Memory::g_pfa->AllocateZeroed();
|
|
||||||
if (!outPage || !inPage) return -1;
|
|
||||||
|
|
||||||
child->outBuf = (uint8_t*)outPage;
|
|
||||||
child->inBuf = (uint8_t*)inPage;
|
|
||||||
child->outHead = 0;
|
|
||||||
child->outTail = 0;
|
|
||||||
child->inHead = 0;
|
|
||||||
child->inTail = 0;
|
|
||||||
child->keyHead = 0;
|
|
||||||
child->keyTail = 0;
|
|
||||||
child->redirected = true;
|
|
||||||
child->parentPid = Sched::GetCurrentPid();
|
|
||||||
|
|
||||||
return childPid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
|
||||||
if (child == nullptr || !child->redirected || !child->outBuf) return -1;
|
|
||||||
return RingRead(child->outBuf, child->outHead, child->outTail, Sched::Process::IoBufSize, (uint8_t*)buf, maxLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
|
||||||
if (child == nullptr || !child->redirected || !child->inBuf) return -1;
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
RingWrite(child->inBuf, child->inHead, child->inTail, Sched::Process::IoBufSize, (uint8_t)data[i]);
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
|
|
||||||
if (key == nullptr) return -1;
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
|
||||||
if (child == nullptr || !child->redirected) return -1;
|
|
||||||
child->keyBuf[child->keyHead] = *key;
|
|
||||||
child->keyHead = (child->keyHead + 1) % 64;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
|
||||||
if (child == nullptr || !child->redirected) return -1;
|
|
||||||
child->termCols = cols;
|
|
||||||
child->termRows = rows;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Process listing / kill ----
|
|
||||||
|
|
||||||
static int Sys_ProcList(ProcInfo* buf, int maxCount) {
|
|
||||||
if (buf == nullptr || maxCount <= 0) return 0;
|
|
||||||
int count = 0;
|
|
||||||
for (int i = 0; i < Sched::MaxProcesses && count < maxCount; i++) {
|
|
||||||
auto* proc = Sched::GetProcessSlot(i);
|
|
||||||
if (!proc || proc->state == Sched::ProcessState::Free) continue;
|
|
||||||
|
|
||||||
buf[count].pid = (int32_t)proc->pid;
|
|
||||||
buf[count].parentPid = (int32_t)proc->parentPid;
|
|
||||||
buf[count].state = (uint8_t)proc->state;
|
|
||||||
buf[count]._pad[0] = 0;
|
|
||||||
buf[count]._pad[1] = 0;
|
|
||||||
buf[count]._pad[2] = 0;
|
|
||||||
{
|
|
||||||
int j = 0;
|
|
||||||
for (; j < 63 && proc->name[j]; j++)
|
|
||||||
buf[count].name[j] = proc->name[j];
|
|
||||||
buf[count].name[j] = '\0';
|
|
||||||
}
|
|
||||||
buf[count].heapUsed = (proc->heapNext > Sched::UserHeapBase)
|
|
||||||
? proc->heapNext - Sched::UserHeapBase : 0;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_Kill(int pid) {
|
|
||||||
// Refuse to kill PID 0 (init)
|
|
||||||
if (pid == 0) return -1;
|
|
||||||
// Refuse to kill the caller's own process
|
|
||||||
if (pid == Sched::GetCurrentPid()) return -1;
|
|
||||||
|
|
||||||
auto* proc = Sched::GetProcessByPid(pid);
|
|
||||||
if (!proc) return -1;
|
|
||||||
|
|
||||||
// Clean up any windows owned by this process
|
|
||||||
WinServer::CleanupProcess(pid);
|
|
||||||
|
|
||||||
proc->state = Sched::ProcessState::Terminated;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Device list ----
|
|
||||||
|
|
||||||
static void dl_strcpy(char* dst, const char* src, int max) {
|
|
||||||
int i = 0;
|
|
||||||
for (; i < max - 1 && src[i]; i++) dst[i] = src[i];
|
|
||||||
dst[i] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dl_append(char* dst, int pos, const char* src, int max) {
|
|
||||||
for (int i = 0; src[i] && pos < max - 1; i++) dst[pos++] = src[i];
|
|
||||||
dst[pos] = '\0';
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dl_append_hex(char* dst, int pos, unsigned val, int digits, int max) {
|
|
||||||
const char* hex = "0123456789abcdef";
|
|
||||||
char tmp[8];
|
|
||||||
for (int i = digits - 1; i >= 0; i--) { tmp[i] = hex[val & 0xF]; val >>= 4; }
|
|
||||||
for (int i = 0; i < digits && pos < max - 1; i++) dst[pos++] = tmp[i];
|
|
||||||
dst[pos] = '\0';
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dl_append_dec(char* dst, int pos, int val, int max) {
|
|
||||||
if (val == 0) { if (pos < max - 1) dst[pos++] = '0'; dst[pos] = '\0'; return pos; }
|
|
||||||
char tmp[12]; int i = 0;
|
|
||||||
while (val > 0) { tmp[i++] = '0' + (val % 10); val /= 10; }
|
|
||||||
while (i > 0 && pos < max - 1) dst[pos++] = tmp[--i];
|
|
||||||
dst[pos] = '\0';
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_DevList(DevInfo* buf, int maxCount) {
|
|
||||||
if (buf == nullptr || maxCount <= 0) return 0;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
auto add = [&](uint8_t cat, const char* name, const char* detail) {
|
|
||||||
if (count >= maxCount) return;
|
|
||||||
buf[count].category = cat;
|
|
||||||
buf[count]._pad[0] = 0; buf[count]._pad[1] = 0; buf[count]._pad[2] = 0;
|
|
||||||
dl_strcpy(buf[count].name, name, 48);
|
|
||||||
dl_strcpy(buf[count].detail, detail, 48);
|
|
||||||
count++;
|
|
||||||
};
|
|
||||||
|
|
||||||
// CPU cores (category 0)
|
|
||||||
int cpuCount = Hal::GetDetectedCpuCount();
|
|
||||||
if (cpuCount > 0) {
|
|
||||||
char detail[48];
|
|
||||||
int p = 0;
|
|
||||||
p = dl_append(detail, p, "x86_64, ", 48);
|
|
||||||
p = dl_append_dec(detail, p, cpuCount, 48);
|
|
||||||
p = dl_append(detail, p, " core(s)", 48);
|
|
||||||
add(0, "Processor", detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt controllers (category 1)
|
|
||||||
add(1, "Local APIC", "Per-CPU interrupt controller");
|
|
||||||
add(1, "I/O APIC", "System interrupt router");
|
|
||||||
|
|
||||||
// Timer (category 2)
|
|
||||||
add(2, "LAPIC Timer", "Local APIC periodic timer");
|
|
||||||
|
|
||||||
// PS/2 Input (category 3)
|
|
||||||
add(3, "PS/2 Keyboard", "IRQ 1, scan code set 1");
|
|
||||||
if (Drivers::PS2::IsDualChannel()) {
|
|
||||||
add(3, "PS/2 Mouse", "IRQ 12, dual-channel 8042");
|
|
||||||
}
|
|
||||||
|
|
||||||
// USB devices (category 4)
|
|
||||||
if (Drivers::USB::Xhci::IsInitialized()) {
|
|
||||||
for (uint8_t slot = 1; slot <= Drivers::USB::Xhci::MAX_SLOTS && count < maxCount; slot++) {
|
|
||||||
auto* dev = Drivers::USB::Xhci::GetDevice(slot);
|
|
||||||
if (!dev || !dev->Active) continue;
|
|
||||||
const char* devName = "USB Device";
|
|
||||||
if (dev->InterfaceClass == 3) {
|
|
||||||
if (dev->InterfaceProtocol == 1) devName = "USB HID Keyboard";
|
|
||||||
else if (dev->InterfaceProtocol == 2) devName = "USB HID Mouse";
|
|
||||||
else devName = "USB HID Device";
|
|
||||||
} else if (dev->InterfaceClass == 8) {
|
|
||||||
devName = "USB Mass Storage";
|
|
||||||
} else if (dev->InterfaceClass == 9) {
|
|
||||||
devName = "USB Hub";
|
|
||||||
}
|
|
||||||
char detail[48];
|
|
||||||
int p = 0;
|
|
||||||
p = dl_append(detail, p, "Port ", 48);
|
|
||||||
p = dl_append_dec(detail, p, dev->PortId, 48);
|
|
||||||
p = dl_append(detail, p, ", VID:", 48);
|
|
||||||
p = dl_append_hex(detail, p, dev->VendorId, 4, 48);
|
|
||||||
p = dl_append(detail, p, " PID:", 48);
|
|
||||||
p = dl_append_hex(detail, p, dev->ProductId, 4, 48);
|
|
||||||
add(4, devName, detail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Network (category 5)
|
|
||||||
if (Drivers::Net::E1000::IsInitialized()) {
|
|
||||||
add(5, "Intel E1000", "Gigabit Ethernet (82540EM)");
|
|
||||||
}
|
|
||||||
if (Drivers::Net::E1000E::IsInitialized()) {
|
|
||||||
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display (category 6)
|
|
||||||
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
|
||||||
auto* gpu = Drivers::Graphics::IntelGPU::GetGpuInfo();
|
|
||||||
if (gpu) {
|
|
||||||
add(6, gpu->name, "Intel Integrated Graphics");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PCI devices (category 7)
|
|
||||||
auto& pciDevs = Pci::GetDevices();
|
|
||||||
for (int i = 0; i < (int)pciDevs.size() && count < maxCount; i++) {
|
|
||||||
auto& d = pciDevs[i];
|
|
||||||
const char* className = Pci::GetClassName(d.ClassCode, d.SubClass);
|
|
||||||
char detail[48];
|
|
||||||
int p = 0;
|
|
||||||
p = dl_append_hex(detail, p, d.Bus, 2, 48);
|
|
||||||
p = dl_append(detail, p, ":", 48);
|
|
||||||
p = dl_append_hex(detail, p, d.Device, 2, 48);
|
|
||||||
p = dl_append(detail, p, ".", 48);
|
|
||||||
p = dl_append_dec(detail, p, d.Function, 48);
|
|
||||||
p = dl_append(detail, p, " ", 48);
|
|
||||||
p = dl_append_hex(detail, p, d.VendorId, 4, 48);
|
|
||||||
p = dl_append(detail, p, ":", 48);
|
|
||||||
p = dl_append_hex(detail, p, d.DeviceId, 4, 48);
|
|
||||||
add(7, className, detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Kernel introspection syscalls ----
|
|
||||||
|
|
||||||
static void Sys_MemStats(MemStats* out) {
|
|
||||||
if (out == nullptr) return;
|
|
||||||
Memory::g_pfa->GetStats(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Window scale syscalls ----
|
|
||||||
|
|
||||||
static int Sys_WinSetScale(int scale) {
|
|
||||||
return WinServer::SetScale(scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_WinGetScale() {
|
|
||||||
return WinServer::GetScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Window server syscalls ----
|
|
||||||
|
|
||||||
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
|
|
||||||
if (result == nullptr || title == nullptr) return -1;
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr) return -1;
|
|
||||||
|
|
||||||
uint64_t outVa = 0;
|
|
||||||
int id = WinServer::Create(proc->pid, proc->pml4Phys, title, w, h,
|
|
||||||
proc->heapNext, outVa);
|
|
||||||
result->id = id;
|
|
||||||
result->pixelVa = (id >= 0) ? outVa : 0;
|
|
||||||
return id >= 0 ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_WinDestroy(int windowId) {
|
|
||||||
return WinServer::Destroy(windowId, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_WinPresent(int windowId) {
|
|
||||||
return WinServer::Present(windowId, Sched::GetCurrentPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_WinPoll(int windowId, WinEvent* outEvent) {
|
|
||||||
if (outEvent == nullptr) return -1;
|
|
||||||
return WinServer::Poll(windowId, Sched::GetCurrentPid(), outEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_WinEnum(WinInfo* outArray, int maxCount) {
|
|
||||||
if (outArray == nullptr || maxCount <= 0) return 0;
|
|
||||||
return WinServer::Enumerate(outArray, maxCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_WinMap(int windowId) {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr) return 0;
|
|
||||||
return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
|
|
||||||
if (event == nullptr) return -1;
|
|
||||||
return WinServer::SendEvent(windowId, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t Sys_WinResize(int windowId, int newW, int newH) {
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
|
||||||
if (proc == nullptr) return 0;
|
|
||||||
uint64_t outVa = 0;
|
|
||||||
int r = WinServer::Resize(windowId, proc->pid, proc->pml4Phys, newW, newH,
|
|
||||||
proc->heapNext, outVa);
|
|
||||||
return (r == 0) ? outVa : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Dispatch ----
|
// ---- Dispatch ----
|
||||||
|
|
||||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||||
|
|||||||
+71
-22
@@ -11,35 +11,69 @@
|
|||||||
namespace Zenith {
|
namespace Zenith {
|
||||||
|
|
||||||
// Syscall numbers
|
// Syscall numbers
|
||||||
|
|
||||||
|
/* Process.hpp */
|
||||||
static constexpr uint64_t SYS_EXIT = 0;
|
static constexpr uint64_t SYS_EXIT = 0;
|
||||||
static constexpr uint64_t SYS_YIELD = 1;
|
static constexpr uint64_t SYS_YIELD = 1;
|
||||||
static constexpr uint64_t SYS_SLEEP_MS = 2;
|
static constexpr uint64_t SYS_SLEEP_MS = 2;
|
||||||
static constexpr uint64_t SYS_GETPID = 3;
|
static constexpr uint64_t SYS_GETPID = 3;
|
||||||
|
|
||||||
|
/* Terminal.hpp */
|
||||||
static constexpr uint64_t SYS_PRINT = 4;
|
static constexpr uint64_t SYS_PRINT = 4;
|
||||||
static constexpr uint64_t SYS_PUTCHAR = 5;
|
static constexpr uint64_t SYS_PUTCHAR = 5;
|
||||||
|
|
||||||
|
/* Filesystem.hpp */
|
||||||
static constexpr uint64_t SYS_OPEN = 6;
|
static constexpr uint64_t SYS_OPEN = 6;
|
||||||
static constexpr uint64_t SYS_READ = 7;
|
static constexpr uint64_t SYS_READ = 7;
|
||||||
static constexpr uint64_t SYS_GETSIZE = 8;
|
static constexpr uint64_t SYS_GETSIZE = 8;
|
||||||
static constexpr uint64_t SYS_CLOSE = 9;
|
static constexpr uint64_t SYS_CLOSE = 9;
|
||||||
static constexpr uint64_t SYS_READDIR = 10;
|
static constexpr uint64_t SYS_READDIR = 10;
|
||||||
|
|
||||||
|
/* Heap.hpp */
|
||||||
static constexpr uint64_t SYS_ALLOC = 11;
|
static constexpr uint64_t SYS_ALLOC = 11;
|
||||||
static constexpr uint64_t SYS_FREE = 12;
|
static constexpr uint64_t SYS_FREE = 12;
|
||||||
|
|
||||||
|
/* Time.hpp */
|
||||||
static constexpr uint64_t SYS_GETTICKS = 13;
|
static constexpr uint64_t SYS_GETTICKS = 13;
|
||||||
static constexpr uint64_t SYS_GETMILLISECONDS = 14;
|
static constexpr uint64_t SYS_GETMILLISECONDS = 14;
|
||||||
|
|
||||||
|
/* Info.hpp */
|
||||||
static constexpr uint64_t SYS_GETINFO = 15;
|
static constexpr uint64_t SYS_GETINFO = 15;
|
||||||
|
|
||||||
|
/* Keyboard.hpp */
|
||||||
static constexpr uint64_t SYS_ISKEYAVAILABLE = 16;
|
static constexpr uint64_t SYS_ISKEYAVAILABLE = 16;
|
||||||
static constexpr uint64_t SYS_GETKEY = 17;
|
static constexpr uint64_t SYS_GETKEY = 17;
|
||||||
static constexpr uint64_t SYS_GETCHAR = 18;
|
static constexpr uint64_t SYS_GETCHAR = 18;
|
||||||
|
|
||||||
|
|
||||||
|
/* Net.hpp */
|
||||||
static constexpr uint64_t SYS_PING = 19;
|
static constexpr uint64_t SYS_PING = 19;
|
||||||
|
|
||||||
|
|
||||||
|
/* Process.hpp */
|
||||||
static constexpr uint64_t SYS_SPAWN = 20;
|
static constexpr uint64_t SYS_SPAWN = 20;
|
||||||
|
|
||||||
|
/* Graphics.hpp */
|
||||||
static constexpr uint64_t SYS_FBINFO = 21;
|
static constexpr uint64_t SYS_FBINFO = 21;
|
||||||
static constexpr uint64_t SYS_FBMAP = 22;
|
static constexpr uint64_t SYS_FBMAP = 22;
|
||||||
|
|
||||||
|
/* Process.hpp */
|
||||||
static constexpr uint64_t SYS_WAITPID = 23;
|
static constexpr uint64_t SYS_WAITPID = 23;
|
||||||
|
|
||||||
|
/* Graphics.hpp */
|
||||||
static constexpr uint64_t SYS_TERMSIZE = 24;
|
static constexpr uint64_t SYS_TERMSIZE = 24;
|
||||||
|
|
||||||
|
/* Process.hpp */
|
||||||
static constexpr uint64_t SYS_GETARGS = 25;
|
static constexpr uint64_t SYS_GETARGS = 25;
|
||||||
|
|
||||||
|
/* Power.hpp */
|
||||||
static constexpr uint64_t SYS_RESET = 26;
|
static constexpr uint64_t SYS_RESET = 26;
|
||||||
static constexpr uint64_t SYS_SHUTDOWN = 27;
|
static constexpr uint64_t SYS_SHUTDOWN = 27;
|
||||||
|
|
||||||
|
/* Time.hpp */
|
||||||
static constexpr uint64_t SYS_GETTIME = 28;
|
static constexpr uint64_t SYS_GETTIME = 28;
|
||||||
|
|
||||||
|
/* Net.hpp */
|
||||||
static constexpr uint64_t SYS_SOCKET = 29;
|
static constexpr uint64_t SYS_SOCKET = 29;
|
||||||
static constexpr uint64_t SYS_CONNECT = 30;
|
static constexpr uint64_t SYS_CONNECT = 30;
|
||||||
static constexpr uint64_t SYS_BIND = 31;
|
static constexpr uint64_t SYS_BIND = 31;
|
||||||
@@ -48,25 +82,38 @@ 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_GETNETCFG = 37;
|
||||||
static constexpr uint64_t SYS_SETNETCFG = 38;
|
static constexpr uint64_t SYS_SETNETCFG = 38;
|
||||||
static constexpr uint64_t SYS_SENDTO = 39;
|
static constexpr uint64_t SYS_SENDTO = 39;
|
||||||
static constexpr uint64_t SYS_RECVFROM = 40;
|
static constexpr uint64_t SYS_RECVFROM = 40;
|
||||||
static constexpr uint64_t SYS_FWRITE = 41;
|
|
||||||
static constexpr uint64_t SYS_FCREATE = 42;
|
/* Filesystem.hpp */
|
||||||
static constexpr uint64_t SYS_TERMSCALE = 43;
|
static constexpr uint64_t SYS_FWRITE = 41;
|
||||||
static constexpr uint64_t SYS_RESOLVE = 44;
|
static constexpr uint64_t SYS_FCREATE = 42;
|
||||||
static constexpr uint64_t SYS_GETRANDOM = 45;
|
|
||||||
|
/* Graphics.hpp */
|
||||||
|
static constexpr uint64_t SYS_TERMSCALE = 43;
|
||||||
|
|
||||||
|
/* Net.hpp */
|
||||||
|
static constexpr uint64_t SYS_RESOLVE = 44;
|
||||||
|
|
||||||
|
/* Random.hpp */
|
||||||
|
static constexpr uint64_t SYS_GETRANDOM = 45;
|
||||||
|
|
||||||
static constexpr uint64_t SYS_KLOG = 46;
|
static constexpr uint64_t SYS_KLOG = 46;
|
||||||
static constexpr uint64_t SYS_MOUSESTATE = 47;
|
|
||||||
static constexpr uint64_t SYS_SETMOUSEBOUNDS = 48;
|
/* Mouse.hpp */
|
||||||
static constexpr uint64_t SYS_SPAWN_REDIR = 49;
|
static constexpr uint64_t SYS_MOUSESTATE = 47;
|
||||||
static constexpr uint64_t SYS_CHILDIO_READ = 50;
|
static constexpr uint64_t SYS_SETMOUSEBOUNDS = 48;
|
||||||
static constexpr uint64_t SYS_CHILDIO_WRITE = 51;
|
|
||||||
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
|
/* IoRedir.hpp */
|
||||||
|
static constexpr uint64_t SYS_SPAWN_REDIR = 49;
|
||||||
|
static constexpr uint64_t SYS_CHILDIO_READ = 50;
|
||||||
|
static constexpr uint64_t SYS_CHILDIO_WRITE = 51;
|
||||||
|
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
|
||||||
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
|
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
|
||||||
|
|
||||||
// Window server syscalls
|
/* Window.hpp */
|
||||||
static constexpr uint64_t SYS_WINCREATE = 54;
|
static constexpr uint64_t SYS_WINCREATE = 54;
|
||||||
static constexpr uint64_t SYS_WINDESTROY = 55;
|
static constexpr uint64_t SYS_WINDESTROY = 55;
|
||||||
static constexpr uint64_t SYS_WINPRESENT = 56;
|
static constexpr uint64_t SYS_WINPRESENT = 56;
|
||||||
@@ -74,16 +121,18 @@ namespace Zenith {
|
|||||||
static constexpr uint64_t SYS_WINENUM = 58;
|
static constexpr uint64_t SYS_WINENUM = 58;
|
||||||
static constexpr uint64_t SYS_WINMAP = 59;
|
static constexpr uint64_t SYS_WINMAP = 59;
|
||||||
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
||||||
static constexpr uint64_t SYS_WINRESIZE = 64;
|
static constexpr uint64_t SYS_WINRESIZE = 64;
|
||||||
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
||||||
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
||||||
|
|
||||||
// Process management syscalls
|
/* Process.hpp */
|
||||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||||
static constexpr uint64_t SYS_KILL = 62;
|
static constexpr uint64_t SYS_KILL = 62;
|
||||||
|
|
||||||
|
/* Device.hpp */
|
||||||
static constexpr uint64_t SYS_DEVLIST = 63;
|
static constexpr uint64_t SYS_DEVLIST = 63;
|
||||||
|
|
||||||
// Kernel introspection syscalls
|
/* MemInfo.hpp */
|
||||||
static constexpr uint64_t SYS_MEMSTATS = 67;
|
static constexpr uint64_t SYS_MEMSTATS = 67;
|
||||||
|
|
||||||
static constexpr int SOCK_TCP = 1;
|
static constexpr int SOCK_TCP = 1;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Terminal.hpp
|
||||||
|
* SYS_PRINT, SYS_PUTCHAR syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static void Sys_Print(const char* text) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc && proc->redirected) {
|
||||||
|
auto* target = GetRedirTarget(proc);
|
||||||
|
if (target && target->outBuf) {
|
||||||
|
for (int i = 0; text[i]; i++) {
|
||||||
|
RingWrite(target->outBuf, target->outHead, target->outTail, Sched::Process::IoBufSize, (uint8_t)text[i]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kt::Print(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_Putchar(char c) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc && proc->redirected) {
|
||||||
|
auto* target = GetRedirTarget(proc);
|
||||||
|
if (target && target->outBuf) {
|
||||||
|
RingWrite(target->outBuf, target->outHead, target->outTail, Sched::Process::IoBufSize, (uint8_t)c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kt::Putchar(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Time.hpp
|
||||||
|
* SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Timekeeping/ApicTimer.hpp>
|
||||||
|
#include <Timekeeping/Time.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
static uint64_t Sys_GetTicks() {
|
||||||
|
return Timekeeping::GetTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_GetMilliseconds() {
|
||||||
|
return Timekeeping::GetMilliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sys_GetTime(DateTime* out) {
|
||||||
|
if (out == nullptr) return;
|
||||||
|
Timekeeping::DateTime dt = Timekeeping::GetDateTime();
|
||||||
|
out->Year = dt.Year;
|
||||||
|
out->Month = dt.Month;
|
||||||
|
out->Day = dt.Day;
|
||||||
|
out->Hour = dt.Hour;
|
||||||
|
out->Minute = dt.Minute;
|
||||||
|
out->Second = dt.Second;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Window.hpp
|
||||||
|
* SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL,
|
||||||
|
* SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE,
|
||||||
|
* SYS_WINSETSCALE, SYS_WINGETSCALE syscalls
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
#include "WinServer.hpp"
|
||||||
|
|
||||||
|
namespace Zenith {
|
||||||
|
|
||||||
|
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
|
||||||
|
if (result == nullptr || title == nullptr) return -1;
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return -1;
|
||||||
|
|
||||||
|
uint64_t outVa = 0;
|
||||||
|
int id = WinServer::Create(proc->pid, proc->pml4Phys, title, w, h,
|
||||||
|
proc->heapNext, outVa);
|
||||||
|
result->id = id;
|
||||||
|
result->pixelVa = (id >= 0) ? outVa : 0;
|
||||||
|
return id >= 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinDestroy(int windowId) {
|
||||||
|
return WinServer::Destroy(windowId, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_WinPresent(int windowId) {
|
||||||
|
return WinServer::Present(windowId, Sched::GetCurrentPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinPoll(int windowId, WinEvent* outEvent) {
|
||||||
|
if (outEvent == nullptr) return -1;
|
||||||
|
return WinServer::Poll(windowId, Sched::GetCurrentPid(), outEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinEnum(WinInfo* outArray, int maxCount) {
|
||||||
|
if (outArray == nullptr || maxCount <= 0) return 0;
|
||||||
|
return WinServer::Enumerate(outArray, maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_WinMap(int windowId) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return 0;
|
||||||
|
return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
|
||||||
|
if (event == nullptr) return -1;
|
||||||
|
return WinServer::SendEvent(windowId, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t Sys_WinResize(int windowId, int newW, int newH) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr) return 0;
|
||||||
|
uint64_t outVa = 0;
|
||||||
|
int r = WinServer::Resize(windowId, proc->pid, proc->pml4Phys, newW, newH,
|
||||||
|
proc->heapNext, outVa);
|
||||||
|
return (r == 0) ? outVa : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinSetScale(int scale) {
|
||||||
|
return WinServer::SetScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinGetScale() {
|
||||||
|
return WinServer::GetScale();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace Sched {
|
namespace Sched {
|
||||||
|
|
||||||
static constexpr int MaxProcesses = 16;
|
static constexpr int MaxProcesses = 256;
|
||||||
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per process
|
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per process
|
||||||
static constexpr uint64_t StackSize = StackPages * 0x1000;
|
static constexpr uint64_t StackSize = StackPages * 0x1000;
|
||||||
static constexpr uint64_t UserStackPages = 4; // 16 KiB user stack
|
static constexpr uint64_t UserStackPages = 4; // 16 KiB user stack
|
||||||
|
|||||||
+23
-2
@@ -3,5 +3,26 @@
|
|||||||
ZenithOS legal/copyright information
|
ZenithOS legal/copyright information
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Copyright (c) 2025-2026 Daniel Hammer, et al.
|
Copyright (c) 2025-2026 Daniel Hammer, et al. All rights reserved.
|
||||||
(including contributors to other projects, i.e. The Limine Bootloader. Please refer to any other project's own license.)
|
(includes contributors to other projects, i.e. The Limine Bootloader. Please refer to any other project's own license.)
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
== License for the Limine C++ template (certain portions derive therefrom) ==
|
||||||
|
Copyright (C) 2023-2026 Mintsuki and contributors.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user