feat: implement new IPC layer

This commit is contained in:
2026-04-01 13:11:03 +02:00
parent 6276f8b162
commit 346977a0f9
34 changed files with 3210 additions and 897 deletions
+107 -29
View File
@@ -1,37 +1,115 @@
#pragma once
#include <Sched/Scheduler.hpp>
#include <Ipc/Ipc.hpp>
namespace Montauk {
// 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);
}
// SPSC ring buffer helpers. On x86 TSO, atomic-width aligned
// loads/stores are naturally atomic. The compiler barrier ensures
// the data write is visible before the head update.
static void RingWrite(uint8_t* buf, volatile uint32_t& head, uint32_t /*tail*/, uint32_t size, uint8_t byte) {
uint32_t h = head;
buf[h] = byte;
asm volatile("" ::: "memory"); // compiler barrier
head = (h + 1) % size;
}
static int RingRead(uint8_t* buf, volatile uint32_t& head, volatile uint32_t& tail, uint32_t size, uint8_t* out, int maxLen) {
int count = 0;
uint32_t t = tail;
uint32_t h = head;
while (t != h && count < maxLen) {
out[count++] = buf[t];
t = (t + 1) % size;
if (proc->ioOutHandle >= 0 || proc->ioInHandle >= 0 || proc->ioKeyHandle >= 0) {
return proc;
}
asm volatile("" ::: "memory"); // compiler barrier
tail = t;
return count;
return (proc->parentPid >= 0) ? Sched::GetProcessByPid(proc->parentPid) : nullptr;
}
}
static bool ResolveProcessHandle(Sched::Process* proc, int handle, Ipc::HandleType expectedType,
Ipc::Object*& outObject, uint32_t* outRights = nullptr) {
if (proc == nullptr || handle < 0) return false;
int slot = Ipc::SlotForPid(proc->pid);
if (slot < 0) return false;
Ipc::HandleType type = Ipc::HandleType::None;
Ipc::Object* object = nullptr;
uint32_t rights = 0;
if (!Ipc::SnapshotHandleForSlot(slot, handle, type, object, rights)) return false;
if (type != expectedType || object == nullptr) return false;
outObject = object;
if (outRights != nullptr) *outRights = rights;
return true;
}
static Ipc::Stream* GetRedirOutStream(Sched::Process* proc) {
proc = GetRedirTarget(proc);
if (proc == nullptr) return nullptr;
Ipc::Object* object = nullptr;
if (!ResolveProcessHandle(proc, proc->ioOutHandle, Ipc::HandleType::Stream, object)) return nullptr;
return (Ipc::Stream*)object;
}
static Ipc::Stream* GetRedirInStream(Sched::Process* proc) {
proc = GetRedirTarget(proc);
if (proc == nullptr) return nullptr;
Ipc::Object* object = nullptr;
if (!ResolveProcessHandle(proc, proc->ioInHandle, Ipc::HandleType::Stream, object)) return nullptr;
return (Ipc::Stream*)object;
}
static Ipc::Mailbox* GetRedirKeyMailbox(Sched::Process* proc) {
proc = GetRedirTarget(proc);
if (proc == nullptr) return nullptr;
Ipc::Object* object = nullptr;
if (!ResolveProcessHandle(proc, proc->ioKeyHandle, Ipc::HandleType::Mailbox, object)) return nullptr;
return (Ipc::Mailbox*)object;
}
static int DuplicateHandleBetweenSlots(int srcSlot, int handle, int dstSlot) {
if (srcSlot < 0 || dstSlot < 0 || handle < 0) return -1;
Ipc::HandleType type = Ipc::HandleType::None;
Ipc::Object* object = nullptr;
uint32_t rights = 0;
if (!Ipc::SnapshotHandleForSlot(srcSlot, handle, type, object, rights)) return -1;
if ((rights & Ipc::RightDup) == 0) return -1;
return Ipc::InstallHandleForSlot(dstSlot, object, type, rights);
}
static bool ConfigureRedirWaitsetForSlot(int slot, Sched::Process* proc) {
if (slot < 0 || proc == nullptr) return false;
int waitset = Ipc::CreateWaitsetHandleForSlot(slot);
if (waitset < 0) return false;
if (proc->ioInHandle >= 0) {
if (Ipc::WaitsetAddHandleForSlot(slot, waitset, proc->ioInHandle,
Ipc::SignalReadable | Ipc::SignalPeerClosed) < 0) {
Ipc::CloseHandleForSlot(slot, waitset);
return false;
}
}
if (proc->ioKeyHandle >= 0) {
if (Ipc::WaitsetAddHandleForSlot(slot, waitset, proc->ioKeyHandle,
Ipc::SignalReadable | Ipc::SignalPeerClosed) < 0) {
Ipc::CloseHandleForSlot(slot, waitset);
return false;
}
}
proc->ioWaitsetHandle = waitset;
return true;
}
static int WriteAllToStream(Ipc::Stream* stream, const uint8_t* data, int len) {
if (stream == nullptr || data == nullptr || len <= 0) return -1;
int total = 0;
while (total < len) {
int written = Ipc::StreamWrite(stream, data + total, len - total, false);
if (written < 0) {
return (total > 0) ? total : -1;
}
if (written == 0) {
Sched::BlockOnObject(stream, 0);
continue;
}
total += written;
}
return total;
}
}
+7 -6
View File
@@ -12,25 +12,26 @@
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Libraries/Memory.hpp>
#include <Ipc/Ipc.hpp>
#include "Path.hpp"
namespace Montauk {
static int Sys_Open(const char* path) {
char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
return Fs::Vfs::VfsOpen(resolved);
return Ipc::OpenFileHandle(resolved);
}
static int Sys_Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
return Fs::Vfs::VfsRead(handle, buffer, offset, size);
return Ipc::FileReadHandle(handle, buffer, offset, size);
}
static uint64_t Sys_GetSize(int handle) {
return Fs::Vfs::VfsGetSize(handle);
return Ipc::FileGetSizeHandle(handle);
}
static void Sys_Close(int handle) {
Fs::Vfs::VfsClose(handle);
Ipc::CloseHandle(handle);
}
static int Sys_ReadDir(const char* path, const char** outNames, int maxEntries) {
@@ -89,13 +90,13 @@ namespace Montauk {
}
static int Sys_FWrite(int handle, const uint8_t* data, uint64_t offset, uint64_t size) {
return Fs::Vfs::VfsWrite(handle, data, offset, size);
return Ipc::FileWriteHandle(handle, data, offset, size);
}
static int Sys_FCreate(const char* path) {
char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
return Fs::Vfs::VfsCreate(resolved);
return Ipc::CreateFileHandle(resolved);
}
static int Sys_FDelete(const char* path) {
+3
View File
@@ -66,6 +66,9 @@ namespace Montauk {
// If the process is redirected to a GUI terminal, return those dimensions
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
if (proc->termCols > 0 && proc->termRows > 0) {
return ((uint64_t)proc->termRows << 32) | ((uint64_t)proc->termCols & 0xFFFFFFFF);
}
auto* target = GetRedirTarget(proc);
if (target && target->termCols > 0 && target->termRows > 0) {
return ((uint64_t)target->termRows << 32) | ((uint64_t)target->termCols & 0xFFFFFFFF);
+63 -26
View File
@@ -7,8 +7,6 @@
#pragma once
#include <Sched/Scheduler.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include "Syscall.hpp"
#include "Common.hpp"
#include "Path.hpp"
@@ -19,53 +17,92 @@ namespace Montauk {
char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
int parentSlot = Ipc::CurrentSlot();
int childPid = Sched::Spawn(resolved, args);
if (childPid < 0) return -1;
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr) return -1;
int childSlot = Ipc::SlotForPid(childPid);
if (child == nullptr || childSlot < 0 || parentSlot < 0) return -1;
// Allocate ring buffers
void* outPage = Memory::g_pfa->AllocateZeroed();
void* inPage = Memory::g_pfa->AllocateZeroed();
if (!outPage || !inPage) return -1;
Ipc::Stream* outStream = Ipc::CreateStream();
Ipc::Stream* inStream = Ipc::CreateStream();
Ipc::Mailbox* keyMailbox = Ipc::CreateMailbox();
if (outStream == nullptr || inStream == nullptr || keyMailbox == nullptr) {
if (outStream != nullptr) Ipc::ReleaseStream(outStream, false, false);
if (inStream != nullptr) Ipc::ReleaseStream(inStream, false, false);
if (keyMailbox != nullptr) Ipc::ReleaseMailbox(keyMailbox, false, false);
Sched::KillProcess(childPid);
return -1;
}
int parentOutHandle = Ipc::InstallHandleForSlot(
parentSlot, (Ipc::Object*)outStream, Ipc::HandleType::Stream, Ipc::RightRead);
int parentInHandle = Ipc::InstallHandleForSlot(
parentSlot, (Ipc::Object*)inStream, Ipc::HandleType::Stream, Ipc::RightWrite);
int parentKeyHandle = Ipc::InstallHandleForSlot(
parentSlot, (Ipc::Object*)keyMailbox, Ipc::HandleType::Mailbox, Ipc::RightSend);
child->ioOutHandle = Ipc::InstallHandleForSlot(
childSlot, (Ipc::Object*)outStream, Ipc::HandleType::Stream,
Ipc::RightWrite | Ipc::RightWait | Ipc::RightDup);
child->ioInHandle = Ipc::InstallHandleForSlot(
childSlot, (Ipc::Object*)inStream, Ipc::HandleType::Stream,
Ipc::RightRead | Ipc::RightWait | Ipc::RightDup);
child->ioKeyHandle = Ipc::InstallHandleForSlot(
childSlot, (Ipc::Object*)keyMailbox, Ipc::HandleType::Mailbox,
Ipc::RightRecv | Ipc::RightWait | Ipc::RightDup);
if (parentOutHandle < 0 || parentInHandle < 0 || parentKeyHandle < 0 ||
child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
!ConfigureRedirWaitsetForSlot(childSlot, child)) {
if (parentOutHandle >= 0) Ipc::CloseHandleForSlot(parentSlot, parentOutHandle);
if (parentInHandle >= 0) Ipc::CloseHandleForSlot(parentSlot, parentInHandle);
if (parentKeyHandle >= 0) Ipc::CloseHandleForSlot(parentSlot, parentKeyHandle);
if (child->ioOutHandle < 0) Ipc::ReleaseStream(outStream, false, false);
if (child->ioInHandle < 0) Ipc::ReleaseStream(inStream, false, false);
if (child->ioKeyHandle < 0) Ipc::ReleaseMailbox(keyMailbox, false, false);
Sched::KillProcess(childPid);
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();
child->termCols = 0;
child->termRows = 0;
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);
Ipc::Stream* stream = GetRedirOutStream(child);
if (child == nullptr || !child->redirected || stream == nullptr) return -1;
return Ipc::StreamRead(stream, (uint8_t*)buf, maxLen, true);
}
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;
Ipc::Stream* stream = GetRedirInStream(child);
if (child == nullptr || !child->redirected || stream == nullptr) return -1;
return WriteAllToStream(stream, (const uint8_t*)data, 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;
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child);
if (child == nullptr || !child->redirected || mailbox == nullptr) return -1;
for (;;) {
int rc = Ipc::MailboxSend(mailbox, 0, key, sizeof(KeyEvent));
if (rc < 0) return -1;
if (rc == 0) {
Sched::BlockOnObject(mailbox, 0);
continue;
}
return 0;
}
}
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
+101
View File
@@ -0,0 +1,101 @@
/*
* IpcSyscall.hpp
* Generic IPC handle, stream, mailbox, waitset, process, and surface syscalls
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <Ipc/Ipc.hpp>
#include <Sched/Scheduler.hpp>
#include "Syscall.hpp"
namespace Montauk {
static int Sys_DupHandle(int handle) {
return Ipc::DupHandle(handle);
}
static uint32_t Sys_WaitHandle(int handle, uint32_t wantedSignals, uint64_t timeoutMs) {
return Ipc::WaitOnHandle(handle, wantedSignals, timeoutMs);
}
static int Sys_StreamCreate(int* outReadHandle, int* outWriteHandle, uint32_t capacity) {
if (outReadHandle == nullptr || outWriteHandle == nullptr) return -1;
int readHandle = -1;
int writeHandle = -1;
if (Ipc::CreateStreamHandlePair(capacity, readHandle, writeHandle) < 0) return -1;
*outReadHandle = readHandle;
*outWriteHandle = writeHandle;
return 0;
}
static int Sys_StreamRead(int handle, uint8_t* buffer, int maxLen) {
return Ipc::StreamReadHandle(handle, buffer, maxLen);
}
static int Sys_StreamWrite(int handle, const uint8_t* data, int len) {
return Ipc::StreamWriteHandle(handle, data, len);
}
static int Sys_MailboxCreate(int* outSendHandle, int* outRecvHandle) {
if (outSendHandle == nullptr || outRecvHandle == nullptr) return -1;
int sendHandle = -1;
int recvHandle = -1;
if (Ipc::CreateMailboxHandlePair(sendHandle, recvHandle) < 0) return -1;
*outSendHandle = sendHandle;
*outRecvHandle = recvHandle;
return 0;
}
static int Sys_MailboxSend(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle) {
return Ipc::MailboxSendHandle(handle, msgType, data, len, attachHandle);
}
static int Sys_MailboxRecv(int handle, uint32_t* outMsgType, void* data,
uint16_t* inOutLen, int* outAttachHandle) {
return Ipc::MailboxRecvHandle(handle, outMsgType, data, inOutLen, outAttachHandle);
}
static int Sys_WaitsetCreate() {
return Ipc::CreateWaitsetHandleForCurrent();
}
static int Sys_WaitsetAdd(int waitsetHandle, int targetHandle, uint32_t signals) {
return Ipc::WaitsetAddHandle(waitsetHandle, targetHandle, signals);
}
static int Sys_WaitsetRemove(int waitsetHandle, int index) {
return Ipc::WaitsetRemoveIndex(waitsetHandle, index);
}
static int Sys_WaitsetWait(int waitsetHandle, IpcWaitResult* outReady, uint64_t timeoutMs) {
Ipc::WaitsetReady ready = {-1, 0};
int result = Ipc::WaitsetWaitHandle(waitsetHandle, outReady ? &ready : nullptr, timeoutMs);
if (result > 0 && outReady != nullptr) {
outReady->index = ready.index;
outReady->signals = ready.signals;
}
return result;
}
static int Sys_ProcOpen(int pid) {
return Ipc::OpenProcessHandle(pid);
}
static int Sys_SurfaceCreate(uint64_t byteSize) {
return Ipc::CreateSurfaceHandle(byteSize);
}
static uint64_t Sys_SurfaceMap(int handle) {
return Ipc::MapSurfaceHandle(handle);
}
static int Sys_SurfaceResize(int handle, uint64_t newSize) {
return Ipc::ResizeSurfaceHandle(handle, newSize);
}
}
+43 -23
View File
@@ -7,6 +7,7 @@
#pragma once
#include <Sched/Scheduler.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Libraries/Memory.hpp>
#include "Common.hpp"
@@ -14,8 +15,8 @@ namespace Montauk {
static bool Sys_IsKeyAvailable() {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target) return target->keyHead != target->keyTail;
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc);
if (mailbox != nullptr) return Ipc::MailboxHasMessage(mailbox);
}
return Drivers::PS2::Keyboard::IsKeyAvailable();
}
@@ -24,15 +25,18 @@ namespace Montauk {
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();
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc);
if (mailbox != nullptr) {
for (;;) {
uint16_t len = sizeof(KeyEvent);
int rc = Ipc::MailboxRecv(mailbox, nullptr, outEvent, &len, true);
if (rc > 0) return;
if (rc < 0) {
memset(outEvent, 0, sizeof(KeyEvent));
return;
}
Sched::BlockOnObject(mailbox, 0);
}
*outEvent = target->keyBuf[target->keyTail];
target->keyTail = (target->keyTail + 1) % 64;
return;
}
}
auto k = Drivers::PS2::Keyboard::GetKey();
@@ -47,24 +51,40 @@ namespace Montauk {
static char Sys_GetChar() {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target) {
while (true) {
if (target->inBuf && target->inTail != target->inHead) {
uint8_t c = target->inBuf[target->inTail];
target->inTail = (target->inTail + 1) % Sched::Process::IoBufSize;
return (char)c;
Ipc::Stream* input = GetRedirInStream(proc);
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc);
if (input != nullptr || mailbox != nullptr) {
for (;;) {
if (input != nullptr) {
uint8_t c = 0;
int rc = Ipc::StreamRead(input, &c, 1, true);
if (rc > 0) return (char)c;
if (rc < 0 && mailbox == nullptr) return 0;
}
if (target->keyTail != target->keyHead) {
auto ev = target->keyBuf[target->keyTail];
target->keyTail = (target->keyTail + 1) % 64;
if (ev.pressed && ev.ascii != 0) {
return ev.ascii;
if (mailbox != nullptr) {
KeyEvent ev{};
uint16_t len = sizeof(ev);
int rc = Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true);
if (rc > 0) {
if (ev.pressed && ev.ascii != 0) return ev.ascii;
continue;
}
if (rc < 0 && input == nullptr) return 0;
}
Sched::Schedule();
if (proc->ioWaitsetHandle >= 0) {
Ipc::WaitsetReady ready{};
if (Ipc::WaitsetWaitHandle(proc->ioWaitsetHandle, &ready, ~0ULL) < 0) {
return 0;
}
} else if (input != nullptr) {
Sched::BlockOnObject(input, 0);
} else if (mailbox != nullptr) {
Sched::BlockOnObject(mailbox, 0);
} else {
return 0;
}
}
}
}
+24 -13
View File
@@ -14,6 +14,7 @@
#include <Fs/Vfs.hpp>
#include "Syscall.hpp"
#include "Common.hpp"
#include "WinServer.hpp"
#include "Path.hpp"
@@ -44,22 +45,32 @@ namespace Montauk {
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
auto* parent = Sched::GetCurrentProcessPtr();
int parentSlot = Ipc::CurrentSlot();
int childPid = Sched::Spawn(resolved, 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;
int childSlot = Ipc::SlotForPid(childPid);
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
Sched::KillProcess(childPid);
return -1;
}
child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot);
child->ioInHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioInHandle, childSlot);
child->ioKeyHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioKeyHandle, childSlot);
if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
!ConfigureRedirWaitsetForSlot(childSlot, child)) {
Sched::KillProcess(childPid);
return -1;
}
child->redirected = true;
child->parentPid = parent->pid;
child->termCols = parent->termCols;
child->termRows = parent->termRows;
}
return childPid;
@@ -157,9 +168,9 @@ namespace Montauk {
const char* entries[1];
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1;
} else {
int handle = Fs::Vfs::VfsOpen(resolved);
if (handle < 0) return -1;
Fs::Vfs::VfsClose(handle);
Fs::Vfs::BackendFile file = {-1, -1};
if (Fs::Vfs::OpenBackendFile(resolved, file) < 0) return -1;
Fs::Vfs::CloseBackendFile(file);
}
int i = 0;
+53
View File
@@ -32,6 +32,7 @@
#include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETSCALE, SYS_WINGETSCALE
#include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
#include "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE
// Assembly entry point
extern "C" void SyscallEntry();
@@ -256,6 +257,8 @@ namespace Montauk {
return (int64_t)Sys_WinEnum((WinInfo*)frame->arg1, (int)frame->arg2);
case SYS_WINMAP:
return (int64_t)Sys_WinMap((int)frame->arg1);
case SYS_WINUNMAP:
return (int64_t)Sys_WinUnmap((int)frame->arg1);
case SYS_WINSENDEVENT:
if (!ValidUserPtr(frame->arg2)) return -1;
return (int64_t)Sys_WinSendEvent((int)frame->arg1, (const WinEvent*)frame->arg2);
@@ -353,6 +356,56 @@ namespace Montauk {
case SYS_CHDIR:
if (!ValidUserPtr(frame->arg1)) return -1;
return Sys_Chdir((const char*)frame->arg1);
case SYS_DUPHANDLE:
return Sys_DupHandle((int)frame->arg1);
case SYS_WAIT_HANDLE:
return (int64_t)Sys_WaitHandle((int)frame->arg1, (uint32_t)frame->arg2, frame->arg3);
case SYS_STREAM_CREATE:
if (!ValidUserPtr(frame->arg1) || !ValidUserPtr(frame->arg2)) return -1;
return Sys_StreamCreate((int*)frame->arg1, (int*)frame->arg2, (uint32_t)frame->arg3);
case SYS_STREAM_READ:
if (!ValidUserPtr(frame->arg2)) return -1;
return Sys_StreamRead((int)frame->arg1, (uint8_t*)frame->arg2, (int)frame->arg3);
case SYS_STREAM_WRITE:
if (!ValidUserPtr(frame->arg2)) return -1;
return Sys_StreamWrite((int)frame->arg1, (const uint8_t*)frame->arg2, (int)frame->arg3);
case SYS_MAILBOX_CREATE:
if (!ValidUserPtr(frame->arg1) || !ValidUserPtr(frame->arg2)) return -1;
return Sys_MailboxCreate((int*)frame->arg1, (int*)frame->arg2);
case SYS_MAILBOX_SEND:
if (frame->arg3 != 0 && !ValidUserPtr(frame->arg3)) return -1;
return Sys_MailboxSend((int)frame->arg1, (uint32_t)frame->arg2,
(const void*)frame->arg3, (uint16_t)frame->arg4,
(int)frame->arg5);
case SYS_MAILBOX_RECV:
if (frame->arg2 != 0 && !IsUserPtr(frame->arg2)) return -1;
if (frame->arg3 != 0 && !IsUserPtr(frame->arg3)) return -1;
if (!ValidUserPtr(frame->arg4)) return -1;
if (frame->arg5 != 0 && !IsUserPtr(frame->arg5)) return -1;
return Sys_MailboxRecv((int)frame->arg1,
IsUserPtr(frame->arg2) ? (uint32_t*)frame->arg2 : nullptr,
IsUserPtr(frame->arg3) ? (void*)frame->arg3 : nullptr,
(uint16_t*)frame->arg4,
IsUserPtr(frame->arg5) ? (int*)frame->arg5 : nullptr);
case SYS_WAITSET_CREATE:
return Sys_WaitsetCreate();
case SYS_WAITSET_ADD:
return Sys_WaitsetAdd((int)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
case SYS_WAITSET_REMOVE:
return Sys_WaitsetRemove((int)frame->arg1, (int)frame->arg2);
case SYS_WAITSET_WAIT:
if (frame->arg2 != 0 && !ValidUserPtr(frame->arg2)) return -1;
return Sys_WaitsetWait((int)frame->arg1,
IsUserPtr(frame->arg2) ? (IpcWaitResult*)frame->arg2 : nullptr,
frame->arg3);
case SYS_PROC_OPEN:
return Sys_ProcOpen((int)frame->arg1);
case SYS_SURFACE_CREATE:
return Sys_SurfaceCreate(frame->arg1);
case SYS_SURFACE_MAP:
return (int64_t)Sys_SurfaceMap((int)frame->arg1);
case SYS_SURFACE_RESIZE:
return Sys_SurfaceResize((int)frame->arg1, frame->arg2);
default:
return -1;
}
+30
View File
@@ -123,6 +123,7 @@ namespace Montauk {
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_WINUNMAP = 97;
static constexpr uint64_t SYS_WINSENDEVENT = 60;
static constexpr uint64_t SYS_WINRESIZE = 64;
static constexpr uint64_t SYS_WINSETSCALE = 65;
@@ -187,6 +188,30 @@ namespace Montauk {
static constexpr uint64_t SYS_GETCWD = 95;
static constexpr uint64_t SYS_CHDIR = 96;
/* IpcSyscall.hpp */
static constexpr uint64_t SYS_DUPHANDLE = 98;
static constexpr uint64_t SYS_WAIT_HANDLE = 99;
static constexpr uint64_t SYS_STREAM_CREATE = 100;
static constexpr uint64_t SYS_STREAM_READ = 101;
static constexpr uint64_t SYS_STREAM_WRITE = 102;
static constexpr uint64_t SYS_MAILBOX_CREATE = 103;
static constexpr uint64_t SYS_MAILBOX_SEND = 104;
static constexpr uint64_t SYS_MAILBOX_RECV = 105;
static constexpr uint64_t SYS_WAITSET_CREATE = 106;
static constexpr uint64_t SYS_WAITSET_ADD = 107;
static constexpr uint64_t SYS_WAITSET_REMOVE = 108;
static constexpr uint64_t SYS_WAITSET_WAIT = 109;
static constexpr uint64_t SYS_PROC_OPEN = 110;
static constexpr uint64_t SYS_SURFACE_CREATE = 111;
static constexpr uint64_t SYS_SURFACE_MAP = 112;
static constexpr uint64_t SYS_SURFACE_RESIZE = 113;
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1;
static constexpr uint32_t IPC_SIGNAL_PEER_CLOSED = 1u << 2;
static constexpr uint32_t IPC_SIGNAL_EXITED = 1u << 3;
static constexpr uint32_t IPC_SIGNAL_READY = 1u << 4;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
@@ -239,6 +264,11 @@ namespace Montauk {
uint8_t buttons;
};
struct IpcWaitResult {
int32_t index;
uint32_t signals;
};
// Window server shared types
struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
+11 -8
View File
@@ -15,10 +15,12 @@ namespace Montauk {
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]);
Ipc::Stream* stream = GetRedirOutStream(proc);
if (stream != nullptr) {
int len = 0;
while (text[len]) len++;
if (len > 0) {
WriteAllToStream(stream, (const uint8_t*)text, len);
}
return;
}
@@ -31,9 +33,10 @@ namespace Montauk {
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);
Ipc::Stream* stream = GetRedirOutStream(proc);
if (stream != nullptr) {
uint8_t byte = (uint8_t)c;
WriteAllToStream(stream, &byte, 1);
return;
}
}
@@ -41,4 +44,4 @@ namespace Montauk {
if (Kt::g_suppressKernelLog) return;
Kt::Putchar(c);
}
};
};
+164 -294
View File
@@ -5,118 +5,104 @@
*/
#include "WinServer.hpp"
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/Paging.hpp>
#include <Memory/HHDM.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Spinlock.hpp>
#include <Sched/Scheduler.hpp>
#include <Timekeeping/ApicTimer.hpp>
namespace WinServer {
static WindowSlot g_slots[MaxWindows];
static int g_uiScale = 1;
static kcp::Mutex wsLock;
static constexpr uint64_t RetireGraceMs = 2000;
static constexpr int MaxRetiredBatches = MaxWindows * 4;
static constexpr int MaxRetiredSnapshots = MaxWindows * 4;
struct RetiredBatch {
struct RetiredSnapshot {
bool used;
int numPages;
uint64_t freeAfterMs;
uint64_t physPages[MaxPixelPages];
int windowId;
Ipc::Surface* surface;
};
static RetiredBatch g_retired[MaxRetiredBatches];
static RetiredSnapshot g_retiredSnapshots[MaxRetiredSnapshots];
// RAII lock guard for WinServer operations
struct WsGuard {
WsGuard() { wsLock.Acquire(); }
~WsGuard() { wsLock.Release(); }
};
static void FreePageBatchLocked(uint64_t* physPages, int numPages) {
for (int i = 0; i < numPages; i++) {
if (physPages[i] != 0) {
Memory::g_pfa->Free((void*)Memory::HHDM(physPages[i]));
physPages[i] = 0;
}
}
static void ResetSlotLocked(WindowSlot& slot) {
memset(&slot, 0, sizeof(WindowSlot));
}
static void ProcessRetiredLocked() {
uint64_t now = Timekeeping::GetMilliseconds();
for (int i = 0; i < MaxRetiredBatches; i++) {
if (!g_retired[i].used) continue;
if (now < g_retired[i].freeAfterMs) continue;
FreePageBatchLocked(g_retired[i].physPages, g_retired[i].numPages);
g_retired[i].used = false;
g_retired[i].numPages = 0;
g_retired[i].freeAfterMs = 0;
}
}
static void RetireSnapshotLocked(int windowId, Ipc::Surface* surface) {
if (surface == nullptr) return;
static void RetirePageBatchLocked(uint64_t* physPages, int numPages) {
if (numPages <= 0) return;
int retireIdx = -1;
for (int i = 0; i < MaxRetiredBatches; i++) {
if (!g_retired[i].used) {
retireIdx = i;
break;
}
}
if (retireIdx < 0) {
ProcessRetiredLocked();
for (int i = 0; i < MaxRetiredBatches; i++) {
if (!g_retired[i].used) {
retireIdx = i;
break;
}
}
}
if (retireIdx < 0) {
uint64_t oldest = ~0ULL;
for (int i = 0; i < MaxRetiredBatches; i++) {
if (g_retired[i].freeAfterMs < oldest) {
oldest = g_retired[i].freeAfterMs;
retireIdx = i;
}
}
if (retireIdx >= 0) {
Kt::KernelLogStream(Kt::ERROR, "WinServer")
<< "Retire queue full, forcing early free of stale window pages";
FreePageBatchLocked(g_retired[retireIdx].physPages, g_retired[retireIdx].numPages);
g_retired[retireIdx].used = false;
g_retired[retireIdx].numPages = 0;
g_retired[retireIdx].freeAfterMs = 0;
}
}
if (retireIdx < 0) {
FreePageBatchLocked(physPages, numPages);
for (int i = 0; i < MaxRetiredSnapshots; i++) {
if (g_retiredSnapshots[i].used) continue;
g_retiredSnapshots[i].used = true;
g_retiredSnapshots[i].windowId = windowId;
g_retiredSnapshots[i].surface = surface;
return;
}
RetiredBatch& batch = g_retired[retireIdx];
batch.used = true;
batch.numPages = numPages;
batch.freeAfterMs = Timekeeping::GetMilliseconds() + RetireGraceMs;
for (int i = 0; i < numPages; i++) {
batch.physPages[i] = physPages[i];
physPages[i] = 0;
Ipc::ReleaseSurface(surface);
}
static int UnmapRetiredSnapshotsLocked(int windowId, int callerPid, uint64_t callerPml4) {
int result = -1;
for (int i = 0; i < MaxRetiredSnapshots; i++) {
if (!g_retiredSnapshots[i].used || g_retiredSnapshots[i].windowId != windowId) continue;
if (g_retiredSnapshots[i].surface != nullptr) {
if (Ipc::UnmapSurfaceForPid(g_retiredSnapshots[i].surface, callerPid, callerPml4) == 0) {
result = 0;
}
Ipc::ReleaseSurface(g_retiredSnapshots[i].surface);
}
g_retiredSnapshots[i].used = false;
g_retiredSnapshots[i].windowId = -1;
g_retiredSnapshots[i].surface = nullptr;
}
return result;
}
static void ReleaseSlotResourcesLocked(WindowSlot& slot, bool unmapOwner) {
if (unmapOwner && slot.liveSurface != nullptr) {
auto* owner = Sched::GetProcessByPid(slot.ownerPid);
if (owner != nullptr) {
Ipc::UnmapSurfaceForPid(slot.liveSurface, slot.ownerPid, owner->pml4Phys);
}
}
if (slot.eventMailbox != nullptr) {
Ipc::ReleaseMailbox(slot.eventMailbox, true, true);
}
if (slot.liveSurface != nullptr) {
Ipc::ReleaseSurface(slot.liveSurface);
}
if (slot.snapshotSurface != nullptr) {
RetireSnapshotLocked((int)(&slot - g_slots), slot.snapshotSurface);
}
ResetSlotLocked(slot);
}
static int SendEventLocked(int windowId, const Montauk::WinEvent* event) {
if (windowId < 0 || windowId >= MaxWindows || event == nullptr) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.eventMailbox == nullptr) return -1;
int rc = Ipc::MailboxSend(slot.eventMailbox, 0, event, sizeof(*event));
return rc > 0 ? 0 : -1;
}
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
uint64_t& heapNext, uint64_t& outVa) {
WsGuard guard;
ProcessRetiredLocked();
// Find a free slot
if (w <= 0 || h <= 0 || w > 16384 || h > 16384) return -1;
int slotIdx = -1;
for (int i = 0; i < MaxWindows; i++) {
if (!g_slots[i].used) {
@@ -126,26 +112,42 @@ namespace WinServer {
}
if (slotIdx < 0) return -1;
// Validate dimensions (cap at 16384 to prevent integer overflow in w*h*4)
if (w <= 0 || h <= 0 || w > 16384 || h > 16384) return -1;
uint64_t bufSize = (uint64_t)w * h * 4;
int numPages = (int)((bufSize + 0xFFF) / 0x1000);
if (numPages > MaxPixelPages) return -1;
uint64_t bufSize = (uint64_t)w * (uint64_t)h * 4ULL;
Ipc::Surface* live = Ipc::CreateSurface(bufSize);
Ipc::Surface* snapshot = Ipc::CreateSurface(bufSize);
Ipc::Mailbox* mailbox = Ipc::CreateMailbox();
if (live == nullptr || snapshot == nullptr || mailbox == nullptr) {
if (live != nullptr) Ipc::ReleaseSurface(live);
if (snapshot != nullptr) Ipc::ReleaseSurface(snapshot);
if (mailbox != nullptr) Ipc::ReleaseMailbox(mailbox, false, false);
return -1;
}
Ipc::RetainSurface(live);
Ipc::RetainSurface(snapshot);
Ipc::RetainMailbox(mailbox, true, true);
uint64_t userVa = 0;
if (Ipc::MapSurfaceForPid(live, ownerPid, ownerPml4, heapNext, userVa) != 0) {
Ipc::ReleaseMailbox(mailbox, true, true);
Ipc::ReleaseSurface(snapshot);
Ipc::ReleaseSurface(live);
return -1;
}
WindowSlot& slot = g_slots[slotIdx];
memset(&slot, 0, sizeof(WindowSlot));
ResetSlotLocked(slot);
slot.used = true;
slot.ownerPid = ownerPid;
slot.width = w;
slot.height = h;
slot.pixelNumPages = numPages;
slot.eventHead = 0;
slot.eventTail = 0;
slot.liveSurface = live;
slot.snapshotSurface = snapshot;
slot.eventMailbox = mailbox;
slot.ownerVa = userVa;
slot.dirty = false;
slot.desktopVa = 0;
slot.desktopPid = 0;
slot.cursor = 0;
// Copy title
int tlen = 0;
while (title[tlen] && tlen < 63) {
slot.title[tlen] = title[tlen];
@@ -153,127 +155,55 @@ namespace WinServer {
}
slot.title[tlen] = '\0';
// Allocate live pixel pages (app renders here) and snapshot pages
// (compositor reads here). Present copies live -> snapshot.
uint64_t userVa = heapNext;
for (int i = 0; i < numPages; i++) {
void* page = Memory::g_pfa->AllocateZeroed();
if (page == nullptr) {
slot.used = false;
return -1;
}
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
slot.pixelPhysPages[i] = physAddr;
if (!Memory::VMM::Paging::MapUserIn(ownerPml4, physAddr, userVa + (uint64_t)i * 0x1000)) {
slot.used = false;
return -1;
}
// Allocate corresponding snapshot page
void* snapPage = Memory::g_pfa->AllocateZeroed();
if (snapPage == nullptr) {
slot.used = false;
return -1;
}
slot.snapshotPhysPages[i] = Memory::SubHHDM((uint64_t)snapPage);
}
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) {
WsGuard guard;
ProcessRetiredLocked();
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
// Unmap LIVE pixel pages from the owner's address space so that
// FreeUserHalf() won't double-free them when the process exits.
// Physical page reclamation is deferred below to avoid stale-TLB
// consumers reading freed memory.
{
auto* ownerProc = Sched::GetProcessByPid(slot.ownerPid);
if (ownerProc) {
for (int p = 0; p < slot.pixelNumPages; p++) {
Memory::VMM::Paging::UnmapUserIn(
ownerProc->pml4Phys,
slot.ownerVa + (uint64_t)p * 0x1000);
}
}
}
// Retire both the owner-facing live pages and the compositor-facing
// snapshot pages. The desktop can still hold stale mappings to the
// snapshot pages for a short time after Enumerate notices the window
// is gone, so freeing them immediately turns that stale mapping into
// a use-after-free.
RetirePageBatchLocked(slot.pixelPhysPages, slot.pixelNumPages);
RetirePageBatchLocked(slot.snapshotPhysPages, slot.pixelNumPages);
slot.used = false;
slot.pixelNumPages = 0;
slot.ownerVa = 0;
slot.desktopVa = 0;
slot.desktopPid = 0;
ReleaseSlotResourcesLocked(slot, true);
return 0;
}
int Present(int windowId, int callerPid) {
// Validate ownership under the lock (brief)
wsLock.Acquire();
ProcessRetiredLocked();
if (windowId < 0 || windowId >= MaxWindows) { wsLock.Release(); return -1; }
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) { wsLock.Release(); return -1; }
int numPages = slot.pixelNumPages;
wsLock.Release();
WsGuard guard;
if (windowId < 0 || windowId >= MaxWindows) return -1;
// Snapshot memcpy OUTSIDE the lock. This is safe because only the
// owning process calls Present/Resize, and a process runs on one
// CPU at a time. Holding wsLock during an 8MB copy would block
// ALL other WinServer operations (Poll, Enumerate, SendEvent)
// across all CPUs, causing convoy stalls and lockups.
for (int i = 0; i < numPages; i++) {
void* src = (void*)Memory::HHDM(slot.pixelPhysPages[i]);
void* dst = (void*)Memory::HHDM(slot.snapshotPhysPages[i]);
memcpy(dst, src, 0x1000);
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid ||
slot.liveSurface == nullptr || slot.snapshotSurface == nullptr) {
return -1;
}
// Set dirty under lock
wsLock.Acquire();
if (Ipc::CopySurface(slot.snapshotSurface, slot.liveSurface) != 0) return -1;
slot.dirty = true;
wsLock.Release();
return 0;
}
int Poll(int windowId, int callerPid, Montauk::WinEvent* outEvent) {
WsGuard guard;
ProcessRetiredLocked();
if (windowId < 0 || windowId >= MaxWindows) return -1;
if (windowId < 0 || windowId >= MaxWindows || outEvent == nullptr) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
if (!slot.used || slot.ownerPid != callerPid || slot.eventMailbox == nullptr) return -1;
if (slot.eventHead == slot.eventTail) return 0; // no events
*outEvent = slot.events[slot.eventTail];
slot.eventTail = (slot.eventTail + 1) % MaxEvents;
return 1;
uint16_t len = sizeof(*outEvent);
int rc = Ipc::MailboxRecv(slot.eventMailbox, nullptr, outEvent, &len, true);
if (rc < 0) return -1;
return rc > 0 ? 1 : 0;
}
int Enumerate(Montauk::WinInfo* outArray, int maxCount) {
WsGuard guard;
ProcessRetiredLocked();
int count = 0;
for (int i = 0; i < MaxWindows && count < maxCount; i++) {
if (!g_slots[i].used) continue;
Montauk::WinInfo& info = outArray[count];
info.id = i;
info.ownerPid = g_slots[i].ownerPid;
@@ -282,7 +212,7 @@ namespace WinServer {
info.height = g_slots[i].height;
info.dirty = g_slots[i].dirty ? 1 : 0;
info.cursor = g_slots[i].cursor;
g_slots[i].dirty = false; // clear dirty after read
g_slots[i].dirty = false;
count++;
}
return count;
@@ -290,59 +220,43 @@ namespace WinServer {
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext) {
WsGuard guard;
ProcessRetiredLocked();
if (windowId < 0 || windowId >= MaxWindows) return 0;
WindowSlot& slot = g_slots[windowId];
if (!slot.used) return 0;
if (!slot.used || slot.snapshotSurface == nullptr) return 0;
// If already mapped into this process, return existing VA
if (slot.desktopPid == callerPid && slot.desktopVa != 0) {
return slot.desktopVa;
uint64_t outVa = 0;
if (Ipc::MapSurfaceForPid(slot.snapshotSurface, callerPid, callerPml4, heapNext, outVa) != 0) {
return 0;
}
uint64_t userVa = heapNext;
// Map the SNAPSHOT pages (not live pages) so the compositor
// always reads a complete frame captured at Present time.
for (int i = 0; i < slot.pixelNumPages; i++) {
if (!Memory::VMM::Paging::MapUserIn(callerPml4, slot.snapshotPhysPages[i],
userVa + (uint64_t)i * 0x1000)) {
return 0;
}
}
slot.desktopVa = userVa;
slot.desktopPid = callerPid;
heapNext += (uint64_t)slot.pixelNumPages * 0x1000;
return userVa;
return outVa;
}
// Internal: send event without acquiring lock (caller must hold wsLock)
static int SendEventLocked(int windowId, const Montauk::WinEvent* event) {
if (windowId < 0 || windowId >= MaxWindows) return -1;
int Unmap(int windowId, int callerPid, uint64_t callerPml4) {
WsGuard guard;
if (windowId < 0 || windowId >= MaxWindows) {
return UnmapRetiredSnapshotsLocked(windowId, callerPid, callerPml4);
}
int result = UnmapRetiredSnapshotsLocked(windowId, callerPid, callerPml4);
WindowSlot& slot = g_slots[windowId];
if (!slot.used) return -1;
if (!slot.used || slot.snapshotSurface == nullptr) return result;
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;
int current = Ipc::UnmapSurfaceForPid(slot.snapshotSurface, callerPid, callerPml4);
if (current == 0) return 0;
return result;
}
int SendEvent(int windowId, const Montauk::WinEvent* event) {
WsGuard guard;
ProcessRetiredLocked();
return SendEventLocked(windowId, event);
}
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
uint64_t& heapNext, uint64_t& outVa) {
WsGuard guard;
ProcessRetiredLocked();
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
if (newW <= 0 || newH <= 0 || newW > 16384 || newH > 16384) return -1;
@@ -351,56 +265,48 @@ namespace WinServer {
return 0;
}
uint64_t bufSize = (uint64_t)newW * newH * 4;
int numPages = (int)((bufSize + 0xFFF) / 0x1000);
if (numPages > MaxPixelPages) return -1;
// Unmap old LIVE pixel pages from the owner's address space. Actual
// physical page reclamation is deferred below for both live and
// snapshot pages so stale desktop mappings cannot turn into
// use-after-free reads during remap.
int oldNumPages = slot.pixelNumPages;
for (int i = 0; i < oldNumPages; i++) {
Memory::VMM::Paging::UnmapUserIn(
ownerPml4, slot.ownerVa + (uint64_t)i * 0x1000);
}
RetirePageBatchLocked(slot.pixelPhysPages, oldNumPages);
RetirePageBatchLocked(slot.snapshotPhysPages, oldNumPages);
// Allocate new live + snapshot pages and map live 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) return -1;
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
slot.pixelPhysPages[i] = physAddr;
if (!Memory::VMM::Paging::MapUserIn(ownerPml4, physAddr, userVa + (uint64_t)i * 0x1000)) {
return -1;
}
void* snapPage = Memory::g_pfa->AllocateZeroed();
if (snapPage == nullptr) return -1;
slot.snapshotPhysPages[i] = Memory::SubHHDM((uint64_t)snapPage);
uint64_t bufSize = (uint64_t)newW * (uint64_t)newH * 4ULL;
Ipc::Surface* newLive = Ipc::CreateSurface(bufSize);
Ipc::Surface* newSnapshot = Ipc::CreateSurface(bufSize);
if (newLive == nullptr || newSnapshot == nullptr) {
if (newLive != nullptr) Ipc::ReleaseSurface(newLive);
if (newSnapshot != nullptr) Ipc::ReleaseSurface(newSnapshot);
return -1;
}
Ipc::RetainSurface(newLive);
Ipc::RetainSurface(newSnapshot);
uint64_t newOwnerVa = 0;
if (Ipc::MapSurfaceForPid(newLive, callerPid, ownerPml4, heapNext, newOwnerVa) != 0) {
Ipc::ReleaseSurface(newSnapshot);
Ipc::ReleaseSurface(newLive);
return -1;
}
if (slot.liveSurface != nullptr) {
Ipc::UnmapSurfaceForPid(slot.liveSurface, callerPid, ownerPml4);
Ipc::ReleaseSurface(slot.liveSurface);
}
if (slot.snapshotSurface != nullptr) {
RetireSnapshotLocked(windowId, slot.snapshotSurface);
}
slot.liveSurface = newLive;
slot.snapshotSurface = newSnapshot;
slot.width = newW;
slot.height = newH;
slot.pixelNumPages = numPages;
slot.ownerVa = userVa;
heapNext += (uint64_t)numPages * 0x1000;
slot.ownerVa = newOwnerVa;
slot.dirty = true;
// Invalidate desktop mapping so it re-maps on next enumerate
slot.desktopVa = 0;
slot.desktopPid = 0;
outVa = userVa;
outVa = newOwnerVa;
return 0;
}
int SetCursor(int windowId, int callerPid, int cursor) {
WsGuard guard;
ProcessRetiredLocked();
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
slot.cursor = (uint8_t)cursor;
@@ -409,14 +315,11 @@ namespace WinServer {
int SetScale(int scale) {
WsGuard guard;
ProcessRetiredLocked();
if (scale < 0) scale = 0;
if (scale > 2) scale = 2;
g_uiScale = scale;
// Broadcast scale event to all active windows
Montauk::WinEvent ev;
memset(&ev, 0, sizeof(ev));
Montauk::WinEvent ev{};
ev.type = 4;
ev.scale.scale = scale;
for (int i = 0; i < MaxWindows; i++) {
@@ -433,42 +336,9 @@ namespace WinServer {
void CleanupProcess(int pid) {
WsGuard guard;
ProcessRetiredLocked();
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;
// Do NOT free live pixel pages here -- they are still mapped
// in the owner's page tables and FreeUserHalf() (called right
// after CleanupProcess) will free them.
// Snapshot pages can still be mapped into the desktop, so
// retire them instead of freeing them immediately.
RetirePageBatchLocked(g_slots[i].snapshotPhysPages, g_slots[i].pixelNumPages);
g_slots[i].used = false;
g_slots[i].pixelNumPages = 0;
g_slots[i].ownerVa = 0;
g_slots[i].desktopVa = 0;
g_slots[i].desktopPid = 0;
}
// If this process had windows mapped INTO it (was the desktop viewer),
// unmap those pixel pages so FreeUserHalf() won't free pages owned
// by other processes.
if (g_slots[i].used && g_slots[i].desktopPid == pid) {
auto* proc = Sched::GetProcessByPid(pid);
if (proc) {
for (int p = 0; p < g_slots[i].pixelNumPages; p++) {
Memory::VMM::Paging::UnmapUserIn(
proc->pml4Phys,
g_slots[i].desktopVa + (uint64_t)p * 0x1000);
}
}
g_slots[i].desktopVa = 0;
g_slots[i].desktopPid = 0;
}
if (!g_slots[i].used || g_slots[i].ownerPid != pid) continue;
ReleaseSlotResourcesLocked(g_slots[i], false);
}
}
+7 -11
View File
@@ -7,28 +7,23 @@
#pragma once
#include "Syscall.hpp"
#include <cstdint>
#include <Ipc/Ipc.hpp>
namespace WinServer {
static constexpr int MaxWindows = 8;
static constexpr int MaxEvents = 64;
static constexpr int MaxPixelPages = 8192; // up to 3840x2160 @ 32bpp = 32MB
struct WindowSlot {
bool used;
int ownerPid;
char title[64];
int width, height;
uint64_t pixelPhysPages[MaxPixelPages]; // live buffer (app writes here)
uint64_t snapshotPhysPages[MaxPixelPages]; // snapshot (compositor reads here)
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
Montauk::WinEvent events[MaxEvents];
int eventHead, eventTail;
Ipc::Surface* liveSurface; // app writes here
Ipc::Surface* snapshotSurface; // compositor/viewers read here
Ipc::Mailbox* eventMailbox; // input/control events for the app
uint64_t ownerVa; // mapped VA of liveSurface in owner
bool dirty;
uint8_t cursor; // cursor style requested by app (0=arrow, 1=resize_h, 2=resize_v)
uint8_t cursor; // 0=arrow, 1=resize_h, 2=resize_v
};
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
@@ -38,6 +33,7 @@ namespace WinServer {
int Poll(int windowId, int callerPid, Montauk::WinEvent* outEvent);
int Enumerate(Montauk::WinInfo* outArray, int maxCount);
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext);
int Unmap(int windowId, int callerPid, uint64_t callerPml4);
int SendEvent(int windowId, const Montauk::WinEvent* event);
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
uint64_t& heapNext, uint64_t& outVa);
+6
View File
@@ -51,6 +51,12 @@ namespace Montauk {
return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext);
}
static int Sys_WinUnmap(int windowId) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr) return -1;
return WinServer::Unmap(windowId, proc->pid, proc->pml4Phys);
}
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
if (event == nullptr) return -1;
return WinServer::SendEvent(windowId, event);