feat: add Music app features, fix Window Server bug
This commit is contained in:
+124
-53
@@ -12,12 +12,24 @@
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
static WindowSlot g_slots[MaxWindows];
|
||||
static int g_uiScale = 1;
|
||||
static kcp::Mutex wsLock;
|
||||
static constexpr uint64_t RetireGraceMs = 2000;
|
||||
static constexpr int MaxRetiredBatches = MaxWindows * 4;
|
||||
|
||||
struct RetiredBatch {
|
||||
bool used;
|
||||
int numPages;
|
||||
uint64_t freeAfterMs;
|
||||
uint64_t physPages[MaxPixelPages];
|
||||
};
|
||||
|
||||
static RetiredBatch g_retired[MaxRetiredBatches];
|
||||
|
||||
// RAII lock guard for WinServer operations
|
||||
struct WsGuard {
|
||||
@@ -25,9 +37,85 @@ namespace WinServer {
|
||||
~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 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 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
int slotIdx = -1;
|
||||
for (int i = 0; i < MaxWindows; i++) {
|
||||
@@ -102,20 +190,15 @@ namespace WinServer {
|
||||
|
||||
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;
|
||||
|
||||
// Do NOT unmap snapshot pages from the desktop here -- the desktop
|
||||
// may be running on another CPU with stale TLB entries. Unmapping
|
||||
// causes a page fault when the TLB entry is evicted. Just free the
|
||||
// physical pages (PTEs stay present, pointing to freed memory --
|
||||
// the desktop reads garbage for at most one frame before its next
|
||||
// Enumerate sees the window is gone). FreeUserHalf reclaims the
|
||||
// desktop's page table entries when the desktop eventually exits.
|
||||
|
||||
// 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) {
|
||||
@@ -127,23 +210,26 @@ namespace WinServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Free physical pixel pages and snapshot pages
|
||||
for (int i = 0; i < slot.pixelNumPages; i++) {
|
||||
if (slot.pixelPhysPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(slot.pixelPhysPages[i]));
|
||||
}
|
||||
if (slot.snapshotPhysPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(slot.snapshotPhysPages[i]));
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
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; }
|
||||
@@ -170,6 +256,7 @@ namespace WinServer {
|
||||
|
||||
int Poll(int windowId, int callerPid, Montauk::WinEvent* outEvent) {
|
||||
WsGuard guard;
|
||||
ProcessRetiredLocked();
|
||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
||||
@@ -183,6 +270,7 @@ namespace WinServer {
|
||||
|
||||
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;
|
||||
@@ -202,6 +290,7 @@ 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;
|
||||
@@ -245,12 +334,14 @@ namespace WinServer {
|
||||
|
||||
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;
|
||||
@@ -264,34 +355,17 @@ namespace WinServer {
|
||||
int numPages = (int)((bufSize + 0xFFF) / 0x1000);
|
||||
if (numPages > MaxPixelPages) return -1;
|
||||
|
||||
// Do NOT unmap/free snapshot pages from the desktop here.
|
||||
// The desktop may be running on another CPU with stale TLB entries
|
||||
// pointing to the old snapshot pages. Unmapping + freeing them
|
||||
// causes a page fault on the desktop when the TLB entry is evicted.
|
||||
// Instead, leave them mapped -- the desktop will get new snapshot
|
||||
// pages on its next Map call (desktopVa is invalidated below).
|
||||
// The old snapshot pages remain in the desktop's page tables and
|
||||
// are reclaimed by FreeUserHalf when the desktop exits.
|
||||
|
||||
// Unmap old LIVE pixel pages from owner's address space and free them.
|
||||
// Free old snapshot pages too (don't leak). The desktop's PTEs still
|
||||
// point to the old physical addresses, but we don't clear them (no
|
||||
// UnmapUserIn for desktop) to avoid TLB-eviction page faults. The
|
||||
// desktop may read one frame of garbage from the freed pages before
|
||||
// re-mapping -- acceptable during a resize.
|
||||
// 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);
|
||||
if (slot.pixelPhysPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(slot.pixelPhysPages[i]));
|
||||
slot.pixelPhysPages[i] = 0;
|
||||
}
|
||||
if (slot.snapshotPhysPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(slot.snapshotPhysPages[i]));
|
||||
slot.snapshotPhysPages[i] = 0;
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -325,6 +399,7 @@ namespace WinServer {
|
||||
|
||||
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;
|
||||
@@ -334,6 +409,7 @@ namespace WinServer {
|
||||
|
||||
int SetScale(int scale) {
|
||||
WsGuard guard;
|
||||
ProcessRetiredLocked();
|
||||
if (scale < 0) scale = 0;
|
||||
if (scale > 2) scale = 2;
|
||||
g_uiScale = scale;
|
||||
@@ -357,30 +433,25 @@ 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 unmap snapshot pages from the desktop here -- the
|
||||
// desktop may be on another CPU with stale TLB entries.
|
||||
// Just free the physical pages below (PTEs stay present,
|
||||
// desktop reads garbage for one frame, then its next
|
||||
// Enumerate sees the window is gone).
|
||||
|
||||
// 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.
|
||||
|
||||
// Free snapshot pages (not in the owner's page tables).
|
||||
for (int p = 0; p < g_slots[i].pixelNumPages; p++) {
|
||||
if (g_slots[i].snapshotPhysPages[p] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(g_slots[i].snapshotPhysPages[p]));
|
||||
g_slots[i].snapshotPhysPages[p] = 0;
|
||||
}
|
||||
}
|
||||
// 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),
|
||||
|
||||
Reference in New Issue
Block a user