diff --git a/kernel/src/Api/Common.hpp b/kernel/src/Api/Common.hpp index c9e3841..8fbe0e1 100644 --- a/kernel/src/Api/Common.hpp +++ b/kernel/src/Api/Common.hpp @@ -1,37 +1,115 @@ #pragma once + #include +#include 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; } -} \ No newline at end of file + + 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; + } +} diff --git a/kernel/src/Api/Filesystem.hpp b/kernel/src/Api/Filesystem.hpp index 6b822e5..9613dbc 100644 --- a/kernel/src/Api/Filesystem.hpp +++ b/kernel/src/Api/Filesystem.hpp @@ -12,25 +12,26 @@ #include #include #include +#include #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) { diff --git a/kernel/src/Api/Graphics.hpp b/kernel/src/Api/Graphics.hpp index 85fd4e6..5b14009 100644 --- a/kernel/src/Api/Graphics.hpp +++ b/kernel/src/Api/Graphics.hpp @@ -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); diff --git a/kernel/src/Api/IoRedir.hpp b/kernel/src/Api/IoRedir.hpp index 5bd08b7..369799f 100644 --- a/kernel/src/Api/IoRedir.hpp +++ b/kernel/src/Api/IoRedir.hpp @@ -7,8 +7,6 @@ #pragma once #include -#include - #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) { diff --git a/kernel/src/Api/IpcSyscall.hpp b/kernel/src/Api/IpcSyscall.hpp new file mode 100644 index 0000000..c8df954 --- /dev/null +++ b/kernel/src/Api/IpcSyscall.hpp @@ -0,0 +1,101 @@ +/* + * IpcSyscall.hpp + * Generic IPC handle, stream, mailbox, waitset, process, and surface syscalls + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include +#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); + } + +} diff --git a/kernel/src/Api/Keyboard.hpp b/kernel/src/Api/Keyboard.hpp index 26829da..4928f23 100644 --- a/kernel/src/Api/Keyboard.hpp +++ b/kernel/src/Api/Keyboard.hpp @@ -7,6 +7,7 @@ #pragma once #include #include +#include #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; + } } } } diff --git a/kernel/src/Api/Process.hpp b/kernel/src/Api/Process.hpp index d1b59fa..39dc95f 100644 --- a/kernel/src/Api/Process.hpp +++ b/kernel/src/Api/Process.hpp @@ -14,6 +14,7 @@ #include #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; diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 283fd2f..33bb90b 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -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; } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index dd5f71f..7659650 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -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 diff --git a/kernel/src/Api/Terminal.hpp b/kernel/src/Api/Terminal.hpp index 3759b07..fe08ed2 100644 --- a/kernel/src/Api/Terminal.hpp +++ b/kernel/src/Api/Terminal.hpp @@ -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); } -}; \ No newline at end of file +}; diff --git a/kernel/src/Api/WinServer.cpp b/kernel/src/Api/WinServer.cpp index c60e0ed..5de78a8 100644 --- a/kernel/src/Api/WinServer.cpp +++ b/kernel/src/Api/WinServer.cpp @@ -5,118 +5,104 @@ */ #include "WinServer.hpp" -#include -#include -#include #include #include #include #include -#include 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); } } diff --git a/kernel/src/Api/WinServer.hpp b/kernel/src/Api/WinServer.hpp index 0679eeb..eca95a1 100644 --- a/kernel/src/Api/WinServer.hpp +++ b/kernel/src/Api/WinServer.hpp @@ -7,28 +7,23 @@ #pragma once #include "Syscall.hpp" #include +#include 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); diff --git a/kernel/src/Api/Window.hpp b/kernel/src/Api/Window.hpp index d5c294d..dd707da 100644 --- a/kernel/src/Api/Window.hpp +++ b/kernel/src/Api/Window.hpp @@ -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); diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index 604f5e1..6ec881b 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -10,14 +10,7 @@ namespace Fs::Vfs { - struct HandleEntry { - bool inUse; - int driveNumber; - int localHandle; - }; - static FsDriver* driveTable[MaxDrives]; - static HandleEntry handleTable[MaxHandles]; // Protects handle table and driver dispatch from concurrent CPU access. // Uses Mutex (not Spinlock) so interrupts stay enabled while held -- @@ -49,22 +42,12 @@ namespace Fs::Vfs { return true; } - static int AllocHandle() { - for (int i = 0; i < MaxHandles; i++) { - if (!handleTable[i].inUse) return i; - } - return -1; - } - void Initialize() { for (int i = 0; i < MaxDrives; i++) { driveTable[i] = nullptr; } - for (int i = 0; i < MaxHandles; i++) { - handleTable[i].inUse = false; - } - Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives, " << MaxHandles << " handles)"; + Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives)"; } int RegisterDrive(int driveNumber, FsDriver* driver) { @@ -76,7 +59,10 @@ namespace Fs::Vfs { return 0; } - int VfsOpen(const char* path) { + int OpenBackendFile(const char* path, BackendFile& outFile) { + outFile.driveNumber = -1; + outFile.localHandle = -1; + int drive; const char* localPath; @@ -84,67 +70,86 @@ namespace Fs::Vfs { if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1; vfsLock.Acquire(); - int localHandle = driveTable[drive]->Open(localPath); - if (localHandle < 0) { vfsLock.Release(); return -1; } + vfsLock.Release(); + if (localHandle < 0) return -1; - int globalHandle = AllocHandle(); - if (globalHandle < 0) { - driveTable[drive]->Close(localHandle); + outFile.driveNumber = drive; + outFile.localHandle = localHandle; + return 0; + } + + int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) { + vfsLock.Acquire(); + if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr || + file.localHandle < 0) { vfsLock.Release(); return -1; } - handleTable[globalHandle].inUse = true; - handleTable[globalHandle].driveNumber = drive; - handleTable[globalHandle].localHandle = localHandle; - - vfsLock.Release(); - return globalHandle; - } - - int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) { - vfsLock.Acquire(); - if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) { vfsLock.Release(); return -1; } - - HandleEntry& entry = handleTable[handle]; - int result = driveTable[entry.driveNumber]->Read(entry.localHandle, buffer, offset, size); + int result = driveTable[file.driveNumber]->Read(file.localHandle, buffer, offset, size); vfsLock.Release(); return result; } - uint64_t VfsGetSize(int handle) { + uint64_t GetBackendFileSize(const BackendFile& file) { vfsLock.Acquire(); - if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) { vfsLock.Release(); return 0; } + if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr || + file.localHandle < 0) { + vfsLock.Release(); + return 0; + } - HandleEntry& entry = handleTable[handle]; - uint64_t result = driveTable[entry.driveNumber]->GetSize(entry.localHandle); + uint64_t result = driveTable[file.driveNumber]->GetSize(file.localHandle); vfsLock.Release(); return result; } - void VfsClose(int handle) { + bool BackendFileCanWrite(const BackendFile& file) { vfsLock.Acquire(); - if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) { vfsLock.Release(); return; } - - HandleEntry& entry = handleTable[handle]; - driveTable[entry.driveNumber]->Close(entry.localHandle); - entry.inUse = false; + bool canWrite = file.driveNumber >= 0 && file.driveNumber < MaxDrives && + driveTable[file.driveNumber] != nullptr && + file.localHandle >= 0 && + driveTable[file.driveNumber]->Write != nullptr; vfsLock.Release(); + return canWrite; } - int VfsWrite(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size) { + void CloseBackendFile(BackendFile& file) { vfsLock.Acquire(); - if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) { vfsLock.Release(); return -1; } + if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr || + file.localHandle < 0) { + vfsLock.Release(); + file.driveNumber = -1; + file.localHandle = -1; + return; + } - HandleEntry& entry = handleTable[handle]; - if (driveTable[entry.driveNumber]->Write == nullptr) { vfsLock.Release(); return -1; } - int result = driveTable[entry.driveNumber]->Write(entry.localHandle, buffer, offset, size); + driveTable[file.driveNumber]->Close(file.localHandle); + vfsLock.Release(); + + file.driveNumber = -1; + file.localHandle = -1; + } + + int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) { + vfsLock.Acquire(); + if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr || + file.localHandle < 0) { + vfsLock.Release(); + return -1; + } + + if (driveTable[file.driveNumber]->Write == nullptr) { vfsLock.Release(); return -1; } + int result = driveTable[file.driveNumber]->Write(file.localHandle, buffer, offset, size); vfsLock.Release(); return result; } - int VfsCreate(const char* path) { + int CreateBackendFile(const char* path, BackendFile& outFile) { + outFile.driveNumber = -1; + outFile.localHandle = -1; + int drive; const char* localPath; @@ -155,20 +160,12 @@ namespace Fs::Vfs { vfsLock.Acquire(); int localHandle = driveTable[drive]->Create(localPath); - if (localHandle < 0) { vfsLock.Release(); return -1; } - - int globalHandle = AllocHandle(); - if (globalHandle < 0) { - vfsLock.Release(); - return -1; - } - - handleTable[globalHandle].inUse = true; - handleTable[globalHandle].driveNumber = drive; - handleTable[globalHandle].localHandle = localHandle; - vfsLock.Release(); - return globalHandle; + if (localHandle < 0) return -1; + + outFile.driveNumber = drive; + outFile.localHandle = localHandle; + return 0; } int VfsDelete(const char* path) { diff --git a/kernel/src/Fs/Vfs.hpp b/kernel/src/Fs/Vfs.hpp index 162ffcf..1f5c8b5 100644 --- a/kernel/src/Fs/Vfs.hpp +++ b/kernel/src/Fs/Vfs.hpp @@ -11,7 +11,11 @@ namespace Fs::Vfs { static constexpr int MaxDrives = 16; - static constexpr int MaxHandles = 64; + + struct BackendFile { + int driveNumber; + int localHandle; + }; struct FsDriver { int (*Open)(const char* path); @@ -29,13 +33,15 @@ namespace Fs::Vfs { void Initialize(); int RegisterDrive(int driveNumber, FsDriver* driver); - int VfsOpen(const char* path); - int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size); - int VfsWrite(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size); - int VfsCreate(const char* path); + int OpenBackendFile(const char* path, BackendFile& outFile); + int CreateBackendFile(const char* path, BackendFile& outFile); + int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size); + int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size); + uint64_t GetBackendFileSize(const BackendFile& file); + bool BackendFileCanWrite(const BackendFile& file); + void CloseBackendFile(BackendFile& file); + int VfsDelete(const char* path); - uint64_t VfsGetSize(int handle); - void VfsClose(int handle); int VfsReadDir(const char* path, const char** outNames, int maxEntries); int VfsMkdir(const char* path); int VfsRename(const char* oldPath, const char* newPath); diff --git a/kernel/src/Ipc/Ipc.cpp b/kernel/src/Ipc/Ipc.cpp new file mode 100644 index 0000000..d096902 --- /dev/null +++ b/kernel/src/Ipc/Ipc.cpp @@ -0,0 +1,1828 @@ +#include "Ipc.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ipc { + + static constexpr int MaxStreams = 64; + static constexpr int MaxMailboxes = 64; + static constexpr int MaxMailboxMessages = 64; + static constexpr int MaxMailboxMessageBytes = 128; + static constexpr int MaxFiles = 128; + static constexpr int MaxSockets = 64; + static constexpr uint32_t UdpRingSize = 4096; + static constexpr int MaxSurfaces = 32; + static constexpr int MaxSurfacePages = 8192; + static constexpr int MaxProcessObjects = 512; + static constexpr int MaxWaitsets = 32; + static constexpr int MaxWaitsetEntries = 16; + static constexpr int SocketTypeTcp = 1; + static constexpr int SocketTypeUdp = 2; + + struct Object { + HandleType type; + bool active; + uint32_t refs; + kcp::Mutex lock; + }; + + struct Stream : Object { + uint8_t* buffer; + uint32_t capacity; + uint32_t head; + uint32_t tail; + uint32_t count; + uint32_t readerRefs; + uint32_t writerRefs; + }; + + struct MailboxMessage { + uint32_t type; + uint16_t size; + uint8_t attachmentType; + uint8_t hasAttachment; + uint32_t attachmentRights; + Object* attachmentObject; + uint8_t data[MaxMailboxMessageBytes]; + }; + + struct Mailbox : Object { + MailboxMessage messages[MaxMailboxMessages]; + uint32_t head; + uint32_t tail; + uint32_t count; + uint32_t senderRefs; + uint32_t receiverRefs; + }; + + struct File : Object { + Fs::Vfs::BackendFile backend; + }; + + struct UdpDgramHeader { + uint32_t srcIp; + uint16_t srcPort; + uint16_t dataLen; + }; + + struct Socket : Object { + int socketType; + Net::Tcp::Connection* tcpConn; + uint16_t localPort; + bool udpBound; + uint8_t _pad[1]; + uint8_t udpRing[UdpRingSize]; + uint32_t udpHead; + uint32_t udpTail; + uint32_t udpCount; + kcp::Spinlock socketLock; + }; + + struct Surface : Object { + uint64_t physPages[MaxSurfacePages]; + uint32_t numPages; + uint64_t sizeBytes; + }; + + struct ProcessObject : Object { + int pid; + bool exited; + }; + + struct WaitsetEntry { + bool used; + HandleType type; + Object* object; + uint32_t rights; + uint32_t signals; + }; + + struct Waitset : Object { + WaitsetEntry entries[MaxWaitsetEntries]; + }; + + struct SurfaceMap { + bool used; + Surface* surface; + uint64_t va; + uint32_t numPages; + }; + + static HandleEntry g_handleTables[Sched::MaxProcesses][MaxHandlesPerProcess] = {}; + static SurfaceMap g_surfaceMaps[Sched::MaxProcesses][MaxSurfaceMapsPerProcess] = {}; + + static Stream g_streams[MaxStreams] = {}; + static Mailbox g_mailboxes[MaxMailboxes] = {}; + static File g_files[MaxFiles] = {}; + static Socket g_sockets[MaxSockets] = {}; + static Surface g_surfaces[MaxSurfaces] = {}; + static ProcessObject g_processObjects[MaxProcessObjects] = {}; + static Waitset g_waitsets[MaxWaitsets] = {}; + static ProcessObject* g_processObjectsBySlot[Sched::MaxProcesses] = {}; + + static kcp::Mutex g_streamPoolLock; + static kcp::Mutex g_mailboxPoolLock; + static kcp::Mutex g_filePoolLock; + static kcp::Mutex g_socketPoolLock; + static kcp::Mutex g_surfacePoolLock; + static kcp::Mutex g_processPoolLock; + static kcp::Mutex g_waitsetPoolLock; + static kcp::Mutex g_ephemeralPortLock; + static uint16_t g_nextEphemeralPort = 49152; + + static void ReleaseRawObject(Object* object); + + static void InitObject(Object& object, HandleType type) { + object.type = type; + object.active = true; + object.refs = 0; + } + + static void RetainRawObject(Object* object) { + if (object == nullptr) return; + object->lock.Acquire(); + object->refs++; + object->lock.Release(); + } + + static void DestroyStream(Stream* stream) { + if (stream->buffer != nullptr) { + int numPages = (int)((stream->capacity + 0xFFFu) / 0x1000u); + Memory::g_pfa->Free(stream->buffer, numPages); + stream->buffer = nullptr; + } + stream->capacity = 0; + stream->head = 0; + stream->tail = 0; + stream->count = 0; + stream->readerRefs = 0; + stream->writerRefs = 0; + } + + static void DestroyFile(File* file) { + if (file == nullptr) return; + Fs::Vfs::CloseBackendFile(file->backend); + file->backend.driveNumber = -1; + file->backend.localHandle = -1; + } + + static void DestroyMailbox(Mailbox* mailbox) { + if (mailbox == nullptr) return; + for (uint32_t i = 0; i < MaxMailboxMessages; i++) { + if (!mailbox->messages[i].hasAttachment || mailbox->messages[i].attachmentObject == nullptr) continue; + ReleaseRawObject(mailbox->messages[i].attachmentObject); + mailbox->messages[i].attachmentObject = nullptr; + mailbox->messages[i].attachmentType = (uint8_t)HandleType::None; + mailbox->messages[i].attachmentRights = 0; + mailbox->messages[i].hasAttachment = 0; + } + mailbox->head = 0; + mailbox->tail = 0; + mailbox->count = 0; + mailbox->senderRefs = 0; + mailbox->receiverRefs = 0; + } + + static void DestroySocket(Socket* socket) { + if (socket == nullptr) return; + + socket->socketLock.Acquire(); + Net::Tcp::Connection* tcpConn = socket->tcpConn; + bool udpBound = socket->udpBound; + uint16_t localPort = socket->localPort; + socket->tcpConn = nullptr; + socket->udpBound = false; + socket->localPort = 0; + socket->udpHead = 0; + socket->udpTail = 0; + socket->udpCount = 0; + socket->socketType = 0; + socket->socketLock.Release(); + + if (tcpConn != nullptr) { + Net::Tcp::Close(tcpConn); + } + if (udpBound && localPort != 0) { + Net::Udp::Unbind(localPort); + } + } + + static void DestroySurface(Surface* surface) { + for (uint32_t i = 0; i < surface->numPages; i++) { + if (surface->physPages[i] != 0) { + Memory::g_pfa->Free((void*)Memory::HHDM(surface->physPages[i])); + surface->physPages[i] = 0; + } + } + surface->numPages = 0; + surface->sizeBytes = 0; + } + + static void DestroyWaitset(Waitset* waitset) { + for (int i = 0; i < MaxWaitsetEntries; i++) { + if (!waitset->entries[i].used) continue; + ReleaseRawObject(waitset->entries[i].object); + waitset->entries[i].used = false; + waitset->entries[i].object = nullptr; + waitset->entries[i].rights = 0; + waitset->entries[i].signals = 0; + waitset->entries[i].type = HandleType::None; + } + } + + static void ReleaseRawObject(Object* object) { + if (object == nullptr) return; + + bool destroy = false; + HandleType type = HandleType::None; + + object->lock.Acquire(); + if (object->refs > 0) { + object->refs--; + } + if (object->refs == 0) { + destroy = true; + type = object->type; + object->active = false; + } + object->lock.Release(); + + if (!destroy) return; + + switch (type) { + case HandleType::Stream: + DestroyStream((Stream*)object); + break; + case HandleType::Mailbox: + DestroyMailbox((Mailbox*)object); + break; + case HandleType::File: + DestroyFile((File*)object); + break; + case HandleType::Socket: + DestroySocket((Socket*)object); + break; + case HandleType::Surface: + DestroySurface((Surface*)object); + break; + case HandleType::Process: + break; + case HandleType::Waitset: + DestroyWaitset((Waitset*)object); + break; + default: + break; + } + } + + static uint32_t CurrentSignalsForSnapshot(HandleType type, Object* object, uint32_t rights); + static uint32_t CurrentSocketSignals(Socket* socket, uint32_t rights); + static bool WaitsetCheckReady(Waitset* waitset, WaitsetReady* outReady); + + static uint16_t AllocEphemeralPort() { + g_ephemeralPortLock.Acquire(); + uint16_t port = g_nextEphemeralPort++; + if (g_nextEphemeralPort == 0) g_nextEphemeralPort = 49152; + g_ephemeralPortLock.Release(); + return port; + } + + static void UdpSocketDispatcher(uint32_t srcIp, uint16_t srcPort, + uint16_t dstPort, + const uint8_t* data, uint16_t length) { + for (int i = 0; i < MaxSockets; i++) { + if (!g_sockets[i].active || g_sockets[i].socketType != SocketTypeUdp) continue; + + g_sockets[i].socketLock.Acquire(); + if (!g_sockets[i].udpBound || g_sockets[i].localPort != dstPort) { + g_sockets[i].socketLock.Release(); + continue; + } + + uint32_t needed = sizeof(UdpDgramHeader) + length; + if (g_sockets[i].udpCount + needed > UdpRingSize) { + g_sockets[i].socketLock.Release(); + return; + } + + UdpDgramHeader hdr = {srcIp, srcPort, length}; + const uint8_t* hdrBytes = (const uint8_t*)&hdr; + for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) { + g_sockets[i].udpRing[g_sockets[i].udpTail] = hdrBytes[j]; + g_sockets[i].udpTail = (g_sockets[i].udpTail + 1) % UdpRingSize; + } + for (uint16_t j = 0; j < length; j++) { + g_sockets[i].udpRing[g_sockets[i].udpTail] = data[j]; + g_sockets[i].udpTail = (g_sockets[i].udpTail + 1) % UdpRingSize; + } + g_sockets[i].udpCount += needed; + g_sockets[i].socketLock.Release(); + + NotifyObjectChanged((Object*)&g_sockets[i]); + return; + } + } + + int CurrentSlot() { + auto* proc = Sched::GetCurrentProcessPtr(); + if (proc == nullptr) return -1; + auto* slot0 = Sched::GetProcessSlot(0); + return (int)(proc - slot0); + } + + int SlotForPid(int pid) { + for (int i = 0; i < Sched::MaxProcesses; i++) { + auto* proc = Sched::GetProcessSlot(i); + if (proc == nullptr) continue; + auto state = proc->state; + if (state == Sched::ProcessState::Free) continue; + if (proc->pid == pid) return i; + } + return -1; + } + + static void* AllocContiguousPages(int numPages) { + if (numPages <= 0) return nullptr; + void* first = Memory::g_pfa->AllocateZeroed(); + if (first == nullptr) return nullptr; + if (numPages == 1) return first; + void* span = Memory::g_pfa->ReallocConsecutive(first, numPages); + if (span == nullptr) { + Memory::g_pfa->Free(first); + return nullptr; + } + return span; + } + + static void RetainForHandle(Object* object, HandleType type, uint32_t rights) { + if (object == nullptr) return; + + switch (type) { + case HandleType::Stream: { + auto* stream = (Stream*)object; + stream->lock.Acquire(); + stream->refs++; + if (rights & RightRead) stream->readerRefs++; + if (rights & RightWrite) stream->writerRefs++; + stream->lock.Release(); + break; + } + case HandleType::Mailbox: { + auto* mailbox = (Mailbox*)object; + mailbox->lock.Acquire(); + mailbox->refs++; + if (rights & RightSend) mailbox->senderRefs++; + if (rights & RightRecv) mailbox->receiverRefs++; + mailbox->lock.Release(); + break; + } + default: + RetainRawObject(object); + break; + } + } + + static void ReleaseForHandle(Object* object, HandleType type, uint32_t rights) { + if (object == nullptr) return; + + bool notify = false; + bool destroy = false; + + switch (type) { + case HandleType::Stream: { + auto* stream = (Stream*)object; + stream->lock.Acquire(); + if (rights & RightRead && stream->readerRefs > 0) { + stream->readerRefs--; + notify = true; + } + if (rights & RightWrite && stream->writerRefs > 0) { + stream->writerRefs--; + notify = true; + } + if (stream->refs > 0) stream->refs--; + if (stream->refs == 0) { + destroy = true; + stream->active = false; + } + stream->lock.Release(); + + if (notify) NotifyObjectChanged((Object*)stream); + if (destroy) DestroyStream(stream); + break; + } + case HandleType::Mailbox: { + auto* mailbox = (Mailbox*)object; + mailbox->lock.Acquire(); + if (rights & RightSend && mailbox->senderRefs > 0) { + mailbox->senderRefs--; + notify = true; + } + if (rights & RightRecv && mailbox->receiverRefs > 0) { + mailbox->receiverRefs--; + notify = true; + } + if (mailbox->refs > 0) mailbox->refs--; + if (mailbox->refs == 0) { + destroy = true; + mailbox->active = false; + } + mailbox->lock.Release(); + + if (notify) NotifyObjectChanged((Object*)mailbox); + if (destroy) DestroyMailbox(mailbox); + break; + } + default: + ReleaseRawObject(object); + break; + } + } + + int InstallHandleForSlot(int slot, Object* object, HandleType type, uint32_t rights) { + if (slot < 0 || slot >= Sched::MaxProcesses || object == nullptr) return -1; + + for (int i = 0; i < MaxHandlesPerProcess; i++) { + if (g_handleTables[slot][i].used) continue; + g_handleTables[slot][i].used = true; + g_handleTables[slot][i].rights = rights; + g_handleTables[slot][i].type = type; + g_handleTables[slot][i].object = object; + RetainForHandle(object, type, rights); + return i; + } + return -1; + } + + bool SnapshotHandleForSlot(int slot, int handle, HandleType& type, Object*& object, uint32_t& rights) { + if (slot < 0 || slot >= Sched::MaxProcesses) return false; + if (handle < 0 || handle >= MaxHandlesPerProcess) return false; + + const HandleEntry& entry = g_handleTables[slot][handle]; + if (!entry.used || entry.object == nullptr) return false; + + type = entry.type; + object = entry.object; + rights = entry.rights; + return true; + } + + int CloseHandleForSlot(int slot, int handle) { + if (slot < 0 || slot >= Sched::MaxProcesses) return -1; + if (handle < 0 || handle >= MaxHandlesPerProcess) return -1; + if (!g_handleTables[slot][handle].used) return -1; + + HandleEntry entry = g_handleTables[slot][handle]; + g_handleTables[slot][handle].used = false; + g_handleTables[slot][handle].rights = 0; + g_handleTables[slot][handle].type = HandleType::None; + g_handleTables[slot][handle].object = nullptr; + + ReleaseForHandle(entry.object, entry.type, entry.rights); + return 0; + } + + int CloseHandle(int handle) { + return CloseHandleForSlot(CurrentSlot(), handle); + } + + int DupHandle(int handle) { + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + int slot = CurrentSlot(); + if (!SnapshotHandleForSlot(slot, handle, type, object, rights)) return -1; + if ((rights & RightDup) == 0) return -1; + return InstallHandleForSlot(slot, object, type, rights); + } + + Stream* CreateStream(uint32_t capacity) { + if (capacity == 0) capacity = DefaultStreamCapacity; + + int numPages = (int)((capacity + 0xFFFu) / 0x1000u); + void* buffer = AllocContiguousPages(numPages); + if (buffer == nullptr) return nullptr; + + g_streamPoolLock.Acquire(); + for (int i = 0; i < MaxStreams; i++) { + if (g_streams[i].active) continue; + InitObject(g_streams[i], HandleType::Stream); + g_streams[i].buffer = (uint8_t*)buffer; + g_streams[i].capacity = (uint32_t)numPages * 0x1000u; + g_streams[i].head = 0; + g_streams[i].tail = 0; + g_streams[i].count = 0; + g_streams[i].readerRefs = 0; + g_streams[i].writerRefs = 0; + g_streamPoolLock.Release(); + return &g_streams[i]; + } + g_streamPoolLock.Release(); + + Memory::g_pfa->Free(buffer, numPages); + return nullptr; + } + + int CreateStreamHandlePairForSlot(int slot, uint32_t capacity, int& outReadHandle, int& outWriteHandle) { + outReadHandle = -1; + outWriteHandle = -1; + + Stream* stream = CreateStream(capacity); + if (stream == nullptr) return -1; + + int readHandle = InstallHandleForSlot(slot, (Object*)stream, HandleType::Stream, + RightRead | RightWait | RightDup); + if (readHandle < 0) { + DestroyStream(stream); + stream->active = false; + return -1; + } + + int writeHandle = InstallHandleForSlot(slot, (Object*)stream, HandleType::Stream, + RightWrite | RightWait | RightDup); + if (writeHandle < 0) { + CloseHandleForSlot(slot, readHandle); + return -1; + } + + outReadHandle = readHandle; + outWriteHandle = writeHandle; + return 0; + } + + int CreateStreamHandlePair(uint32_t capacity, int& outReadHandle, int& outWriteHandle) { + return CreateStreamHandlePairForSlot(CurrentSlot(), capacity, outReadHandle, outWriteHandle); + } + + void RetainStream(Stream* stream, bool readSide, bool writeSide) { + if (stream == nullptr) return; + stream->lock.Acquire(); + stream->refs++; + if (readSide) stream->readerRefs++; + if (writeSide) stream->writerRefs++; + stream->lock.Release(); + } + + void ReleaseStream(Stream* stream, bool readSide, bool writeSide) { + if (stream == nullptr) return; + ReleaseForHandle((Object*)stream, HandleType::Stream, + (readSide ? (uint32_t)RightRead : 0u) | + (writeSide ? (uint32_t)RightWrite : 0u)); + } + + int StreamRead(Stream* stream, uint8_t* out, int maxLen, bool /*nonBlocking*/) { + if (stream == nullptr || out == nullptr || maxLen <= 0) return -1; + + stream->lock.Acquire(); + if (stream->count == 0) { + bool closed = stream->writerRefs == 0; + stream->lock.Release(); + return closed ? -1 : 0; + } + + int count = 0; + while (count < maxLen && stream->count > 0) { + out[count++] = stream->buffer[stream->tail]; + stream->tail = (stream->tail + 1) % stream->capacity; + stream->count--; + } + stream->lock.Release(); + + NotifyObjectChanged((Object*)stream); + return count; + } + + int StreamWrite(Stream* stream, const uint8_t* data, int len, bool /*nonBlocking*/) { + if (stream == nullptr || data == nullptr || len <= 0) return -1; + + stream->lock.Acquire(); + if (stream->readerRefs == 0) { + stream->lock.Release(); + return -1; + } + + int written = 0; + while (written < len && stream->count < stream->capacity) { + stream->buffer[stream->head] = data[written++]; + stream->head = (stream->head + 1) % stream->capacity; + stream->count++; + } + stream->lock.Release(); + + if (written > 0) NotifyObjectChanged((Object*)stream); + return written; + } + + int StreamReadHandle(int handle, uint8_t* out, int maxLen) { + if (out == nullptr) return -1; + + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return -1; + if (type != HandleType::Stream || (rights & RightRead) == 0) return -1; + return StreamRead((Stream*)object, out, maxLen, true); + } + + int StreamWriteHandle(int handle, const uint8_t* data, int len) { + if (data == nullptr) return -1; + + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return -1; + if (type != HandleType::Stream || (rights & RightWrite) == 0) return -1; + return StreamWrite((Stream*)object, data, len, true); + } + + bool StreamHasData(Stream* stream) { + if (stream == nullptr) return false; + stream->lock.Acquire(); + bool hasData = stream->count > 0; + stream->lock.Release(); + return hasData; + } + + Mailbox* CreateMailbox() { + g_mailboxPoolLock.Acquire(); + for (int i = 0; i < MaxMailboxes; i++) { + if (g_mailboxes[i].active) continue; + InitObject(g_mailboxes[i], HandleType::Mailbox); + g_mailboxes[i].head = 0; + g_mailboxes[i].tail = 0; + g_mailboxes[i].count = 0; + g_mailboxes[i].senderRefs = 0; + g_mailboxes[i].receiverRefs = 0; + for (int j = 0; j < MaxMailboxMessages; j++) { + g_mailboxes[i].messages[j].type = 0; + g_mailboxes[i].messages[j].size = 0; + g_mailboxes[i].messages[j].attachmentType = (uint8_t)HandleType::None; + g_mailboxes[i].messages[j].hasAttachment = 0; + g_mailboxes[i].messages[j].attachmentRights = 0; + g_mailboxes[i].messages[j].attachmentObject = nullptr; + } + g_mailboxPoolLock.Release(); + return &g_mailboxes[i]; + } + g_mailboxPoolLock.Release(); + return nullptr; + } + + int CreateMailboxHandlePairForSlot(int slot, int& outSendHandle, int& outRecvHandle) { + outSendHandle = -1; + outRecvHandle = -1; + + Mailbox* mailbox = CreateMailbox(); + if (mailbox == nullptr) return -1; + + int sendHandle = InstallHandleForSlot(slot, (Object*)mailbox, HandleType::Mailbox, + RightSend | RightWait | RightDup); + if (sendHandle < 0) { + DestroyMailbox(mailbox); + mailbox->active = false; + return -1; + } + + int recvHandle = InstallHandleForSlot(slot, (Object*)mailbox, HandleType::Mailbox, + RightRecv | RightWait | RightDup); + if (recvHandle < 0) { + CloseHandleForSlot(slot, sendHandle); + return -1; + } + + outSendHandle = sendHandle; + outRecvHandle = recvHandle; + return 0; + } + + int CreateMailboxHandlePair(int& outSendHandle, int& outRecvHandle) { + return CreateMailboxHandlePairForSlot(CurrentSlot(), outSendHandle, outRecvHandle); + } + + void RetainMailbox(Mailbox* mailbox, bool sender, bool receiver) { + if (mailbox == nullptr) return; + mailbox->lock.Acquire(); + mailbox->refs++; + if (sender) mailbox->senderRefs++; + if (receiver) mailbox->receiverRefs++; + mailbox->lock.Release(); + } + + void ReleaseMailbox(Mailbox* mailbox, bool sender, bool receiver) { + if (mailbox == nullptr) return; + ReleaseForHandle((Object*)mailbox, HandleType::Mailbox, + (sender ? (uint32_t)RightSend : 0u) | + (receiver ? (uint32_t)RightRecv : 0u)); + } + + static int MailboxSendInternal(Mailbox* mailbox, int senderSlot, uint32_t msgType, + const void* data, uint16_t len, int attachHandle) { + if (mailbox == nullptr) return -1; + if (len > MaxMailboxMessageBytes) return -1; + + HandleType attachmentType = HandleType::None; + Object* attachmentObject = nullptr; + uint32_t attachmentRights = 0; + if (attachHandle >= 0) { + if (senderSlot < 0) return -1; + if (!SnapshotHandleForSlot(senderSlot, attachHandle, attachmentType, attachmentObject, attachmentRights)) { + return -1; + } + if ((attachmentRights & RightDup) == 0 || attachmentObject == nullptr) return -1; + RetainRawObject(attachmentObject); + } + + mailbox->lock.Acquire(); + if (mailbox->receiverRefs == 0) { + mailbox->lock.Release(); + if (attachmentObject != nullptr) ReleaseRawObject(attachmentObject); + return -1; + } + if (mailbox->count >= MaxMailboxMessages) { + mailbox->lock.Release(); + if (attachmentObject != nullptr) ReleaseRawObject(attachmentObject); + return 0; + } + + MailboxMessage& msg = mailbox->messages[mailbox->head]; + msg.type = msgType; + msg.size = len; + msg.attachmentType = (uint8_t)attachmentType; + msg.hasAttachment = attachmentObject != nullptr ? 1 : 0; + msg.attachmentRights = attachmentRights; + msg.attachmentObject = attachmentObject; + if (len > 0 && data != nullptr) { + memcpy(msg.data, data, len); + } + mailbox->head = (mailbox->head + 1) % MaxMailboxMessages; + mailbox->count++; + mailbox->lock.Release(); + + NotifyObjectChanged((Object*)mailbox); + return len; + } + + int MailboxSend(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len) { + return MailboxSendInternal(mailbox, -1, msgType, data, len, -1); + } + + static int MailboxRecvInternal(Mailbox* mailbox, int receiverSlot, uint32_t* msgType, + void* data, uint16_t* inOutLen, int* outAttachHandle, + bool /*nonBlocking*/) { + if (mailbox == nullptr || inOutLen == nullptr) return -1; + + mailbox->lock.Acquire(); + if (mailbox->count == 0) { + bool closed = mailbox->senderRefs == 0; + mailbox->lock.Release(); + return closed ? -1 : 0; + } + + MailboxMessage& msg = mailbox->messages[mailbox->tail]; + int attachedHandle = -1; + if (msg.hasAttachment) { + if (receiverSlot < 0 || outAttachHandle == nullptr) { + mailbox->lock.Release(); + return -1; + } + + attachedHandle = InstallHandleForSlot(receiverSlot, msg.attachmentObject, + (HandleType)msg.attachmentType, + msg.attachmentRights); + if (attachedHandle < 0) { + mailbox->lock.Release(); + return -1; + } + } + + uint16_t copyLen = msg.size; + if (copyLen > *inOutLen) copyLen = *inOutLen; + if (copyLen > 0 && data != nullptr) { + memcpy(data, msg.data, copyLen); + } + if (msgType != nullptr) *msgType = msg.type; + *inOutLen = copyLen; + if (outAttachHandle != nullptr) *outAttachHandle = attachedHandle; + + mailbox->tail = (mailbox->tail + 1) % MaxMailboxMessages; + mailbox->count--; + Object* attachedObject = msg.attachmentObject; + bool hadAttachment = msg.hasAttachment != 0; + msg.attachmentType = (uint8_t)HandleType::None; + msg.hasAttachment = 0; + msg.attachmentRights = 0; + msg.attachmentObject = nullptr; + mailbox->lock.Release(); + + if (hadAttachment && attachedObject != nullptr) { + ReleaseRawObject(attachedObject); + } + NotifyObjectChanged((Object*)mailbox); + return (int)copyLen; + } + + int MailboxRecv(Mailbox* mailbox, uint32_t* msgType, void* data, uint16_t* inOutLen, bool nonBlocking) { + return MailboxRecvInternal(mailbox, -1, msgType, data, inOutLen, nullptr, nonBlocking); + } + + int MailboxSendHandle(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle) { + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + int slot = CurrentSlot(); + if (!SnapshotHandleForSlot(slot, handle, type, object, rights)) return -1; + if (type != HandleType::Mailbox || (rights & RightSend) == 0) return -1; + return MailboxSendInternal((Mailbox*)object, slot, msgType, data, len, attachHandle); + } + + int MailboxRecvHandle(int handle, uint32_t* msgType, void* data, uint16_t* inOutLen, int* outAttachHandle) { + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + int slot = CurrentSlot(); + if (!SnapshotHandleForSlot(slot, handle, type, object, rights)) return -1; + if (type != HandleType::Mailbox || (rights & RightRecv) == 0) return -1; + return MailboxRecvInternal((Mailbox*)object, slot, msgType, data, inOutLen, outAttachHandle, true); + } + + bool MailboxHasMessage(Mailbox* mailbox) { + if (mailbox == nullptr) return false; + mailbox->lock.Acquire(); + bool hasMsg = mailbox->count > 0; + mailbox->lock.Release(); + return hasMsg; + } + + int OpenFileHandleForSlot(int slot, const char* path, bool create) { + if (slot < 0 || slot >= Sched::MaxProcesses || path == nullptr) return -1; + + Fs::Vfs::BackendFile backend = {-1, -1}; + int result = create ? Fs::Vfs::CreateBackendFile(path, backend) + : Fs::Vfs::OpenBackendFile(path, backend); + if (result < 0) return -1; + + g_filePoolLock.Acquire(); + for (int i = 0; i < MaxFiles; i++) { + if (g_files[i].active) continue; + InitObject(g_files[i], HandleType::File); + g_files[i].backend = backend; + g_filePoolLock.Release(); + + uint32_t rights = RightRead | RightWait | RightDup; + if (Fs::Vfs::BackendFileCanWrite(g_files[i].backend)) { + rights |= RightWrite; + } + + int handle = InstallHandleForSlot(slot, (Object*)&g_files[i], HandleType::File, rights); + if (handle >= 0) return handle; + + DestroyFile(&g_files[i]); + g_files[i].active = false; + return -1; + } + g_filePoolLock.Release(); + + Fs::Vfs::CloseBackendFile(backend); + return -1; + } + + int OpenFileHandle(const char* path) { + return OpenFileHandleForSlot(CurrentSlot(), path, false); + } + + int CreateFileHandle(const char* path) { + return OpenFileHandleForSlot(CurrentSlot(), path, true); + } + + int FileReadHandle(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) { + if (buffer == nullptr) return -1; + + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return -1; + if (type != HandleType::File || (rights & RightRead) == 0) return -1; + return Fs::Vfs::ReadBackendFile(((File*)object)->backend, buffer, offset, size); + } + + int FileWriteHandle(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size) { + if (buffer == nullptr) return -1; + + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return -1; + if (type != HandleType::File || (rights & RightWrite) == 0) return -1; + return Fs::Vfs::WriteBackendFile(((File*)object)->backend, buffer, offset, size); + } + + uint64_t FileGetSizeHandle(int handle) { + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return 0; + if (type != HandleType::File || (rights & RightRead) == 0) return 0; + return Fs::Vfs::GetBackendFileSize(((File*)object)->backend); + } + + static Socket* AllocateSocketObject(int type) { + g_socketPoolLock.Acquire(); + for (int i = 0; i < MaxSockets; i++) { + if (g_sockets[i].active) continue; + InitObject(g_sockets[i], HandleType::Socket); + g_sockets[i].socketType = type; + g_sockets[i].tcpConn = nullptr; + g_sockets[i].localPort = 0; + g_sockets[i].udpBound = false; + g_sockets[i].udpHead = 0; + g_sockets[i].udpTail = 0; + g_sockets[i].udpCount = 0; + g_socketPoolLock.Release(); + return &g_sockets[i]; + } + g_socketPoolLock.Release(); + return nullptr; + } + + static bool SnapshotSocketHandle(int handle, Socket*& outSocket, uint32_t& outRights) { + HandleType type = HandleType::None; + Object* object = nullptr; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, outRights)) return false; + if (type != HandleType::Socket || object == nullptr) return false; + outSocket = (Socket*)object; + return true; + } + + int CreateSocketHandleForSlot(int slot, int type) { + if (slot < 0 || slot >= Sched::MaxProcesses) return -1; + if (type != SocketTypeTcp && type != SocketTypeUdp) return -1; + + Socket* socket = AllocateSocketObject(type); + if (socket == nullptr) return -1; + + int handle = InstallHandleForSlot(slot, (Object*)socket, HandleType::Socket, + RightRead | RightWrite | RightWait | RightManage | RightDup); + if (handle >= 0) return handle; + + DestroySocket(socket); + socket->active = false; + return -1; + } + + int CreateSocketHandle(int type) { + return CreateSocketHandleForSlot(CurrentSlot(), type); + } + + int SocketConnectHandle(int handle, uint32_t ip, uint16_t port) { + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightManage) == 0 || socket->socketType != SocketTypeTcp) return -1; + + socket->socketLock.Acquire(); + if (socket->tcpConn != nullptr) { + socket->socketLock.Release(); + return -1; + } + uint16_t localPort = AllocEphemeralPort(); + socket->localPort = localPort; + socket->socketLock.Release(); + + Net::Tcp::Connection* conn = Net::Tcp::Connect(ip, port, localPort); + if (conn == nullptr) { + socket->socketLock.Acquire(); + if (socket->tcpConn == nullptr) socket->localPort = 0; + socket->socketLock.Release(); + NotifyObjectChanged((Object*)socket); + return -1; + } + + socket->socketLock.Acquire(); + if (socket->tcpConn != nullptr) { + socket->socketLock.Release(); + Net::Tcp::Close(conn); + return -1; + } + socket->tcpConn = conn; + socket->socketLock.Release(); + + NotifyObjectChanged((Object*)socket); + return 0; + } + + int SocketBindHandle(int handle, uint16_t port) { + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightManage) == 0) return -1; + + if (socket->socketType == SocketTypeTcp) { + socket->socketLock.Acquire(); + if (socket->tcpConn != nullptr) { + socket->socketLock.Release(); + return -1; + } + socket->localPort = port; + socket->socketLock.Release(); + NotifyObjectChanged((Object*)socket); + return 0; + } + + if (socket->socketType != SocketTypeUdp) return -1; + + socket->socketLock.Acquire(); + bool alreadyBound = socket->udpBound; + uint16_t oldPort = socket->localPort; + socket->socketLock.Release(); + + if (alreadyBound && oldPort == port) { + return 0; + } + + if (alreadyBound && oldPort != 0 && oldPort != port) { + Net::Udp::Unbind(oldPort); + } + + if (!Net::Udp::Bind(port, UdpSocketDispatcher)) { + if (alreadyBound && oldPort != 0 && oldPort != port) { + Net::Udp::Bind(oldPort, UdpSocketDispatcher); + } + return -1; + } + + socket->socketLock.Acquire(); + socket->localPort = port; + socket->udpBound = true; + socket->socketLock.Release(); + NotifyObjectChanged((Object*)socket); + return 0; + } + + int SocketListenHandle(int handle) { + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightManage) == 0 || socket->socketType != SocketTypeTcp) return -1; + + socket->socketLock.Acquire(); + uint16_t localPort = socket->localPort; + bool busy = socket->tcpConn != nullptr; + socket->socketLock.Release(); + if (busy || localPort == 0) return -1; + + Net::Tcp::Connection* listener = Net::Tcp::Listen(localPort); + if (listener == nullptr) return -1; + + socket->socketLock.Acquire(); + if (socket->tcpConn != nullptr) { + socket->socketLock.Release(); + Net::Tcp::Close(listener); + return -1; + } + socket->tcpConn = listener; + socket->socketLock.Release(); + NotifyObjectChanged((Object*)socket); + return 0; + } + + int SocketAcceptHandle(int handle) { + int slot = CurrentSlot(); + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightManage) == 0 || socket->socketType != SocketTypeTcp) return -1; + + socket->socketLock.Acquire(); + Net::Tcp::Connection* listener = socket->tcpConn; + uint16_t localPort = socket->localPort; + socket->socketLock.Release(); + if (listener == nullptr || Net::Tcp::GetState(listener) != Net::Tcp::State::Listen) return -1; + + Net::Tcp::Connection* clientConn = Net::Tcp::Accept(listener); + if (clientConn == nullptr) return -1; + + Socket* accepted = AllocateSocketObject(SocketTypeTcp); + if (accepted == nullptr) { + Net::Tcp::Close(clientConn); + return -1; + } + + accepted->socketLock.Acquire(); + accepted->tcpConn = clientConn; + accepted->localPort = localPort; + accepted->socketLock.Release(); + + int acceptedHandle = InstallHandleForSlot(slot, (Object*)accepted, HandleType::Socket, + RightRead | RightWrite | RightWait | RightManage | RightDup); + if (acceptedHandle < 0) { + DestroySocket(accepted); + accepted->active = false; + return -1; + } + + NotifyObjectChanged((Object*)socket); + return acceptedHandle; + } + + int SocketSendHandle(int handle, const uint8_t* data, uint32_t len) { + if (data == nullptr) return -1; + + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightWrite) == 0 || socket->socketType != SocketTypeTcp) return -1; + + socket->socketLock.Acquire(); + Net::Tcp::Connection* conn = socket->tcpConn; + socket->socketLock.Release(); + if (conn == nullptr) return -1; + return Net::Tcp::Send(conn, data, (uint16_t)len); + } + + int SocketRecvHandle(int handle, uint8_t* buffer, uint32_t maxLen) { + if (buffer == nullptr) return -1; + + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightRead) == 0 || socket->socketType != SocketTypeTcp) return -1; + + socket->socketLock.Acquire(); + Net::Tcp::Connection* conn = socket->tcpConn; + socket->socketLock.Release(); + if (conn == nullptr) return -1; + + int result = Net::Tcp::ReceiveNonBlocking(conn, buffer, (uint16_t)maxLen); + if (result != 0) NotifyObjectChanged((Object*)socket); + return result; + } + + int SocketSendToHandle(int handle, const uint8_t* data, uint32_t len, uint32_t destIp, uint16_t destPort) { + if (data == nullptr) return -1; + + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightWrite) == 0 || socket->socketType != SocketTypeUdp) return -1; + + socket->socketLock.Acquire(); + uint16_t localPort = socket->localPort; + bool udpBound = socket->udpBound; + socket->socketLock.Release(); + + if (!udpBound || localPort == 0) { + localPort = AllocEphemeralPort(); + if (!Net::Udp::Bind(localPort, UdpSocketDispatcher)) return -1; + + socket->socketLock.Acquire(); + socket->localPort = localPort; + socket->udpBound = true; + socket->socketLock.Release(); + NotifyObjectChanged((Object*)socket); + } + + if (!Net::Udp::Send(destIp, localPort, destPort, data, (uint16_t)len)) { + return -1; + } + return (int)len; + } + + int SocketRecvFromHandle(int handle, uint8_t* buffer, uint32_t maxLen, uint32_t* srcIp, uint16_t* srcPort) { + if (buffer == nullptr) return -1; + + Socket* socket = nullptr; + uint32_t rights = 0; + if (!SnapshotSocketHandle(handle, socket, rights)) return -1; + if ((rights & RightRead) == 0 || socket->socketType != SocketTypeUdp) return -1; + + socket->socketLock.Acquire(); + if (socket->udpCount < sizeof(UdpDgramHeader)) { + socket->socketLock.Release(); + return -1; + } + + UdpDgramHeader hdr = {}; + uint8_t* hdrBytes = (uint8_t*)&hdr; + for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) { + hdrBytes[j] = socket->udpRing[socket->udpHead]; + socket->udpHead = (socket->udpHead + 1) % UdpRingSize; + } + socket->udpCount -= sizeof(UdpDgramHeader); + + uint16_t copyLen = hdr.dataLen; + if (copyLen > maxLen) copyLen = (uint16_t)maxLen; + for (uint16_t j = 0; j < copyLen; j++) { + buffer[j] = socket->udpRing[socket->udpHead]; + socket->udpHead = (socket->udpHead + 1) % UdpRingSize; + } + for (uint16_t j = copyLen; j < hdr.dataLen; j++) { + socket->udpHead = (socket->udpHead + 1) % UdpRingSize; + } + socket->udpCount -= hdr.dataLen; + socket->socketLock.Release(); + + if (srcIp != nullptr) *srcIp = hdr.srcIp; + if (srcPort != nullptr) *srcPort = hdr.srcPort; + + NotifyObjectChanged((Object*)socket); + return (int)copyLen; + } + + void NotifyTcpConnectionChanged(Net::Tcp::Connection* connection) { + if (connection == nullptr) return; + + for (int i = 0; i < MaxSockets; i++) { + if (!g_sockets[i].active || g_sockets[i].socketType != SocketTypeTcp) continue; + + g_sockets[i].socketLock.Acquire(); + bool matches = g_sockets[i].tcpConn == connection; + g_sockets[i].socketLock.Release(); + if (matches) { + NotifyObjectChanged((Object*)&g_sockets[i]); + } + } + } + + Surface* CreateSurface(uint64_t byteSize) { + if (byteSize == 0) byteSize = 0x1000; + uint32_t numPages = (uint32_t)((byteSize + 0xFFFu) / 0x1000u); + if (numPages == 0 || numPages > MaxSurfacePages) return nullptr; + + g_surfacePoolLock.Acquire(); + int slot = -1; + for (int i = 0; i < MaxSurfaces; i++) { + if (!g_surfaces[i].active) { + slot = i; + break; + } + } + if (slot < 0) { + g_surfacePoolLock.Release(); + return nullptr; + } + + InitObject(g_surfaces[slot], HandleType::Surface); + g_surfaces[slot].numPages = numPages; + g_surfaces[slot].sizeBytes = (uint64_t)numPages * 0x1000ULL; + for (uint32_t i = 0; i < MaxSurfacePages; i++) g_surfaces[slot].physPages[i] = 0; + g_surfacePoolLock.Release(); + + for (uint32_t i = 0; i < numPages; i++) { + void* page = Memory::g_pfa->AllocateZeroed(); + if (page == nullptr) { + DestroySurface(&g_surfaces[slot]); + g_surfaces[slot].active = false; + return nullptr; + } + g_surfaces[slot].physPages[i] = Memory::SubHHDM((uint64_t)page); + } + return &g_surfaces[slot]; + } + + int CreateSurfaceHandle(uint64_t byteSize) { + Surface* surface = CreateSurface(byteSize); + if (surface == nullptr) return -1; + + int handle = InstallHandleForSlot(CurrentSlot(), (Object*)surface, HandleType::Surface, + RightMap | RightManage | RightWait | RightDup); + if (handle >= 0) return handle; + + DestroySurface(surface); + surface->active = false; + return -1; + } + + void RetainSurface(Surface* surface) { + RetainRawObject((Object*)surface); + } + + void ReleaseSurface(Surface* surface) { + ReleaseRawObject((Object*)surface); + } + + uint64_t GetSurfaceSize(const Surface* surface) { + if (surface == nullptr) return 0; + return surface->sizeBytes; + } + + int ResizeSurface(Surface* surface, uint64_t newSize) { + if (surface == nullptr) return -1; + if (newSize == 0) newSize = 0x1000; + + uint32_t newPages = (uint32_t)((newSize + 0xFFFu) / 0x1000u); + if (newPages == 0 || newPages > MaxSurfacePages) return -1; + + surface->lock.Acquire(); + for (uint32_t i = 0; i < surface->numPages; i++) { + if (surface->physPages[i] != 0) { + Memory::g_pfa->Free((void*)Memory::HHDM(surface->physPages[i])); + surface->physPages[i] = 0; + } + } + surface->numPages = newPages; + surface->sizeBytes = (uint64_t)newPages * 0x1000ULL; + surface->lock.Release(); + + for (uint32_t i = 0; i < newPages; i++) { + void* page = Memory::g_pfa->AllocateZeroed(); + if (page == nullptr) return -1; + surface->physPages[i] = Memory::SubHHDM((uint64_t)page); + } + NotifyObjectChanged((Object*)surface); + return 0; + } + + uint64_t MapSurfaceHandle(int handle) { + auto* proc = Sched::GetCurrentProcessPtr(); + if (proc == nullptr) return 0; + + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return 0; + if (type != HandleType::Surface || (rights & RightMap) == 0) return 0; + + uint64_t va = 0; + if (MapSurfaceForPid((Surface*)object, proc->pid, proc->pml4Phys, proc->heapNext, va) < 0) { + return 0; + } + return va; + } + + int ResizeSurfaceHandle(int handle, uint64_t newSize) { + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(CurrentSlot(), handle, type, object, rights)) return -1; + if (type != HandleType::Surface || (rights & RightManage) == 0) return -1; + return ResizeSurface((Surface*)object, newSize); + } + + int CopySurface(Surface* dst, Surface* src) { + if (dst == nullptr || src == nullptr) return -1; + if (dst->numPages != src->numPages) return -1; + + for (uint32_t i = 0; i < src->numPages; i++) { + void* srcPage = (void*)Memory::HHDM(src->physPages[i]); + void* dstPage = (void*)Memory::HHDM(dst->physPages[i]); + memcpy(dstPage, srcPage, 0x1000); + } + NotifyObjectChanged((Object*)dst); + return 0; + } + + int MapSurfaceForPid(Surface* surface, int pid, uint64_t pml4Phys, uint64_t& heapNext, uint64_t& outVa) { + if (surface == nullptr) return -1; + + int slot = SlotForPid(pid); + if (slot < 0) return -1; + + for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) { + if (g_surfaceMaps[slot][i].used && g_surfaceMaps[slot][i].surface == surface) { + outVa = g_surfaceMaps[slot][i].va; + return 0; + } + } + + int mapIdx = -1; + for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) { + if (!g_surfaceMaps[slot][i].used) { + mapIdx = i; + break; + } + } + if (mapIdx < 0) return -1; + + uint64_t baseVa = heapNext; + for (uint32_t i = 0; i < surface->numPages; i++) { + if (!Memory::VMM::Paging::MapUserIn(pml4Phys, surface->physPages[i], + baseVa + (uint64_t)i * 0x1000ULL)) { + for (uint32_t j = 0; j < i; j++) { + Memory::VMM::Paging::UnmapUserIn(pml4Phys, baseVa + (uint64_t)j * 0x1000ULL); + } + return -1; + } + } + + g_surfaceMaps[slot][mapIdx].used = true; + g_surfaceMaps[slot][mapIdx].surface = surface; + g_surfaceMaps[slot][mapIdx].va = baseVa; + g_surfaceMaps[slot][mapIdx].numPages = surface->numPages; + RetainRawObject((Object*)surface); + + heapNext += (uint64_t)surface->numPages * 0x1000ULL; + outVa = baseVa; + return 0; + } + + int UnmapSurfaceForPid(Surface* surface, int pid, uint64_t pml4Phys) { + if (surface == nullptr) return -1; + + int slot = SlotForPid(pid); + if (slot < 0) return -1; + + int unmapped = 0; + for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) { + if (!g_surfaceMaps[slot][i].used || g_surfaceMaps[slot][i].surface != surface) continue; + for (uint32_t p = 0; p < g_surfaceMaps[slot][i].numPages; p++) { + Memory::VMM::Paging::UnmapUserIn(pml4Phys, g_surfaceMaps[slot][i].va + (uint64_t)p * 0x1000ULL); + } + g_surfaceMaps[slot][i].used = false; + g_surfaceMaps[slot][i].surface = nullptr; + g_surfaceMaps[slot][i].va = 0; + g_surfaceMaps[slot][i].numPages = 0; + ReleaseRawObject((Object*)surface); + unmapped++; + } + + return unmapped > 0 ? 0 : -1; + } + + ProcessObject* GetProcessObject(int pid) { + g_processPoolLock.Acquire(); + for (int i = 0; i < MaxProcessObjects; i++) { + if (!g_processObjects[i].active) continue; + if (g_processObjects[i].pid == pid) { + g_processPoolLock.Release(); + return &g_processObjects[i]; + } + } + g_processPoolLock.Release(); + return nullptr; + } + + int OpenProcessHandle(int pid) { + ProcessObject* process = GetProcessObject(pid); + if (process == nullptr) return -1; + return InstallHandleForSlot(CurrentSlot(), (Object*)process, HandleType::Process, + RightWait | RightDup); + } + + void ProcessStartedInSlot(int slot, int pid) { + if (slot < 0 || slot >= Sched::MaxProcesses) return; + + g_processPoolLock.Acquire(); + for (int i = 0; i < MaxProcessObjects; i++) { + if (g_processObjects[i].active) continue; + InitObject(g_processObjects[i], HandleType::Process); + g_processObjects[i].pid = pid; + g_processObjects[i].exited = false; + g_processObjectsBySlot[slot] = &g_processObjects[i]; + g_processObjects[i].refs = 1; // liveness reference + g_processPoolLock.Release(); + return; + } + g_processPoolLock.Release(); + Kt::KernelLogStream(Kt::ERROR, "IPC") << "Out of process objects for PID " << (uint64_t)pid; + } + + void ProcessExitedInSlot(int slot, int /*pid*/) { + if (slot < 0 || slot >= Sched::MaxProcesses) return; + + ProcessObject* object = g_processObjectsBySlot[slot]; + if (object == nullptr) return; + + object->lock.Acquire(); + object->exited = true; + object->lock.Release(); + + NotifyObjectChanged((Object*)object); + ReleaseRawObject((Object*)object); + g_processObjectsBySlot[slot] = nullptr; + } + + bool ProcessHasExited(ProcessObject* process) { + if (process == nullptr) return true; + process->lock.Acquire(); + bool exited = process->exited; + process->lock.Release(); + return exited; + } + + static uint32_t CurrentSocketSignals(Socket* socket, uint32_t rights) { + if (socket == nullptr) return SignalNone; + + if (socket->socketType == SocketTypeUdp) { + uint32_t signals = SignalReady; + socket->socketLock.Acquire(); + if ((rights & RightRead) && socket->udpCount >= sizeof(UdpDgramHeader)) { + signals |= SignalReadable; + } + if (rights & RightWrite) { + signals |= SignalWritable; + } + socket->socketLock.Release(); + return signals; + } + + if (socket->socketType != SocketTypeTcp) return SignalNone; + + socket->socketLock.Acquire(); + Net::Tcp::Connection* conn = socket->tcpConn; + socket->socketLock.Release(); + if (conn == nullptr) return SignalNone; + + uint32_t signals = SignalNone; + Net::Tcp::State state = Net::Tcp::GetState(conn); + if (state == Net::Tcp::State::Listen) { + if ((rights & RightRead) && Net::Tcp::HasPendingAccept(conn)) { + signals |= SignalReadable | SignalReady; + } + return signals; + } + + if ((rights & RightRead) && Net::Tcp::HasReceiveData(conn)) { + signals |= SignalReadable; + } + if ((rights & RightWrite) && Net::Tcp::CanSend(conn)) { + signals |= SignalWritable; + } + if ((rights & (RightRead | RightWrite)) && Net::Tcp::IsClosedForIo(conn)) { + signals |= SignalPeerClosed; + } + if (signals != SignalNone) signals |= SignalReady; + return signals; + } + + static uint32_t CurrentSignalsForSnapshot(HandleType type, Object* object, uint32_t rights) { + if (object == nullptr) return SignalNone; + + switch (type) { + case HandleType::Stream: { + auto* stream = (Stream*)object; + uint32_t signals = SignalNone; + stream->lock.Acquire(); + if ((rights & RightRead) && stream->count > 0) signals |= SignalReadable; + if ((rights & RightWrite) && stream->count < stream->capacity && stream->readerRefs > 0) signals |= SignalWritable; + if ((rights & RightRead) && stream->writerRefs == 0) signals |= SignalPeerClosed; + if ((rights & RightWrite) && stream->readerRefs == 0) signals |= SignalPeerClosed; + stream->lock.Release(); + return signals; + } + case HandleType::Mailbox: { + auto* mailbox = (Mailbox*)object; + uint32_t signals = SignalNone; + mailbox->lock.Acquire(); + if ((rights & RightRecv) && mailbox->count > 0) signals |= SignalReadable; + if ((rights & RightSend) && mailbox->count < MaxMailboxMessages && mailbox->receiverRefs > 0) signals |= SignalWritable; + if ((rights & RightRecv) && mailbox->senderRefs == 0) signals |= SignalPeerClosed; + if ((rights & RightSend) && mailbox->receiverRefs == 0) signals |= SignalPeerClosed; + mailbox->lock.Release(); + return signals; + } + case HandleType::File: { + uint32_t signals = SignalReady; + if (rights & RightRead) signals |= SignalReadable; + if (rights & RightWrite) signals |= SignalWritable; + return signals; + } + case HandleType::Socket: + return CurrentSocketSignals((Socket*)object, rights); + case HandleType::Surface: + return (rights & RightMap) ? SignalReady : SignalNone; + case HandleType::Process: { + auto* process = (ProcessObject*)object; + return ProcessHasExited(process) ? SignalExited : SignalNone; + } + case HandleType::Waitset: { + WaitsetReady ready{}; + return WaitsetCheckReady((Waitset*)object, &ready) ? SignalReady : SignalNone; + } + default: + return SignalNone; + } + } + + uint32_t GetHandleSignalsForSlot(int slot, int handle) { + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(slot, handle, type, object, rights)) return SignalNone; + return CurrentSignalsForSnapshot(type, object, rights); + } + + static bool WaitsetCheckReady(Waitset* waitset, WaitsetReady* outReady) { + if (waitset == nullptr) return false; + for (int i = 0; i < MaxWaitsetEntries; i++) { + if (!waitset->entries[i].used || waitset->entries[i].object == nullptr) continue; + uint32_t current = CurrentSignalsForSnapshot(waitset->entries[i].type, + waitset->entries[i].object, + waitset->entries[i].rights); + uint32_t readySignals = current & waitset->entries[i].signals; + if (readySignals == 0) continue; + if (outReady != nullptr) { + outReady->index = i; + outReady->signals = readySignals; + } + return true; + } + return false; + } + + void NotifyObjectChanged(Object* object) { + if (object == nullptr) return; + + Sched::WakeObjectWaiters(object); + + for (int i = 0; i < MaxWaitsets; i++) { + if (!g_waitsets[i].active) continue; + WaitsetReady ready{}; + if (WaitsetCheckReady(&g_waitsets[i], &ready)) { + Sched::WakeObjectWaiters(&g_waitsets[i]); + } + } + } + + uint32_t WaitOnHandle(int handle, uint32_t wantedSignals, uint64_t timeoutMs) { + int slot = CurrentSlot(); + HandleType type = HandleType::None; + Object* object = nullptr; + uint32_t rights = 0; + if (!SnapshotHandleForSlot(slot, handle, type, object, rights)) return (uint32_t)-1; + if ((rights & RightWait) == 0) return (uint32_t)-1; + + uint64_t start = Timekeeping::GetMilliseconds(); + for (;;) { + uint32_t current = CurrentSignalsForSnapshot(type, object, rights); + uint32_t ready = current & wantedSignals; + if (ready != 0) return ready; + + if (timeoutMs == 0) return 0; + if (timeoutMs != ~0ULL) { + uint64_t elapsed = Timekeeping::GetMilliseconds() - start; + if (elapsed >= timeoutMs) return 0; + Sched::BlockOnObject(object, timeoutMs - elapsed); + } else { + Sched::BlockOnObject(object, 0); + } + } + } + + int CreateWaitsetHandleForSlot(int slot) { + if (slot < 0 || slot >= Sched::MaxProcesses) return -1; + + g_waitsetPoolLock.Acquire(); + for (int i = 0; i < MaxWaitsets; i++) { + if (g_waitsets[i].active) continue; + InitObject(g_waitsets[i], HandleType::Waitset); + for (int j = 0; j < MaxWaitsetEntries; j++) { + g_waitsets[i].entries[j].used = false; + g_waitsets[i].entries[j].type = HandleType::None; + g_waitsets[i].entries[j].object = nullptr; + g_waitsets[i].entries[j].rights = 0; + g_waitsets[i].entries[j].signals = 0; + } + g_waitsetPoolLock.Release(); + return InstallHandleForSlot(slot, (Object*)&g_waitsets[i], HandleType::Waitset, + RightWait | RightManage | RightDup); + } + g_waitsetPoolLock.Release(); + return -1; + } + + int CreateWaitsetHandleForCurrent() { + return CreateWaitsetHandleForSlot(CurrentSlot()); + } + + int WaitsetAddHandleForSlot(int slot, int waitsetHandle, int targetHandle, uint32_t signals) { + HandleType waitsetType = HandleType::None; + Object* waitsetObject = nullptr; + uint32_t waitsetRights = 0; + if (!SnapshotHandleForSlot(slot, waitsetHandle, waitsetType, waitsetObject, waitsetRights)) return -1; + if (waitsetType != HandleType::Waitset || (waitsetRights & RightManage) == 0) return -1; + + HandleType targetType = HandleType::None; + Object* targetObject = nullptr; + uint32_t targetRights = 0; + if (!SnapshotHandleForSlot(slot, targetHandle, targetType, targetObject, targetRights)) return -1; + if ((targetRights & RightWait) == 0) return -1; + if (targetType == HandleType::Waitset) return -1; + + auto* waitset = (Waitset*)waitsetObject; + waitset->lock.Acquire(); + for (int i = 0; i < MaxWaitsetEntries; i++) { + if (waitset->entries[i].used) continue; + waitset->entries[i].used = true; + waitset->entries[i].type = targetType; + waitset->entries[i].object = targetObject; + waitset->entries[i].rights = targetRights; + waitset->entries[i].signals = signals; + RetainRawObject(targetObject); + waitset->lock.Release(); + NotifyObjectChanged(waitsetObject); + return i; + } + waitset->lock.Release(); + return -1; + } + + int WaitsetAddHandle(int waitsetHandle, int targetHandle, uint32_t signals) { + return WaitsetAddHandleForSlot(CurrentSlot(), waitsetHandle, targetHandle, signals); + } + + int WaitsetRemoveIndexForSlot(int slot, int waitsetHandle, int index) { + HandleType waitsetType = HandleType::None; + Object* waitsetObject = nullptr; + uint32_t waitsetRights = 0; + if (!SnapshotHandleForSlot(slot, waitsetHandle, waitsetType, waitsetObject, waitsetRights)) return -1; + if (waitsetType != HandleType::Waitset || (waitsetRights & RightManage) == 0) return -1; + if (index < 0 || index >= MaxWaitsetEntries) return -1; + + auto* waitset = (Waitset*)waitsetObject; + waitset->lock.Acquire(); + if (!waitset->entries[index].used) { + waitset->lock.Release(); + return -1; + } + + Object* targetObject = waitset->entries[index].object; + waitset->entries[index].used = false; + waitset->entries[index].type = HandleType::None; + waitset->entries[index].object = nullptr; + waitset->entries[index].rights = 0; + waitset->entries[index].signals = 0; + waitset->lock.Release(); + + ReleaseRawObject(targetObject); + return 0; + } + + int WaitsetRemoveIndex(int waitsetHandle, int index) { + return WaitsetRemoveIndexForSlot(CurrentSlot(), waitsetHandle, index); + } + + int WaitsetWaitHandle(int waitsetHandle, WaitsetReady* outReady, uint64_t timeoutMs) { + int slot = CurrentSlot(); + + HandleType waitsetType = HandleType::None; + Object* waitsetObject = nullptr; + uint32_t waitsetRights = 0; + if (!SnapshotHandleForSlot(slot, waitsetHandle, waitsetType, waitsetObject, waitsetRights)) return -1; + if (waitsetType != HandleType::Waitset || (waitsetRights & RightWait) == 0) return -1; + + uint64_t start = Timekeeping::GetMilliseconds(); + for (;;) { + WaitsetReady ready = {-1, 0}; + if (WaitsetCheckReady((Waitset*)waitsetObject, &ready)) { + if (outReady != nullptr) *outReady = ready; + return 1; + } + + if (timeoutMs == 0) return 0; + if (timeoutMs != ~0ULL) { + uint64_t elapsed = Timekeeping::GetMilliseconds() - start; + if (elapsed >= timeoutMs) return 0; + Sched::BlockOnObject(waitsetObject, timeoutMs - elapsed); + } else { + Sched::BlockOnObject(waitsetObject, 0); + } + } + } + + void CleanupProcessSlot(int slot, int /*pid*/, uint64_t pml4Phys) { + if (slot < 0 || slot >= Sched::MaxProcesses) return; + + for (int h = 0; h < MaxHandlesPerProcess; h++) { + if (g_handleTables[slot][h].used) { + CloseHandleForSlot(slot, h); + } + } + + for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) { + if (!g_surfaceMaps[slot][i].used || g_surfaceMaps[slot][i].surface == nullptr) continue; + for (uint32_t p = 0; p < g_surfaceMaps[slot][i].numPages; p++) { + Memory::VMM::Paging::UnmapUserIn(pml4Phys, g_surfaceMaps[slot][i].va + (uint64_t)p * 0x1000ULL); + } + Object* surfaceObject = (Object*)g_surfaceMaps[slot][i].surface; + g_surfaceMaps[slot][i].used = false; + g_surfaceMaps[slot][i].surface = nullptr; + g_surfaceMaps[slot][i].va = 0; + g_surfaceMaps[slot][i].numPages = 0; + ReleaseRawObject(surfaceObject); + } + } + + void Initialize() { + for (int i = 0; i < Sched::MaxProcesses; i++) { + g_processObjectsBySlot[i] = nullptr; + } + Kt::KernelLogStream(Kt::OK, "IPC") << "Initialized (" + << (uint64_t)MaxHandlesPerProcess << " handles/process, " + << (uint64_t)MaxStreams << " streams, " + << (uint64_t)MaxMailboxes << " mailboxes, " + << (uint64_t)MaxFiles << " files, " + << (uint64_t)MaxSockets << " sockets, " + << (uint64_t)MaxSurfaces << " surfaces)"; + } + +} diff --git a/kernel/src/Ipc/Ipc.hpp b/kernel/src/Ipc/Ipc.hpp new file mode 100644 index 0000000..92b5555 --- /dev/null +++ b/kernel/src/Ipc/Ipc.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include + +namespace Net::Tcp { struct Connection; } + +namespace Ipc { + + static constexpr int MaxHandlesPerProcess = 128; + static constexpr int MaxSurfaceMapsPerProcess = 64; + static constexpr uint32_t DefaultStreamCapacity = 4096; + + enum class HandleType : uint8_t { + None = 0, + Stream, + Mailbox, + File, + Socket, + Surface, + Process, + Waitset + }; + + enum Rights : uint32_t { + RightWait = 1u << 0, + RightRead = 1u << 1, + RightWrite = 1u << 2, + RightSend = 1u << 3, + RightRecv = 1u << 4, + RightMap = 1u << 5, + RightManage = 1u << 6, + RightDup = 1u << 7 + }; + + enum Signals : uint32_t { + SignalNone = 0, + SignalReadable = 1u << 0, + SignalWritable = 1u << 1, + SignalPeerClosed = 1u << 2, + SignalExited = 1u << 3, + SignalReady = 1u << 4 + }; + + struct Object; + struct Stream; + struct Mailbox; + struct File; + struct Socket; + struct Surface; + struct ProcessObject; + struct Waitset; + + struct HandleEntry { + bool used; + uint32_t rights; + HandleType type; + Object* object; + }; + + struct WaitsetReady { + int32_t index; + uint32_t signals; + }; + + void Initialize(); + + int CurrentSlot(); + int SlotForPid(int pid); + + int InstallHandleForSlot(int slot, Object* object, HandleType type, uint32_t rights); + int CloseHandleForSlot(int slot, int handle); + int CloseHandle(int handle); + int DupHandle(int handle); + + bool SnapshotHandleForSlot(int slot, int handle, HandleType& type, Object*& object, uint32_t& rights); + uint32_t GetHandleSignalsForSlot(int slot, int handle); + + Stream* CreateStream(uint32_t capacity = DefaultStreamCapacity); + int CreateStreamHandlePairForSlot(int slot, uint32_t capacity, int& outReadHandle, int& outWriteHandle); + int CreateStreamHandlePair(uint32_t capacity, int& outReadHandle, int& outWriteHandle); + void RetainStream(Stream* stream, bool readSide, bool writeSide); + void ReleaseStream(Stream* stream, bool readSide, bool writeSide); + int StreamRead(Stream* stream, uint8_t* out, int maxLen, bool nonBlocking); + int StreamWrite(Stream* stream, const uint8_t* data, int len, bool nonBlocking); + int StreamReadHandle(int handle, uint8_t* out, int maxLen); + int StreamWriteHandle(int handle, const uint8_t* data, int len); + bool StreamHasData(Stream* stream); + + Mailbox* CreateMailbox(); + int CreateMailboxHandlePairForSlot(int slot, int& outSendHandle, int& outRecvHandle); + int CreateMailboxHandlePair(int& outSendHandle, int& outRecvHandle); + void RetainMailbox(Mailbox* mailbox, bool sender, bool receiver); + void ReleaseMailbox(Mailbox* mailbox, bool sender, bool receiver); + int MailboxSend(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len); + int MailboxRecv(Mailbox* mailbox, uint32_t* msgType, void* data, uint16_t* inOutLen, bool nonBlocking); + int MailboxSendHandle(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle); + int MailboxRecvHandle(int handle, uint32_t* msgType, void* data, uint16_t* inOutLen, int* outAttachHandle); + bool MailboxHasMessage(Mailbox* mailbox); + + int OpenFileHandleForSlot(int slot, const char* path, bool create); + int OpenFileHandle(const char* path); + int CreateFileHandle(const char* path); + int FileReadHandle(int handle, uint8_t* buffer, uint64_t offset, uint64_t size); + int FileWriteHandle(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size); + uint64_t FileGetSizeHandle(int handle); + + int CreateSocketHandleForSlot(int slot, int type); + int CreateSocketHandle(int type); + int SocketConnectHandle(int handle, uint32_t ip, uint16_t port); + int SocketBindHandle(int handle, uint16_t port); + int SocketListenHandle(int handle); + int SocketAcceptHandle(int handle); + int SocketSendHandle(int handle, const uint8_t* data, uint32_t len); + int SocketRecvHandle(int handle, uint8_t* buffer, uint32_t maxLen); + int SocketSendToHandle(int handle, const uint8_t* data, uint32_t len, uint32_t destIp, uint16_t destPort); + int SocketRecvFromHandle(int handle, uint8_t* buffer, uint32_t maxLen, uint32_t* srcIp, uint16_t* srcPort); + void NotifyTcpConnectionChanged(Net::Tcp::Connection* connection); + + Surface* CreateSurface(uint64_t byteSize); + int CreateSurfaceHandle(uint64_t byteSize); + void RetainSurface(Surface* surface); + void ReleaseSurface(Surface* surface); + uint64_t GetSurfaceSize(const Surface* surface); + int ResizeSurface(Surface* surface, uint64_t newSize); + int CopySurface(Surface* dst, Surface* src); + uint64_t MapSurfaceHandle(int handle); + int ResizeSurfaceHandle(int handle, uint64_t newSize); + int MapSurfaceForPid(Surface* surface, int pid, uint64_t pml4Phys, uint64_t& heapNext, uint64_t& outVa); + int UnmapSurfaceForPid(Surface* surface, int pid, uint64_t pml4Phys); + + ProcessObject* GetProcessObject(int pid); + int OpenProcessHandle(int pid); + void ProcessStartedInSlot(int slot, int pid); + void ProcessExitedInSlot(int slot, int pid); + bool ProcessHasExited(ProcessObject* process); + + uint32_t WaitOnHandle(int handle, uint32_t wantedSignals, uint64_t timeoutMs); + + int CreateWaitsetHandleForSlot(int slot); + int CreateWaitsetHandleForCurrent(); + int WaitsetAddHandleForSlot(int slot, int waitsetHandle, int targetHandle, uint32_t signals); + int WaitsetAddHandle(int waitsetHandle, int targetHandle, uint32_t signals); + int WaitsetRemoveIndexForSlot(int slot, int waitsetHandle, int index); + int WaitsetRemoveIndex(int waitsetHandle, int index); + int WaitsetWaitHandle(int waitsetHandle, WaitsetReady* outReady, uint64_t timeoutMs); + + void NotifyObjectChanged(Object* object); + void CleanupProcessSlot(int slot, int pid, uint64_t pml4Phys); + +} diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 65e7739..1b91db3 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include using namespace Kt; @@ -227,6 +228,7 @@ extern "C" void kmain() { Montauk::InitializeSyscalls(); Sched::Initialize(); + Ipc::Initialize(); // Boot Application Processors (all subsystems ready, APs can schedule) Smp::BootAPs(); diff --git a/kernel/src/Net/Socket.cpp b/kernel/src/Net/Socket.cpp index 5949a85..4d57cac 100644 --- a/kernel/src/Net/Socket.cpp +++ b/kernel/src/Net/Socket.cpp @@ -5,324 +5,60 @@ */ #include "Socket.hpp" -#include -#include -#include +#include #include -#include -#include using namespace Kt; namespace Net::Socket { - // ---- UDP socket state ---- - - static constexpr uint32_t UDP_RING_SIZE = 4096; - static constexpr int MAX_UDP_SOCKETS = 16; - - struct UdpDgramHeader { - uint32_t SrcIp; - uint16_t SrcPort; - uint16_t DataLen; - }; - - struct UdpSocketState { - uint8_t Ring[UDP_RING_SIZE]; - uint32_t Head; - uint32_t Tail; - uint32_t Count; - uint16_t LocalPort; - bool Active; - }; - - static UdpSocketState g_udpSockets[MAX_UDP_SOCKETS] = {}; - static SocketEntry g_sockets[MAX_SOCKETS] = {}; - static uint16_t g_nextEphemeralPort = 49152; - - static uint16_t AllocEphemeralPort() { - uint16_t port = g_nextEphemeralPort++; - if (g_nextEphemeralPort == 0) g_nextEphemeralPort = 49152; - return port; - } - - static bool ValidFd(int fd, int pid) { - if (fd < 0 || fd >= MAX_SOCKETS) return false; - if (!g_sockets[fd].Active) return false; - if (g_sockets[fd].OwnerPid != pid) return false; - return true; - } - - static UdpSocketState* AllocUdpState() { - for (int i = 0; i < MAX_UDP_SOCKETS; i++) { - if (!g_udpSockets[i].Active) { - g_udpSockets[i].Active = true; - g_udpSockets[i].Head = 0; - g_udpSockets[i].Tail = 0; - g_udpSockets[i].Count = 0; - g_udpSockets[i].LocalPort = 0; - return &g_udpSockets[i]; - } - } - return nullptr; - } - - static void FreeUdpState(UdpSocketState* state) { - if (state) { - if (state->LocalPort != 0) { - Udp::Unbind(state->LocalPort); - } - state->Active = false; - } - } - - static void UdpSocketDispatcher(uint32_t srcIp, uint16_t srcPort, - uint16_t dstPort, - const uint8_t* data, uint16_t length) { - for (int i = 0; i < MAX_UDP_SOCKETS; i++) { - if (g_udpSockets[i].Active && g_udpSockets[i].LocalPort == dstPort) { - UdpSocketState* st = &g_udpSockets[i]; - uint32_t needed = sizeof(UdpDgramHeader) + length; - if (st->Count + needed > UDP_RING_SIZE) { - return; // drop if buffer full - } - - // Enqueue header - UdpDgramHeader hdr; - hdr.SrcIp = srcIp; - hdr.SrcPort = srcPort; - hdr.DataLen = length; - - const uint8_t* hdrBytes = (const uint8_t*)&hdr; - for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) { - st->Ring[st->Tail] = hdrBytes[j]; - st->Tail = (st->Tail + 1) % UDP_RING_SIZE; - } - - // Enqueue payload - for (uint16_t j = 0; j < length; j++) { - st->Ring[st->Tail] = data[j]; - st->Tail = (st->Tail + 1) % UDP_RING_SIZE; - } - - st->Count += needed; - return; - } - } - } - - // ---- Public API ---- - void Initialize() { - for (int i = 0; i < MAX_SOCKETS; i++) { - g_sockets[i].Active = false; - } - for (int i = 0; i < MAX_UDP_SOCKETS; i++) { - g_udpSockets[i].Active = false; - } - KernelLogStream(OK, "Net") << "Socket table initialized"; + KernelLogStream(OK, "Net") << "Socket handles initialized"; } int Create(int type, int pid) { - if (type != SOCK_TCP && type != SOCK_UDP) return -1; - - for (int i = 0; i < MAX_SOCKETS; i++) { - if (!g_sockets[i].Active) { - g_sockets[i].Active = true; - g_sockets[i].Type = type; - g_sockets[i].OwnerPid = pid; - g_sockets[i].TcpConn = nullptr; - g_sockets[i].UdpState = nullptr; - g_sockets[i].LocalPort = 0; - - if (type == SOCK_UDP) { - UdpSocketState* us = AllocUdpState(); - if (!us) { - g_sockets[i].Active = false; - return -1; - } - g_sockets[i].UdpState = us; - } - - return i; - } - } - return -1; + return Ipc::CreateSocketHandleForSlot(Ipc::SlotForPid(pid), type); } - int Connect(int fd, uint32_t ip, uint16_t port, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_TCP) return -1; - if (g_sockets[fd].TcpConn != nullptr) return -1; - - uint16_t srcPort = AllocEphemeralPort(); - g_sockets[fd].LocalPort = srcPort; - - Tcp::Connection* conn = Tcp::Connect(ip, port, srcPort); - if (conn == nullptr) return -1; - - g_sockets[fd].TcpConn = conn; - return 0; + int Connect(int fd, uint32_t ip, uint16_t port, int /*pid*/) { + return Ipc::SocketConnectHandle(fd, ip, port); } - int Bind(int fd, uint16_t port, int pid) { - if (!ValidFd(fd, pid)) return -1; - g_sockets[fd].LocalPort = port; - - if (g_sockets[fd].Type == SOCK_UDP) { - UdpSocketState* us = g_sockets[fd].UdpState; - if (!us) return -1; - us->LocalPort = port; - if (!Udp::Bind(port, UdpSocketDispatcher)) return -1; - } - - return 0; + int Bind(int fd, uint16_t port, int /*pid*/) { + return Ipc::SocketBindHandle(fd, port); } - int Listen(int fd, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_TCP) return -1; - if (g_sockets[fd].LocalPort == 0) return -1; - if (g_sockets[fd].TcpConn != nullptr) return -1; - - Tcp::Connection* conn = Tcp::Listen(g_sockets[fd].LocalPort); - if (conn == nullptr) return -1; - - g_sockets[fd].TcpConn = conn; - return 0; + int Listen(int fd, int /*pid*/) { + return Ipc::SocketListenHandle(fd); } - int Accept(int fd, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_TCP) return -1; - if (g_sockets[fd].TcpConn == nullptr) return -1; - - Tcp::Connection* clientConn = Tcp::Accept(g_sockets[fd].TcpConn); - if (clientConn == nullptr) return -1; - - // Allocate a new socket entry for the accepted connection - for (int i = 0; i < MAX_SOCKETS; i++) { - if (!g_sockets[i].Active) { - g_sockets[i].Active = true; - g_sockets[i].Type = SOCK_TCP; - g_sockets[i].OwnerPid = pid; - g_sockets[i].TcpConn = clientConn; - g_sockets[i].UdpState = nullptr; - g_sockets[i].LocalPort = g_sockets[fd].LocalPort; - return i; - } - } - - // No free socket slot — close the accepted connection - Tcp::Close(clientConn); - return -1; + int Accept(int fd, int /*pid*/) { + return Ipc::SocketAcceptHandle(fd); } - int Send(int fd, const uint8_t* data, uint32_t len, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_TCP) return -1; - if (g_sockets[fd].TcpConn == nullptr) return -1; - return Tcp::Send(g_sockets[fd].TcpConn, data, (uint16_t)len); + int Send(int fd, const uint8_t* data, uint32_t len, int /*pid*/) { + return Ipc::SocketSendHandle(fd, data, len); } - int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_TCP) return -1; - if (g_sockets[fd].TcpConn == nullptr) return -1; - return Tcp::ReceiveNonBlocking(g_sockets[fd].TcpConn, buf, (uint16_t)maxLen); + int Recv(int fd, uint8_t* buf, uint32_t maxLen, int /*pid*/) { + return Ipc::SocketRecvHandle(fd, buf, maxLen); } int SendTo(int fd, const uint8_t* data, uint32_t len, - uint32_t destIp, uint16_t destPort, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_UDP) return -1; - - UdpSocketState* us = g_sockets[fd].UdpState; - if (!us) return -1; - - // Auto-bind ephemeral port if not already bound - if (us->LocalPort == 0) { - uint16_t ep = AllocEphemeralPort(); - us->LocalPort = ep; - g_sockets[fd].LocalPort = ep; - if (!Udp::Bind(ep, UdpSocketDispatcher)) return -1; - } - - if (!Udp::Send(destIp, us->LocalPort, destPort, data, (uint16_t)len)) { - return -1; - } - return (int)len; + uint32_t destIp, uint16_t destPort, int /*pid*/) { + return Ipc::SocketSendToHandle(fd, data, len, destIp, destPort); } int RecvFrom(int fd, uint8_t* buf, uint32_t maxLen, - uint32_t* srcIp, uint16_t* srcPort, int pid) { - if (!ValidFd(fd, pid)) return -1; - if (g_sockets[fd].Type != SOCK_UDP) return -1; - - UdpSocketState* us = g_sockets[fd].UdpState; - if (!us) return -1; - - if (us->Count < sizeof(UdpDgramHeader)) { - return -1; // no data available - } - - // Dequeue header - UdpDgramHeader hdr; - uint8_t* hdrBytes = (uint8_t*)&hdr; - for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) { - hdrBytes[j] = us->Ring[us->Head]; - us->Head = (us->Head + 1) % UDP_RING_SIZE; - } - us->Count -= sizeof(UdpDgramHeader); - - // Dequeue payload - uint16_t copyLen = hdr.DataLen; - if (copyLen > maxLen) copyLen = (uint16_t)maxLen; - - for (uint16_t j = 0; j < copyLen; j++) { - buf[j] = us->Ring[us->Head]; - us->Head = (us->Head + 1) % UDP_RING_SIZE; - } - - // Skip remaining data if buffer was too small - for (uint16_t j = copyLen; j < hdr.DataLen; j++) { - us->Head = (us->Head + 1) % UDP_RING_SIZE; - } - us->Count -= hdr.DataLen; - - if (srcIp) *srcIp = hdr.SrcIp; - if (srcPort) *srcPort = hdr.SrcPort; - - return (int)copyLen; + uint32_t* srcIp, uint16_t* srcPort, int /*pid*/) { + return Ipc::SocketRecvFromHandle(fd, buf, maxLen, srcIp, srcPort); } - void Close(int fd, int pid) { - if (!ValidFd(fd, pid)) return; - if (g_sockets[fd].TcpConn != nullptr) { - Tcp::Close(g_sockets[fd].TcpConn); - g_sockets[fd].TcpConn = nullptr; - } - if (g_sockets[fd].UdpState != nullptr) { - FreeUdpState(g_sockets[fd].UdpState); - g_sockets[fd].UdpState = nullptr; - } - g_sockets[fd].Active = false; + void Close(int fd, int /*pid*/) { + Ipc::CloseHandle(fd); } - void CleanupProcess(int pid) { - for (int i = 0; i < MAX_SOCKETS; i++) { - if (g_sockets[i].Active && g_sockets[i].OwnerPid == pid) { - if (g_sockets[i].TcpConn != nullptr) { - Tcp::Close(g_sockets[i].TcpConn); - g_sockets[i].TcpConn = nullptr; - } - if (g_sockets[i].UdpState != nullptr) { - FreeUdpState(g_sockets[i].UdpState); - g_sockets[i].UdpState = nullptr; - } - g_sockets[i].Active = false; - } - } + void CleanupProcess(int /*pid*/) { } } diff --git a/kernel/src/Net/Socket.hpp b/kernel/src/Net/Socket.hpp index b72364a..8eff498 100644 --- a/kernel/src/Net/Socket.hpp +++ b/kernel/src/Net/Socket.hpp @@ -6,40 +6,27 @@ #pragma once #include -#include namespace Net::Socket { static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; - static constexpr int MAX_SOCKETS = 64; - - struct UdpSocketState; - - struct SocketEntry { - bool Active; - int Type; - int OwnerPid; - Tcp::Connection* TcpConn; - UdpSocketState* UdpState; - uint16_t LocalPort; - }; void Initialize(); - // Create a socket of the given type. Returns fd or -1. + // Create a socket of the given type. Returns an IPC handle or -1. int Create(int type, int pid); - // Connect socket fd to remote ip:port. Returns 0 or -1. + // Connect socket handle to remote ip:port. Returns 0 or -1. int Connect(int fd, uint32_t ip, uint16_t port, int pid); - // Bind socket fd to a local port. Returns 0 or -1. + // Bind socket handle to a local port. Returns 0 or -1. int Bind(int fd, uint16_t port, int pid); // Start listening on a bound socket. Returns 0 or -1. int Listen(int fd, int pid); - // Accept an incoming connection. Returns new fd or -1. + // Accept an incoming connection. Returns a new IPC handle or -1. int Accept(int fd, int pid); // Send data on a connected socket. Returns bytes sent or -1. @@ -59,7 +46,7 @@ namespace Net::Socket { // Close a socket. void Close(int fd, int pid); - // Close all sockets owned by a process (called on process exit). + // Per-process cleanup is handled by IPC handle teardown. void CleanupProcess(int pid); } diff --git a/kernel/src/Net/Tcp.cpp b/kernel/src/Net/Tcp.cpp index 65d835b..d325919 100644 --- a/kernel/src/Net/Tcp.cpp +++ b/kernel/src/Net/Tcp.cpp @@ -8,11 +8,13 @@ #include #include #include +#include #include #include #include #include #include +#include using namespace Kt; @@ -216,6 +218,8 @@ namespace Net::Tcp { listener->PendingRemotePort = srcPort; listener->PendingSeq = seqNum; listener->Lock.Release(); + Sched::WakeObjectWaiters(listener); + Ipc::NotifyTcpConnectionChanged(listener); return; } } @@ -235,12 +239,15 @@ namespace Net::Tcp { } conn->Lock.Acquire(); + bool notify = false; // RST handling if (flags & FLAG_RST) { conn->CurrentState = State::Closed; conn->Active = false; conn->Lock.Release(); + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return; } @@ -255,6 +262,7 @@ namespace Net::Tcp { // Send ACK SendSegment(conn, FLAG_ACK, nullptr, 0); + notify = true; } } break; @@ -266,6 +274,7 @@ namespace Net::Tcp { if (ackNum == conn->SendNext) { conn->SendUnack = ackNum; conn->CurrentState = State::Established; + notify = true; } } break; @@ -275,6 +284,7 @@ namespace Net::Tcp { // Handle incoming data if (flags & FLAG_ACK) { conn->SendUnack = ackNum; + notify = true; } if (payloadLen > 0 && seqNum == conn->RecvNext) { @@ -283,6 +293,7 @@ namespace Net::Tcp { // Send ACK SendSegment(conn, FLAG_ACK, nullptr, 0); + notify = true; } if (flags & FLAG_FIN) { @@ -291,6 +302,7 @@ namespace Net::Tcp { // Send ACK for the FIN SendSegment(conn, FLAG_ACK, nullptr, 0); + notify = true; } break; } @@ -302,13 +314,16 @@ namespace Net::Tcp { conn->RecvNext = seqNum + 1; conn->CurrentState = State::TimeWait; SendSegment(conn, FLAG_ACK, nullptr, 0); + notify = true; } else { conn->CurrentState = State::FinWait2; + notify = true; } } else if (flags & FLAG_FIN) { conn->RecvNext = seqNum + 1; conn->CurrentState = State::TimeWait; SendSegment(conn, FLAG_ACK, nullptr, 0); + notify = true; } break; } @@ -318,6 +333,7 @@ namespace Net::Tcp { conn->RecvNext = seqNum + 1; conn->CurrentState = State::TimeWait; SendSegment(conn, FLAG_ACK, nullptr, 0); + notify = true; } break; } @@ -326,6 +342,7 @@ namespace Net::Tcp { if (flags & FLAG_ACK) { conn->CurrentState = State::Closed; conn->Active = false; + notify = true; } break; } @@ -340,6 +357,10 @@ namespace Net::Tcp { } conn->Lock.Release(); + if (notify) { + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); + } } Connection* Listen(uint16_t port) { @@ -421,19 +442,25 @@ namespace Net::Tcp { } // Wait for ACK to complete the handshake - for (int i = 0; i < 100; i++) { + uint64_t deadline = Timekeeping::GetMilliseconds() + 5000; + while (Timekeeping::GetMilliseconds() < deadline) { if (conn->CurrentState == State::Established) { return conn; } - Timekeeping::Sleep(50); + uint64_t now = Timekeeping::GetMilliseconds(); + uint64_t waitMs = (deadline > now) ? (deadline - now) : 0; + if (waitMs == 0) break; + Sched::BlockOnObject(conn, waitMs); } // Timed out waiting for ACK conn->Active = false; + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return nullptr; } listener->Lock.Release(); - Timekeeping::Sleep(10); + Sched::BlockOnObject(listener, 0); } } @@ -480,11 +507,15 @@ namespace Net::Tcp { // Wait for SYN-ACK for (int attempt = 0; attempt < MAX_RETRANSMITS; attempt++) { - for (int i = 0; i < 20; i++) { + uint64_t deadline = Timekeeping::GetMilliseconds() + 1000; + while (Timekeeping::GetMilliseconds() < deadline) { if (conn->CurrentState == State::Established) { return conn; } - Timekeeping::Sleep(50); + uint64_t now = Timekeeping::GetMilliseconds(); + uint64_t waitMs = (deadline > now) ? (deadline - now) : 0; + if (waitMs == 0) break; + Sched::BlockOnObject(conn, waitMs); } if (conn->CurrentState == State::SynSent) { @@ -512,6 +543,8 @@ namespace Net::Tcp { // Failed to connect conn->Active = false; + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return nullptr; } @@ -580,8 +613,15 @@ namespace Net::Tcp { conn->RetransmitTime = Timekeeping::GetMilliseconds(); conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); + continue; } - Timekeeping::Sleep(10); + + uint64_t waitMs = 10; + if (conn->RetransmitLen > 0 && now <= conn->RetransmitTime + RETRANSMIT_TIMEOUT_MS) { + waitMs = (conn->RetransmitTime + RETRANSMIT_TIMEOUT_MS) - now; + if (waitMs == 0) waitMs = 1; + } + Sched::BlockOnObject(conn, waitMs); } return sent; @@ -625,7 +665,7 @@ namespace Net::Tcp { conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - Timekeeping::Sleep(10); + Sched::BlockOnObject(conn, 0); } } @@ -691,9 +731,11 @@ namespace Net::Tcp { conn->CurrentState == State::Closed) { break; } - Timekeeping::Sleep(50); + Sched::BlockOnObject(conn, 50); } conn->Active = false; + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return; } @@ -709,9 +751,11 @@ namespace Net::Tcp { if (conn->CurrentState == State::Closed) { break; } - Timekeeping::Sleep(50); + Sched::BlockOnObject(conn, 50); } conn->Active = false; + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return; } @@ -721,6 +765,8 @@ namespace Net::Tcp { conn->Active = false; conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return; } @@ -728,6 +774,8 @@ namespace Net::Tcp { conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); conn->Active = false; + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); return; } } @@ -736,7 +784,44 @@ namespace Net::Tcp { if (conn == nullptr) { return State::Closed; } - return conn->CurrentState; + conn->Lock.Acquire(); + State state = conn->CurrentState; + conn->Lock.Release(); + return state; + } + + bool HasPendingAccept(Connection* conn) { + if (conn == nullptr) return false; + conn->Lock.Acquire(); + bool pending = conn->PendingAccept; + conn->Lock.Release(); + return pending; + } + + bool HasReceiveData(Connection* conn) { + if (conn == nullptr) return false; + conn->Lock.Acquire(); + bool hasData = conn->RecvCount > 0; + conn->Lock.Release(); + return hasData; + } + + bool CanSend(Connection* conn) { + if (conn == nullptr) return false; + conn->Lock.Acquire(); + bool writable = conn->CurrentState == State::Established; + conn->Lock.Release(); + return writable; + } + + bool IsClosedForIo(Connection* conn) { + if (conn == nullptr) return true; + conn->Lock.Acquire(); + bool closed = conn->CurrentState == State::CloseWait || + conn->CurrentState == State::Closed || + conn->CurrentState == State::TimeWait; + conn->Lock.Release(); + return closed; } } diff --git a/kernel/src/Net/Tcp.hpp b/kernel/src/Net/Tcp.hpp index c523394..09de699 100644 --- a/kernel/src/Net/Tcp.hpp +++ b/kernel/src/Net/Tcp.hpp @@ -80,4 +80,10 @@ namespace Net::Tcp { // Get the state of a connection State GetState(Connection* conn); + // Readiness helpers used by the IPC layer. + bool HasPendingAccept(Connection* conn); + bool HasReceiveData(Connection* conn); + bool CanSend(Connection* conn); + bool IsClosedForIo(Connection* conn); + } diff --git a/kernel/src/Sched/ElfLoader.cpp b/kernel/src/Sched/ElfLoader.cpp index 63ec791..197359a 100644 --- a/kernel/src/Sched/ElfLoader.cpp +++ b/kernel/src/Sched/ElfLoader.cpp @@ -52,15 +52,15 @@ namespace Sched { } uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) { - int handle = Fs::Vfs::VfsOpen(vfsPath); - if (handle < 0) { + Fs::Vfs::BackendFile file = {-1, -1}; + if (Fs::Vfs::OpenBackendFile(vfsPath, file) < 0) { return 0; } - uint64_t fileSize = Fs::Vfs::VfsGetSize(handle); + uint64_t fileSize = Fs::Vfs::GetBackendFileSize(file); if (fileSize < sizeof(Elf64Header)) { Kt::KernelLogStream(Kt::ERROR, "ELF") << "File too small (" << fileSize << " bytes)"; - Fs::Vfs::VfsClose(handle); + Fs::Vfs::CloseBackendFile(file); return 0; } @@ -68,12 +68,12 @@ namespace Sched { uint8_t* fileData = (uint8_t*)Memory::g_heap->Request(fileSize); if (fileData == nullptr) { Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to allocate " << fileSize << " bytes for file"; - Fs::Vfs::VfsClose(handle); + Fs::Vfs::CloseBackendFile(file); return 0; } - Fs::Vfs::VfsRead(handle, fileData, 0, fileSize); - Fs::Vfs::VfsClose(handle); + Fs::Vfs::ReadBackendFile(file, fileData, 0, fileSize); + Fs::Vfs::CloseBackendFile(file); // Prevent the optimizer from reordering the VfsRead store past the // header validation reads that follow. diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 4476286..09fe990 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -18,6 +18,7 @@ #include #include #include +#include // Assembly: context switch with CR3 and FPU state parameters extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3, @@ -47,6 +48,45 @@ namespace Sched { return (uint64_t)Memory::VMM::g_paging->PML4; } + static void SwitchAwayFromBlockedCurrentLocked() { + auto* cpu = Smp::GetCurrentCpuData(); + int slot = cpu->currentSlot; + if (slot < 0) { + schedLock.Release(); + return; + } + + int next = -1; + for (int i = 0; i < MaxProcesses; i++) { + if (processTable[i].state == ProcessState::Ready) { + next = i; + break; + } + } + + if (next >= 0) { + cpu->currentSlot = next; + processTable[next].state = ProcessState::Running; + readyCount--; + processTable[next].runningOnCpu = cpu->cpuIndex; + processTable[next].sliceRemaining = TimeSliceMs; + + cpu->kernelRsp = processTable[next].kernelStackTop; + cpu->tss->rsp0 = processTable[next].kernelStackTop; + + SchedContextSwitch(&processTable[slot].savedRsp, processTable[next].savedRsp, + processTable[next].pml4Phys, + processTable[slot].fpuState, processTable[next].fpuState); + schedLock.Release(); + return; + } + + cpu->currentSlot = -1; + SchedContextSwitch(&processTable[slot].savedRsp, cpu->idleSavedRsp, + GetKernelCR3(), processTable[slot].fpuState, nullptr); + schedLock.Release(); + } + // Startup function for newly spawned processes. // SchedContextSwitch "returns" here on first schedule. // The schedLock is held (acquired by the switching-from CPU's Schedule). @@ -97,6 +137,7 @@ namespace Sched { processTable[i].killPending = false; processTable[i].waitingForPid = -1; processTable[i].sleepUntilTick = 0; + processTable[i].waitingOnObject = nullptr; processTable[i].redirected = false; processTable[i].parentPid = -1; processTable[i].outBuf = nullptr; @@ -109,6 +150,10 @@ namespace Sched { processTable[i].keyTail = 0; processTable[i].termCols = 0; processTable[i].termRows = 0; + processTable[i].ioOutHandle = -1; + processTable[i].ioInHandle = -1; + processTable[i].ioKeyHandle = -1; + processTable[i].ioWaitsetHandle = -1; } nextPid = 0; @@ -276,6 +321,7 @@ namespace Sched { proc.killPending = false; proc.waitingForPid = -1; proc.sleepUntilTick = 0; + proc.waitingOnObject = nullptr; // Copy arguments string into process proc.args[0] = '\0'; @@ -333,12 +379,18 @@ namespace Sched { proc.keyTail = 0; proc.termCols = 0; proc.termRows = 0; + proc.ioOutHandle = -1; + proc.ioInHandle = -1; + proc.ioKeyHandle = -1; + proc.ioWaitsetHandle = -1; // 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 + Ipc::ProcessStartedInSlot(slot, proc.pid); + int resultPid = proc.pid; schedLock.Release(); @@ -486,6 +538,8 @@ namespace Sched { processTable[i].sleepUntilTick != 0 && now >= processTable[i].sleepUntilTick) { processTable[i].sleepUntilTick = 0; + processTable[i].waitingForPid = -1; + processTable[i].waitingOnObject = nullptr; processTable[i].state = ProcessState::Ready; readyCount++; } @@ -554,15 +608,30 @@ namespace Sched { // Clean up any windows owned by this process WinServer::CleanupProcess(exitingPid); - // Free I/O redirect buffers - if (proc.outBuf) { - Memory::g_pfa->Free(proc.outBuf); - proc.outBuf = nullptr; - } - if (proc.inBuf) { - Memory::g_pfa->Free(proc.inBuf); - proc.inBuf = nullptr; - } + // Release process-scoped IPC handles/mappings before tearing down the address space. + Ipc::CleanupProcessSlot(slot, exitingPid, proc.pml4Phys); + + proc.waitingForPid = -1; + proc.sleepUntilTick = 0; + proc.waitingOnObject = nullptr; + proc.redirected = false; + proc.parentPid = -1; + proc.outBuf = nullptr; + proc.outHead = 0; + proc.outTail = 0; + proc.inBuf = nullptr; + proc.inHead = 0; + proc.inTail = 0; + proc.keyHead = 0; + proc.keyTail = 0; + proc.termCols = 0; + proc.termRows = 0; + proc.ioOutHandle = -1; + proc.ioInHandle = -1; + proc.ioKeyHandle = -1; + proc.ioWaitsetHandle = -1; + + Ipc::ProcessExitedInSlot(slot, exitingPid); // Free all user-space physical pages and page table structures Memory::VMM::Paging::FreeUserHalf(proc.pml4Phys); @@ -579,6 +648,8 @@ namespace Sched { processTable[i].state = ProcessState::Ready; readyCount++; processTable[i].waitingForPid = -1; + processTable[i].waitingOnObject = nullptr; + processTable[i].sleepUntilTick = 0; } } @@ -664,6 +735,9 @@ namespace Sched { readyCount--; proc.state = ProcessState::Terminated; proc.killPending = false; + proc.waitingForPid = -1; + proc.sleepUntilTick = 0; + proc.waitingOnObject = nullptr; // Wake any processes blocked on this PID for (int i = 0; i < MaxProcesses; i++) { @@ -672,6 +746,8 @@ namespace Sched { processTable[i].state = ProcessState::Ready; readyCount++; processTable[i].waitingForPid = -1; + processTable[i].waitingOnObject = nullptr; + processTable[i].sleepUntilTick = 0; } } @@ -679,15 +755,26 @@ namespace Sched { // Safe to clean up resources now -- process is not running anywhere. WinServer::CleanupProcess(killedPid); + Ipc::CleanupProcessSlot(slot, killedPid, proc.pml4Phys); - if (proc.outBuf) { - Memory::g_pfa->Free(proc.outBuf); - proc.outBuf = nullptr; - } - if (proc.inBuf) { - Memory::g_pfa->Free(proc.inBuf); - proc.inBuf = nullptr; - } + proc.redirected = false; + proc.parentPid = -1; + proc.outBuf = nullptr; + proc.outHead = 0; + proc.outTail = 0; + proc.inBuf = nullptr; + proc.inHead = 0; + proc.inTail = 0; + proc.keyHead = 0; + proc.keyTail = 0; + proc.termCols = 0; + proc.termRows = 0; + proc.ioOutHandle = -1; + proc.ioInHandle = -1; + proc.ioKeyHandle = -1; + proc.ioWaitsetHandle = -1; + + Ipc::ProcessExitedInSlot(slot, killedPid); Memory::VMM::Paging::FreeUserHalf(proc.pml4Phys); @@ -727,39 +814,10 @@ namespace Sched { // ExitProcess will wake us when the target terminates. processTable[slot].state = ProcessState::Blocked; processTable[slot].waitingForPid = pid; + processTable[slot].waitingOnObject = nullptr; + processTable[slot].sleepUntilTick = 0; processTable[slot].runningOnCpu = -1; - - // Find next ready process to switch to - int next = -1; - for (int i = 0; i < MaxProcesses; i++) { - if (processTable[i].state == ProcessState::Ready) { - next = i; - break; - } - } - - if (next >= 0) { - cpu->currentSlot = next; - processTable[next].state = ProcessState::Running; - readyCount--; - processTable[next].runningOnCpu = cpu->cpuIndex; - processTable[next].sliceRemaining = TimeSliceMs; - - cpu->kernelRsp = processTable[next].kernelStackTop; - cpu->tss->rsp0 = processTable[next].kernelStackTop; - - SchedContextSwitch(&processTable[slot].savedRsp, processTable[next].savedRsp, - processTable[next].pml4Phys, - processTable[slot].fpuState, processTable[next].fpuState); - schedLock.Release(); - } else { - // No ready process -- go idle - cpu->currentSlot = -1; - - SchedContextSwitch(&processTable[slot].savedRsp, cpu->idleSavedRsp, - GetKernelCR3(), processTable[slot].fpuState, nullptr); - schedLock.Release(); - } + SwitchAwayFromBlockedCurrentLocked(); } void BlockForSleep(uint64_t ms) { @@ -772,38 +830,46 @@ namespace Sched { schedLock.Acquire(); processTable[slot].state = ProcessState::Blocked; + processTable[slot].waitingForPid = -1; + processTable[slot].waitingOnObject = nullptr; processTable[slot].sleepUntilTick = Timekeeping::GetTicks() + ms; processTable[slot].runningOnCpu = -1; + SwitchAwayFromBlockedCurrentLocked(); + } - int next = -1; + void BlockOnObject(void* object, uint64_t timeoutMs) { + if (object == nullptr) return; + + auto* cpu = Smp::GetCurrentCpuData(); + int slot = cpu->currentSlot; + if (slot < 0) return; + + schedLock.Acquire(); + processTable[slot].state = ProcessState::Blocked; + processTable[slot].waitingForPid = -1; + processTable[slot].waitingOnObject = object; + processTable[slot].sleepUntilTick = (timeoutMs > 0) + ? (Timekeeping::GetTicks() + timeoutMs) + : 0; + processTable[slot].runningOnCpu = -1; + SwitchAwayFromBlockedCurrentLocked(); + } + + void WakeObjectWaiters(void* object) { + if (object == nullptr) return; + + schedLock.Acquire(); for (int i = 0; i < MaxProcesses; i++) { - if (processTable[i].state == ProcessState::Ready) { - next = i; - break; - } - } - - if (next >= 0) { - cpu->currentSlot = next; - processTable[next].state = ProcessState::Running; - readyCount--; - processTable[next].runningOnCpu = cpu->cpuIndex; - processTable[next].sliceRemaining = TimeSliceMs; - - cpu->kernelRsp = processTable[next].kernelStackTop; - cpu->tss->rsp0 = processTable[next].kernelStackTop; - - SchedContextSwitch(&processTable[slot].savedRsp, processTable[next].savedRsp, - processTable[next].pml4Phys, - processTable[slot].fpuState, processTable[next].fpuState); - schedLock.Release(); - } else { - cpu->currentSlot = -1; - - SchedContextSwitch(&processTable[slot].savedRsp, cpu->idleSavedRsp, - GetKernelCR3(), processTable[slot].fpuState, nullptr); - schedLock.Release(); + if (processTable[i].state != ProcessState::Blocked) continue; + if (processTable[i].waitingOnObject != object) continue; + + processTable[i].waitingOnObject = nullptr; + processTable[i].sleepUntilTick = 0; + processTable[i].waitingForPid = -1; + processTable[i].state = ProcessState::Ready; + readyCount++; } + schedLock.Release(); } bool IsAlive(int pid) { diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index 6331bbe..7ebb684 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -35,7 +35,8 @@ namespace Sched { int pid; ProcessState state; int waitingForPid; // PID this process is blocked on (-1 if none) - uint64_t sleepUntilTick; // Tick deadline for SYS_SLEEP_MS (0 = not sleeping) + uint64_t sleepUntilTick; // Tick deadline for sleep/object wait timeout (0 = none) + void* waitingOnObject; // IPC/scheduler object this process is blocked on (nullptr if none) char name[64]; uint64_t savedRsp; uint64_t stackBase; // Bottom of allocated kernel stack (lowest address) @@ -71,6 +72,12 @@ namespace Sched { int termCols = 0; int termRows = 0; + // IPC-backed redirected terminal channels + int ioOutHandle = -1; + int ioInHandle = -1; + int ioKeyHandle = -1; + int ioWaitsetHandle = -1; + // FPU/SSE state (FXSAVE format, must be 16-byte aligned) uint8_t fpuState[512] __attribute__((aligned(16))); }; @@ -100,6 +107,13 @@ namespace Sched { // Block the current process for the given number of milliseconds. void BlockForSleep(uint64_t ms); + // Block the current process until the given object is signaled or timed out. + // timeoutMs == 0 means wait indefinitely. + void BlockOnObject(void* object, uint64_t timeoutMs = 0); + + // Wake any processes blocked on the given object. + void WakeObjectWaiters(void* object); + // Kill a process by PID. If the process is running on another CPU, // sets a kill-pending flag checked on the next timer tick. // Returns 0 on success, -1 on failure. diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 69ca332..7d00630 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -76,6 +76,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; @@ -126,6 +127,28 @@ namespace Montauk { static constexpr uint64_t SYS_FRENAME = 94; static constexpr uint64_t SYS_GETCWD = 95; static constexpr uint64_t SYS_CHDIR = 96; + 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; // Audio control commands (for SYS_AUDIOCTL) static constexpr int AUDIO_CTL_SET_VOLUME = 0; @@ -188,6 +211,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 diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index df11755..8d44307 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -79,6 +79,7 @@ extern "C" { #define MTK_SYS_WINPOLL 57 #define MTK_SYS_WINENUM 58 #define MTK_SYS_WINMAP 59 +#define MTK_SYS_WINUNMAP 97 #define MTK_SYS_WINSENDEVENT 60 #define MTK_SYS_PROCLIST 61 #define MTK_SYS_KILL 62 @@ -99,9 +100,30 @@ extern "C" { #define MTK_SYS_GETTZ 91 #define MTK_SYS_GETCWD 95 #define MTK_SYS_CHDIR 96 +#define MTK_SYS_DUPHANDLE 98 +#define MTK_SYS_WAIT_HANDLE 99 +#define MTK_SYS_STREAM_CREATE 100 +#define MTK_SYS_STREAM_READ 101 +#define MTK_SYS_STREAM_WRITE 102 +#define MTK_SYS_MAILBOX_CREATE 103 +#define MTK_SYS_MAILBOX_SEND 104 +#define MTK_SYS_MAILBOX_RECV 105 +#define MTK_SYS_WAITSET_CREATE 106 +#define MTK_SYS_WAITSET_ADD 107 +#define MTK_SYS_WAITSET_REMOVE 108 +#define MTK_SYS_WAITSET_WAIT 109 +#define MTK_SYS_PROC_OPEN 110 +#define MTK_SYS_SURFACE_CREATE 111 +#define MTK_SYS_SURFACE_MAP 112 +#define MTK_SYS_SURFACE_RESIZE 113 #define MTK_SOCK_TCP 1 #define MTK_SOCK_UDP 2 +#define MTK_IPC_SIGNAL_READABLE (1u << 0) +#define MTK_IPC_SIGNAL_WRITABLE (1u << 1) +#define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2) +#define MTK_IPC_SIGNAL_EXITED (1u << 3) +#define MTK_IPC_SIGNAL_READY (1u << 4) /* Window event types */ #define MTK_EVENT_KEY 0 @@ -136,6 +158,11 @@ typedef struct { uint8_t buttons; } mtk_mouse_state; +typedef struct { + int32_t index; + uint32_t signals; +} mtk_ipc_wait_result; + typedef struct { uint8_t type; uint8_t _pad[3]; @@ -396,6 +423,78 @@ static inline int mtk_readdir(const char *path, const char **names, int max) { return (int)_mtk_syscall3(MTK_SYS_READDIR, (long)path, (long)names, (long)max); } +/* ==================================================================== + Generic IPC + ==================================================================== */ + +static inline int mtk_dup_handle(int handle) { + return (int)_mtk_syscall1(MTK_SYS_DUPHANDLE, (long)handle); +} + +static inline uint32_t mtk_wait_handle(int handle, uint32_t wanted_signals, unsigned long timeout_ms) { + return (uint32_t)_mtk_syscall3(MTK_SYS_WAIT_HANDLE, (long)handle, (long)wanted_signals, (long)timeout_ms); +} + +static inline int mtk_stream_create(int *out_read_handle, int *out_write_handle, unsigned long capacity) { + return (int)_mtk_syscall3(MTK_SYS_STREAM_CREATE, (long)out_read_handle, (long)out_write_handle, (long)capacity); +} + +static inline int mtk_stream_read(int handle, void *buf, int max_len) { + return (int)_mtk_syscall3(MTK_SYS_STREAM_READ, (long)handle, (long)buf, (long)max_len); +} + +static inline int mtk_stream_write(int handle, const void *data, int len) { + return (int)_mtk_syscall3(MTK_SYS_STREAM_WRITE, (long)handle, (long)data, (long)len); +} + +static inline int mtk_mailbox_create(int *out_send_handle, int *out_recv_handle) { + return (int)_mtk_syscall2(MTK_SYS_MAILBOX_CREATE, (long)out_send_handle, (long)out_recv_handle); +} + +static inline int mtk_mailbox_send(int handle, uint32_t msg_type, const void *data, + uint16_t len, int attach_handle) { + return (int)_mtk_syscall5(MTK_SYS_MAILBOX_SEND, (long)handle, (long)msg_type, + (long)data, (long)len, (long)attach_handle); +} + +static inline int mtk_mailbox_recv(int handle, uint32_t *out_msg_type, void *data, + uint16_t *in_out_len, int *out_attach_handle) { + return (int)_mtk_syscall5(MTK_SYS_MAILBOX_RECV, (long)handle, (long)out_msg_type, + (long)data, (long)in_out_len, (long)out_attach_handle); +} + +static inline int mtk_waitset_create(void) { + return (int)_mtk_syscall0(MTK_SYS_WAITSET_CREATE); +} + +static inline int mtk_waitset_add(int waitset_handle, int target_handle, uint32_t signals) { + return (int)_mtk_syscall3(MTK_SYS_WAITSET_ADD, (long)waitset_handle, (long)target_handle, (long)signals); +} + +static inline int mtk_waitset_remove(int waitset_handle, int index) { + return (int)_mtk_syscall2(MTK_SYS_WAITSET_REMOVE, (long)waitset_handle, (long)index); +} + +static inline int mtk_waitset_wait(int waitset_handle, mtk_ipc_wait_result *out_ready, unsigned long timeout_ms) { + return (int)_mtk_syscall3(MTK_SYS_WAITSET_WAIT, (long)waitset_handle, (long)out_ready, (long)timeout_ms); +} + +static inline int mtk_proc_open(int pid) { + return (int)_mtk_syscall1(MTK_SYS_PROC_OPEN, (long)pid); +} + +static inline int mtk_surface_create(unsigned long byte_size) { + return (int)_mtk_syscall1(MTK_SYS_SURFACE_CREATE, (long)byte_size); +} + +static inline void *mtk_surface_map(int handle) { + return (void *)_mtk_syscall1(MTK_SYS_SURFACE_MAP, (long)handle); +} + +static inline int mtk_surface_resize(int handle, unsigned long new_size) { + return (int)_mtk_syscall2(MTK_SYS_SURFACE_RESIZE, (long)handle, (long)new_size); +} + /* ==================================================================== Memory ==================================================================== */ @@ -478,6 +577,10 @@ static inline int mtk_win_poll(int id, mtk_win_event *event) { return (int)_mtk_syscall2(MTK_SYS_WINPOLL, (long)id, (long)event); } +static inline int mtk_win_unmap(int id) { + return (int)_mtk_syscall1(MTK_SYS_WINUNMAP, (long)id); +} + static inline unsigned long mtk_win_resize(int id, int w, int h) { return (unsigned long)_mtk_syscall3(MTK_SYS_WINRESIZE, (long)id, (long)w, (long)h); } @@ -486,6 +589,10 @@ static inline int mtk_win_enumerate(mtk_win_info *info, int max) { return (int)_mtk_syscall2(MTK_SYS_WINENUM, (long)info, (long)max); } +static inline unsigned long mtk_win_map(int id) { + return (unsigned long)_mtk_syscall1(MTK_SYS_WINMAP, (long)id); +} + static inline int mtk_win_setcursor(int id, int cursor) { return (int)_mtk_syscall2(MTK_SYS_WINSETCURSOR, (long)id, (long)cursor); } diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 2ba9c9e..22e849c 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -223,6 +223,62 @@ namespace montauk { (uint64_t)maxLen, (uint64_t)srcIp, (uint64_t)srcPort); } + // Generic IPC + inline int dup_handle(int handle) { + return (int)syscall1(Montauk::SYS_DUPHANDLE, (uint64_t)handle); + } + inline uint32_t wait_handle(int handle, uint32_t wantedSignals, uint64_t timeoutMs = ~0ULL) { + return (uint32_t)syscall3(Montauk::SYS_WAIT_HANDLE, (uint64_t)handle, + (uint64_t)wantedSignals, timeoutMs); + } + inline int stream_create(int* outReadHandle, int* outWriteHandle, uint32_t capacity = 0) { + return (int)syscall3(Montauk::SYS_STREAM_CREATE, (uint64_t)outReadHandle, + (uint64_t)outWriteHandle, (uint64_t)capacity); + } + inline int stream_read(int handle, void* buf, int maxLen) { + return (int)syscall3(Montauk::SYS_STREAM_READ, (uint64_t)handle, (uint64_t)buf, (uint64_t)maxLen); + } + inline int stream_write(int handle, const void* data, int len) { + return (int)syscall3(Montauk::SYS_STREAM_WRITE, (uint64_t)handle, (uint64_t)data, (uint64_t)len); + } + inline int mailbox_create(int* outSendHandle, int* outRecvHandle) { + return (int)syscall2(Montauk::SYS_MAILBOX_CREATE, (uint64_t)outSendHandle, (uint64_t)outRecvHandle); + } + inline int mailbox_send(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle = -1) { + return (int)syscall5(Montauk::SYS_MAILBOX_SEND, (uint64_t)handle, (uint64_t)msgType, + (uint64_t)data, (uint64_t)len, (uint64_t)(int64_t)attachHandle); + } + inline int mailbox_recv(int handle, uint32_t* outMsgType, void* data, + uint16_t* inOutLen, int* outAttachHandle = nullptr) { + return (int)syscall5(Montauk::SYS_MAILBOX_RECV, (uint64_t)handle, (uint64_t)outMsgType, + (uint64_t)data, (uint64_t)inOutLen, (uint64_t)outAttachHandle); + } + inline int waitset_create() { + return (int)syscall0(Montauk::SYS_WAITSET_CREATE); + } + inline int waitset_add(int waitsetHandle, int targetHandle, uint32_t signals) { + return (int)syscall3(Montauk::SYS_WAITSET_ADD, (uint64_t)waitsetHandle, + (uint64_t)targetHandle, (uint64_t)signals); + } + inline int waitset_remove(int waitsetHandle, int index) { + return (int)syscall2(Montauk::SYS_WAITSET_REMOVE, (uint64_t)waitsetHandle, (uint64_t)index); + } + inline int waitset_wait(int waitsetHandle, Montauk::IpcWaitResult* outReady, uint64_t timeoutMs = ~0ULL) { + return (int)syscall3(Montauk::SYS_WAITSET_WAIT, (uint64_t)waitsetHandle, (uint64_t)outReady, timeoutMs); + } + inline int proc_open(int pid) { + return (int)syscall1(Montauk::SYS_PROC_OPEN, (uint64_t)pid); + } + inline int surface_create(uint64_t byteSize) { + return (int)syscall1(Montauk::SYS_SURFACE_CREATE, byteSize); + } + inline void* surface_map(int handle) { + return (void*)syscall1(Montauk::SYS_SURFACE_MAP, (uint64_t)handle); + } + inline int surface_resize(int handle, uint64_t newSize) { + return (int)syscall2(Montauk::SYS_SURFACE_RESIZE, (uint64_t)handle, newSize); + } + // Process management inline void waitpid(int pid) { syscall1(Montauk::SYS_WAITPID, (uint64_t)pid); } @@ -435,6 +491,9 @@ namespace montauk { inline uint64_t win_map(int id) { return (uint64_t)syscall1(Montauk::SYS_WINMAP, (uint64_t)id); } + inline int win_unmap(int id) { + return (int)syscall1(Montauk::SYS_WINUNMAP, (uint64_t)id); + } inline int win_sendevent(int id, const Montauk::WinEvent* event) { return (int)syscall2(Montauk::SYS_WINSENDEVENT, (uint64_t)id, (uint64_t)event); } diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 05a040d..c69d624 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -390,11 +390,14 @@ bool desktop_poll_external_windows(DesktopState* ds) { for (int i = 0; i < ds->window_count; i++) { if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) { found = true; + bool remapRequested = false; + uint64_t currentVa = (uint64_t)(uintptr_t)ds->windows[i].content; if (ds->windows[i].content_w != extWins[e].width || ds->windows[i].content_h != extWins[e].height) { ds->windows[i].content_w = extWins[e].width; ds->windows[i].content_h = extWins[e].height; ds->windows[i].dirty = true; + remapRequested = true; if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) { changed = true; } @@ -411,12 +414,20 @@ bool desktop_poll_external_windows(DesktopState* ds) { changed = true; } ds->windows[i].ext_cursor = extWins[e].cursor; - // Always verify mapping is current. If a window slot was - // recycled (app exited, new app got same slot), desktopVa - // was zeroed and Map() returns a new VA. Detect this and - // update the content pointer to avoid using a stale VA. + + if (remapRequested && currentVa != 0) { + montauk::win_unmap(extId); + ds->windows[i].content = nullptr; + currentVa = 0; + } + uint64_t va = montauk::win_map(extId); - if (va != 0 && va != (uint64_t)(uintptr_t)ds->windows[i].content) { + if (!remapRequested && va != 0 && va != currentVa) { + montauk::win_unmap(extId); + va = montauk::win_map(extId); + } + + if (va != 0 && va != currentVa) { ds->windows[i].content = (uint32_t*)va; ds->windows[i].content_w = extWins[e].width; ds->windows[i].content_h = extWins[e].height; @@ -508,6 +519,9 @@ bool desktop_poll_external_windows(DesktopState* ds) { if (!stillExists) { // Window gone — remove without freeing content (shared memory) + if (ds->windows[i].content != nullptr) { + montauk::win_unmap(extId); + } ds->windows[i].content = nullptr; // prevent free gui::desktop_close_window(ds, i); changed = true; diff --git a/programs/src/dhcp/main.cpp b/programs/src/dhcp/main.cpp index 476fb07..8c5fa6a 100644 --- a/programs/src/dhcp/main.cpp +++ b/programs/src/dhcp/main.cpp @@ -397,7 +397,9 @@ extern "C" void _start() { } } } - montauk::yield(); + uint64_t now = montauk::get_milliseconds(); + if (now >= startMs + 10000) break; + montauk::wait_handle(fd, Montauk::IPC_SIGNAL_READABLE, startMs + 10000 - now); } if (!gotOffer) { @@ -447,7 +449,9 @@ extern "C" void _start() { } } } - montauk::yield(); + uint64_t now = montauk::get_milliseconds(); + if (now >= startMs + 10000) break; + montauk::wait_handle(fd, Montauk::IPC_SIGNAL_READABLE, startMs + 10000 - now); } montauk::closesocket(fd); diff --git a/programs/src/fetch/main.cpp b/programs/src/fetch/main.cpp index 3f1bb1a..578e3ff 100644 --- a/programs/src/fetch/main.cpp +++ b/programs/src/fetch/main.cpp @@ -198,8 +198,14 @@ static int plain_http_exchange(int fd, const char* request, int reqLen, if (r > 0) { respLen += r; deadline = montauk::get_milliseconds() + 15000; } else if (r < 0) break; else { - if (montauk::get_milliseconds() >= deadline) break; - montauk::sleep_ms(1); + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) break; + uint32_t signals = montauk::wait_handle( + fd, + Montauk::IPC_SIGNAL_READABLE | Montauk::IPC_SIGNAL_PEER_CLOSED, + deadline - now + ); + if (signals == 0 || signals == (uint32_t)-1) break; } } return respLen; diff --git a/programs/src/httpd/main.cpp b/programs/src/httpd/main.cpp index cd545b9..5754a6a 100644 --- a/programs/src/httpd/main.cpp +++ b/programs/src/httpd/main.cpp @@ -386,9 +386,17 @@ static void handle_client(int clientFd) { } else if (r == 0) { break; // Connection closed } else { - idleCount++; - if (idleCount > 500) break; // Timeout - montauk::yield(); + uint32_t signals = montauk::wait_handle( + clientFd, + Montauk::IPC_SIGNAL_READABLE | Montauk::IPC_SIGNAL_PEER_CLOSED, + 20 + ); + if (signals == 0) { + idleCount++; + if (idleCount > 500) break; + } else if (signals & Montauk::IPC_SIGNAL_PEER_CLOSED) { + break; + } } } reqBuf[reqLen] = '\0'; diff --git a/programs/src/irc/main.cpp b/programs/src/irc/main.cpp index 6a70568..5a168c9 100644 --- a/programs/src/irc/main.cpp +++ b/programs/src/irc/main.cpp @@ -1028,7 +1028,9 @@ extern "C" void _start() { } } else { if (!dirty) { - montauk::yield(); + montauk::wait_handle(irc.fd, + Montauk::IPC_SIGNAL_READABLE | Montauk::IPC_SIGNAL_PEER_CLOSED, + 10); continue; } } diff --git a/programs/src/tcpconnect/main.cpp b/programs/src/tcpconnect/main.cpp index 0a37e81..387ed8d 100644 --- a/programs/src/tcpconnect/main.cpp +++ b/programs/src/tcpconnect/main.cpp @@ -187,8 +187,7 @@ extern "C" void _start() { montauk::putchar(ev.ascii); } } else { - // No key and no data -- yield to avoid busy-spinning - montauk::yield(); + montauk::wait_handle(fd, Montauk::IPC_SIGNAL_READABLE | Montauk::IPC_SIGNAL_PEER_CLOSED, 10); } }