feat: TrueType (TTF) font rendering, many new desktop applications and DOOM support, among other improvements
This commit is contained in:
+250
-1
@@ -29,6 +29,12 @@
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
#include "../Libraries/flanterm/src/flanterm.h"
|
||||
#include "WinServer.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Hal/Apic/ApicInit.hpp>
|
||||
|
||||
// Assembly entry point
|
||||
extern "C" void SyscallEntry();
|
||||
@@ -605,6 +611,228 @@ namespace Zenith {
|
||||
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;
|
||||
}
|
||||
|
||||
// ---- 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 int 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);
|
||||
}
|
||||
|
||||
// ---- Dispatch ----
|
||||
|
||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||
@@ -743,6 +971,27 @@ namespace Zenith {
|
||||
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEvent*)frame->arg2);
|
||||
case SYS_CHILDIO_SETTERMSZ:
|
||||
return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
||||
case SYS_WINCREATE:
|
||||
return (int64_t)Sys_WinCreate((const char*)frame->arg1, (int)frame->arg2,
|
||||
(int)frame->arg3, (WinCreateResult*)frame->arg4);
|
||||
case SYS_WINDESTROY:
|
||||
return (int64_t)Sys_WinDestroy((int)frame->arg1);
|
||||
case SYS_WINPRESENT:
|
||||
return (int64_t)Sys_WinPresent((int)frame->arg1);
|
||||
case SYS_WINPOLL:
|
||||
return (int64_t)Sys_WinPoll((int)frame->arg1, (WinEvent*)frame->arg2);
|
||||
case SYS_WINENUM:
|
||||
return (int64_t)Sys_WinEnum((WinInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_WINMAP:
|
||||
return (int64_t)Sys_WinMap((int)frame->arg1);
|
||||
case SYS_WINSENDEVENT:
|
||||
return (int64_t)Sys_WinSendEvent((int)frame->arg1, (const WinEvent*)frame->arg2);
|
||||
case SYS_PROCLIST:
|
||||
return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_KILL:
|
||||
return (int64_t)Sys_Kill((int)frame->arg1);
|
||||
case SYS_DEVLIST:
|
||||
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -769,7 +1018,7 @@ namespace Zenith {
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 53 syscalls)";
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 64 syscalls)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,6 +66,20 @@ namespace Zenith {
|
||||
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
|
||||
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
|
||||
|
||||
// Window server syscalls
|
||||
static constexpr uint64_t SYS_WINCREATE = 54;
|
||||
static constexpr uint64_t SYS_WINDESTROY = 55;
|
||||
static constexpr uint64_t SYS_WINPRESENT = 56;
|
||||
static constexpr uint64_t SYS_WINPOLL = 57;
|
||||
static constexpr uint64_t SYS_WINENUM = 58;
|
||||
static constexpr uint64_t SYS_WINMAP = 59;
|
||||
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
||||
|
||||
// Process management syscalls
|
||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||
static constexpr uint64_t SYS_KILL = 62;
|
||||
static constexpr uint64_t SYS_DEVLIST = 63;
|
||||
|
||||
static constexpr int SOCK_TCP = 1;
|
||||
static constexpr int SOCK_UDP = 2;
|
||||
|
||||
@@ -118,6 +132,48 @@ namespace Zenith {
|
||||
uint8_t buttons;
|
||||
};
|
||||
|
||||
// Window server shared types
|
||||
struct WinEvent {
|
||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close
|
||||
uint8_t _pad[3];
|
||||
union {
|
||||
KeyEvent key;
|
||||
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
||||
struct { int32_t w, h; } resize;
|
||||
};
|
||||
};
|
||||
|
||||
struct WinInfo {
|
||||
int32_t id;
|
||||
int32_t ownerPid;
|
||||
char title[64];
|
||||
int32_t width, height;
|
||||
uint8_t dirty;
|
||||
uint8_t _pad[3];
|
||||
};
|
||||
|
||||
struct WinCreateResult {
|
||||
int32_t id; // -1 on failure
|
||||
uint32_t _pad;
|
||||
uint64_t pixelVa; // VA of pixel buffer in caller's address space
|
||||
};
|
||||
|
||||
struct DevInfo {
|
||||
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI
|
||||
uint8_t _pad[3];
|
||||
char name[48];
|
||||
char detail[48];
|
||||
};
|
||||
|
||||
struct ProcInfo {
|
||||
int32_t pid;
|
||||
int32_t parentPid;
|
||||
uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated
|
||||
uint8_t _pad[3];
|
||||
char name[64];
|
||||
uint64_t heapUsed; // heapNext - UserHeapBase (bytes)
|
||||
};
|
||||
|
||||
// Stack frame pushed by SyscallEntry.asm
|
||||
struct SyscallFrame {
|
||||
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* WinServer.cpp
|
||||
* Window server kernel implementation for external process windows
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "WinServer.hpp"
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
static WindowSlot g_slots[MaxWindows];
|
||||
|
||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
||||
uint64_t& heapNext, uint64_t& outVa) {
|
||||
// Find a free slot
|
||||
int slotIdx = -1;
|
||||
for (int i = 0; i < MaxWindows; i++) {
|
||||
if (!g_slots[i].used) {
|
||||
slotIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slotIdx < 0) return -1;
|
||||
|
||||
// Validate dimensions
|
||||
if (w <= 0 || h <= 0) return -1;
|
||||
uint64_t bufSize = (uint64_t)w * h * 4;
|
||||
int numPages = (int)((bufSize + 0xFFF) / 0x1000);
|
||||
if (numPages > MaxPixelPages) return -1;
|
||||
|
||||
WindowSlot& slot = g_slots[slotIdx];
|
||||
memset(&slot, 0, sizeof(WindowSlot));
|
||||
slot.used = true;
|
||||
slot.ownerPid = ownerPid;
|
||||
slot.width = w;
|
||||
slot.height = h;
|
||||
slot.pixelNumPages = numPages;
|
||||
slot.eventHead = 0;
|
||||
slot.eventTail = 0;
|
||||
slot.dirty = false;
|
||||
slot.desktopVa = 0;
|
||||
slot.desktopPid = 0;
|
||||
|
||||
// Copy title
|
||||
int tlen = 0;
|
||||
while (title[tlen] && tlen < 63) {
|
||||
slot.title[tlen] = title[tlen];
|
||||
tlen++;
|
||||
}
|
||||
slot.title[tlen] = '\0';
|
||||
|
||||
// Allocate physical pages and map into owner's address space
|
||||
uint64_t userVa = heapNext;
|
||||
for (int i = 0; i < numPages; i++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
// Cleanup on failure - mark slot unused
|
||||
slot.used = false;
|
||||
return -1;
|
||||
}
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
slot.pixelPhysPages[i] = physAddr;
|
||||
Memory::VMM::Paging::MapUserIn(ownerPml4, physAddr, userVa + (uint64_t)i * 0x1000);
|
||||
}
|
||||
|
||||
slot.ownerVa = userVa;
|
||||
heapNext += (uint64_t)numPages * 0x1000;
|
||||
outVa = userVa;
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "WinServer") << "Created window " << slotIdx
|
||||
<< " (" << w << "x" << h << ") for PID " << ownerPid;
|
||||
|
||||
return slotIdx;
|
||||
}
|
||||
|
||||
int Destroy(int windowId, int callerPid) {
|
||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
||||
|
||||
slot.used = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Present(int windowId, int callerPid) {
|
||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
||||
|
||||
slot.dirty = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Poll(int windowId, int callerPid, Zenith::WinEvent* outEvent) {
|
||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
||||
|
||||
if (slot.eventHead == slot.eventTail) return 0; // no events
|
||||
|
||||
*outEvent = slot.events[slot.eventTail];
|
||||
slot.eventTail = (slot.eventTail + 1) % MaxEvents;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Enumerate(Zenith::WinInfo* outArray, int maxCount) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MaxWindows && count < maxCount; i++) {
|
||||
if (!g_slots[i].used) continue;
|
||||
Zenith::WinInfo& info = outArray[count];
|
||||
info.id = i;
|
||||
info.ownerPid = g_slots[i].ownerPid;
|
||||
for (int j = 0; j < 64; j++) info.title[j] = g_slots[i].title[j];
|
||||
info.width = g_slots[i].width;
|
||||
info.height = g_slots[i].height;
|
||||
info.dirty = g_slots[i].dirty ? 1 : 0;
|
||||
g_slots[i].dirty = false; // clear dirty after read
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext) {
|
||||
if (windowId < 0 || windowId >= MaxWindows) return 0;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used) return 0;
|
||||
|
||||
// If already mapped into this process, return existing VA
|
||||
if (slot.desktopPid == callerPid && slot.desktopVa != 0) {
|
||||
return slot.desktopVa;
|
||||
}
|
||||
|
||||
uint64_t userVa = heapNext;
|
||||
|
||||
for (int i = 0; i < slot.pixelNumPages; i++) {
|
||||
Memory::VMM::Paging::MapUserIn(callerPml4, slot.pixelPhysPages[i],
|
||||
userVa + (uint64_t)i * 0x1000);
|
||||
}
|
||||
|
||||
slot.desktopVa = userVa;
|
||||
slot.desktopPid = callerPid;
|
||||
heapNext += (uint64_t)slot.pixelNumPages * 0x1000;
|
||||
|
||||
return userVa;
|
||||
}
|
||||
|
||||
int SendEvent(int windowId, const Zenith::WinEvent* event) {
|
||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used) return -1;
|
||||
|
||||
int nextHead = (slot.eventHead + 1) % MaxEvents;
|
||||
if (nextHead == slot.eventTail) return -1; // queue full, drop event
|
||||
|
||||
slot.events[slot.eventHead] = *event;
|
||||
slot.eventHead = nextHead;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CleanupProcess(int pid) {
|
||||
for (int i = 0; i < MaxWindows; i++) {
|
||||
if (g_slots[i].used && g_slots[i].ownerPid == pid) {
|
||||
Kt::KernelLogStream(Kt::INFO, "WinServer") << "Cleaning up window "
|
||||
<< i << " for exited PID " << pid;
|
||||
g_slots[i].used = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* WinServer.hpp
|
||||
* Window server kernel state for external process windows
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Syscall.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
static constexpr int MaxWindows = 8;
|
||||
static constexpr int MaxEvents = 64;
|
||||
static constexpr int MaxPixelPages = 2048; // up to 2048x1024 @ 32bpp = 8MB
|
||||
|
||||
struct WindowSlot {
|
||||
bool used;
|
||||
int ownerPid;
|
||||
char title[64];
|
||||
int width, height;
|
||||
uint64_t pixelPhysPages[MaxPixelPages];
|
||||
int pixelNumPages;
|
||||
uint64_t ownerVa; // VA in owner's address space
|
||||
uint64_t desktopVa; // VA in desktop's address space (0 = not yet mapped)
|
||||
int desktopPid; // PID of the process that mapped it
|
||||
Zenith::WinEvent events[MaxEvents];
|
||||
int eventHead, eventTail;
|
||||
bool dirty;
|
||||
};
|
||||
|
||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
||||
uint64_t& heapNext, uint64_t& outVa);
|
||||
int Destroy(int windowId, int callerPid);
|
||||
int Present(int windowId, int callerPid);
|
||||
int Poll(int windowId, int callerPid, Zenith::WinEvent* outEvent);
|
||||
int Enumerate(Zenith::WinInfo* outArray, int maxCount);
|
||||
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext);
|
||||
int SendEvent(int windowId, const Zenith::WinEvent* event);
|
||||
void CleanupProcess(int pid);
|
||||
|
||||
}
|
||||
@@ -461,9 +461,11 @@ namespace Drivers::USB::UsbDevice {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 10: SET_PROTOCOL(0) -- request Boot Protocol
|
||||
// Step 10: SET_PROTOCOL -- Boot Protocol for keyboards only
|
||||
// -----------------------------------------------------------------
|
||||
if (foundEp) {
|
||||
// Boot Protocol constrains mice to 3-byte reports (no scroll wheel).
|
||||
// Keep mice in Report Protocol (the default) so scroll data is included.
|
||||
if (foundEp && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL,
|
||||
0, 0, 0, nullptr, false);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
@@ -475,7 +477,7 @@ namespace Drivers::USB::UsbDevice {
|
||||
// -----------------------------------------------------------------
|
||||
// Step 11: SET_IDLE(4) -- 16ms idle rate for software typematic
|
||||
// -----------------------------------------------------------------
|
||||
if (foundEp) {
|
||||
if (foundEp && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||
// wValue upper byte = duration in 4ms units, lower byte = report ID
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE,
|
||||
(4 << 8), 0, 0, nullptr, false);
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
static int g_detectedCpuCount = 0;
|
||||
|
||||
int GetDetectedCpuCount() { return g_detectedCpuCount; }
|
||||
|
||||
void ApicInitialize(ACPI::CommonSDTHeader* xsdt) {
|
||||
KernelLogStream(INFO, "APIC") << "Initializing APIC subsystem";
|
||||
|
||||
@@ -29,6 +33,8 @@ namespace Hal {
|
||||
return;
|
||||
}
|
||||
|
||||
g_detectedCpuCount = madt.LocalApicCount;
|
||||
|
||||
if (madt.IoApicAddress == 0) {
|
||||
KernelLogStream(ERROR, "APIC") << "No IOAPIC found in MADT";
|
||||
return;
|
||||
|
||||
@@ -19,4 +19,7 @@ namespace Hal {
|
||||
//
|
||||
// xsdt: pointer to the XSDT (already HHDM-mapped)
|
||||
void ApicInitialize(ACPI::CommonSDTHeader* xsdt);
|
||||
|
||||
// Number of CPU cores detected via MADT (available after ApicInitialize)
|
||||
int GetDetectedCpuCount();
|
||||
};
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
;
|
||||
; Context.asm
|
||||
; Context switch: save/restore callee-saved registers, stack pointer, and CR3
|
||||
; Context switch: save/restore callee-saved registers, stack pointer, CR3, and FPU state
|
||||
; Copyright (c) 2025 Daniel Hammer
|
||||
;
|
||||
|
||||
[bits 64]
|
||||
section .text
|
||||
|
||||
; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3)
|
||||
; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
|
||||
; uint8_t* oldFpuArea, uint8_t* newFpuArea)
|
||||
; rdi = pointer to save old RSP
|
||||
; rsi = new RSP to restore
|
||||
; rdx = new PML4 physical address (for CR3)
|
||||
; rcx = old FPU state area (may be null)
|
||||
; r8 = new FPU state area (may be null)
|
||||
global SchedContextSwitch
|
||||
SchedContextSwitch:
|
||||
; Save FPU state before pushing registers (rcx/r8 are caller-saved)
|
||||
test rcx, rcx
|
||||
jz .skip_fxsave
|
||||
fxsave [rcx]
|
||||
.skip_fxsave:
|
||||
|
||||
; Stash r8 in r9 (callee-saved registers will clobber r8 slot on stack)
|
||||
mov r9, r8
|
||||
|
||||
; Save callee-saved registers on the current stack
|
||||
push rbp
|
||||
push rbx
|
||||
@@ -42,4 +54,10 @@ SchedContextSwitch:
|
||||
pop rbx
|
||||
pop rbp
|
||||
|
||||
; Restore FPU state
|
||||
test r9, r9
|
||||
jz .skip_fxrstor
|
||||
fxrstor [r9]
|
||||
.skip_fxrstor:
|
||||
|
||||
ret
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Api/WinServer.hpp>
|
||||
|
||||
// Assembly: context switch with CR3 parameter
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3);
|
||||
// Assembly: context switch with CR3 and FPU state parameters
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
|
||||
uint8_t* oldFpuArea, uint8_t* newFpuArea);
|
||||
|
||||
// Assembly: jump to user mode via IRETQ
|
||||
extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp);
|
||||
@@ -66,7 +68,7 @@ namespace Sched {
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
processTable[i].pid = i;
|
||||
processTable[i].state = ProcessState::Free;
|
||||
processTable[i].name = nullptr;
|
||||
processTable[i].name[0] = '\0';
|
||||
processTable[i].savedRsp = 0;
|
||||
processTable[i].stackBase = 0;
|
||||
processTable[i].entryPoint = 0;
|
||||
@@ -191,7 +193,11 @@ namespace Sched {
|
||||
Process& proc = processTable[slot];
|
||||
proc.pid = nextPid++;
|
||||
proc.state = ProcessState::Ready;
|
||||
proc.name = vfsPath;
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < 63 && vfsPath[i]; i++) proc.name[i] = vfsPath[i];
|
||||
proc.name[i] = '\0';
|
||||
}
|
||||
proc.savedRsp = (uint64_t)sp;
|
||||
proc.stackBase = (uint64_t)kernelStackBase;
|
||||
proc.entryPoint = entry;
|
||||
@@ -224,6 +230,11 @@ namespace Sched {
|
||||
proc.termCols = 0;
|
||||
proc.termRows = 0;
|
||||
|
||||
// Initialize FPU state: zero out, then set default FCW and MXCSR
|
||||
memset(proc.fpuState, 0, 512);
|
||||
*(uint16_t*)&proc.fpuState[0] = 0x037F; // FCW: default x87 control word
|
||||
*(uint32_t*)&proc.fpuState[24] = 0x1F80; // MXCSR: default SSE control/status
|
||||
|
||||
return proc.pid;
|
||||
}
|
||||
|
||||
@@ -276,7 +287,9 @@ namespace Sched {
|
||||
// Update TSS RSP0 for hardware interrupts from ring 3
|
||||
Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
|
||||
|
||||
SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3);
|
||||
uint8_t* oldFpu = (currentPid >= 0) ? processTable[currentPid].fpuState : nullptr;
|
||||
uint8_t* newFpu = processTable[next].fpuState;
|
||||
SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3, oldFpu, newFpu);
|
||||
}
|
||||
|
||||
void Tick() {
|
||||
@@ -309,6 +322,9 @@ namespace Sched {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up any windows owned by this process
|
||||
WinServer::CleanupProcess(processTable[currentPid].pid);
|
||||
|
||||
processTable[currentPid].state = ProcessState::Terminated;
|
||||
|
||||
int next = -1;
|
||||
@@ -329,11 +345,13 @@ namespace Sched {
|
||||
g_kernelRsp = processTable[next].kernelStackTop;
|
||||
Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
|
||||
|
||||
SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3);
|
||||
SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3,
|
||||
processTable[old].fpuState, processTable[next].fpuState);
|
||||
} else {
|
||||
int old = currentPid;
|
||||
currentPid = -1;
|
||||
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3());
|
||||
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3(),
|
||||
processTable[old].fpuState, nullptr);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
@@ -362,4 +380,9 @@ namespace Sched {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Process* GetProcessSlot(int slot) {
|
||||
if (slot < 0 || slot >= MaxProcesses) return nullptr;
|
||||
return &processTable[slot];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Sched {
|
||||
struct Process {
|
||||
int pid;
|
||||
ProcessState state;
|
||||
const char* name;
|
||||
char name[64];
|
||||
uint64_t savedRsp;
|
||||
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
|
||||
uint64_t entryPoint;
|
||||
@@ -58,6 +58,9 @@ namespace Sched {
|
||||
// GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE)
|
||||
int termCols = 0;
|
||||
int termRows = 0;
|
||||
|
||||
// FPU/SSE state (FXSAVE format, must be 16-byte aligned)
|
||||
uint8_t fpuState[512] __attribute__((aligned(16)));
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
@@ -82,4 +85,7 @@ namespace Sched {
|
||||
// Find a process by PID (returns nullptr if not found or not alive)
|
||||
Process* GetProcessByPid(int pid);
|
||||
|
||||
// Get a pointer to slot i in the process table (for enumeration)
|
||||
Process* GetProcessSlot(int slot);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user