fix: kernel and desktop bug fixes
This commit is contained in:
+333
-42
@@ -15,10 +15,14 @@
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Api/UserMemory.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <Common/Panic.hpp>
|
||||
|
||||
namespace Ipc {
|
||||
|
||||
@@ -132,7 +136,13 @@ namespace Ipc {
|
||||
uint32_t pageIndex = (uint32_t)(byteOffset / 0x1000ULL);
|
||||
uint32_t pageOffset = (uint32_t)(byteOffset % 0x1000ULL);
|
||||
if (pageIndex >= surface->numPages) return nullptr;
|
||||
return (uint32_t*)((uint8_t*)Memory::HHDM(surface->physPages[pageIndex]) + pageOffset);
|
||||
// A 0 physPage means the slot was freed but not yet repopulated
|
||||
// (e.g. transiently during a failed ResizeSurface). HHDM(0) is a
|
||||
// valid kernel virtual address, so returning it would silently
|
||||
// dereference into kernel memory; return nullptr instead.
|
||||
uint64_t phys = surface->physPages[pageIndex];
|
||||
if (phys == 0) return nullptr;
|
||||
return (uint32_t*)((uint8_t*)Memory::HHDM(phys) + pageOffset);
|
||||
}
|
||||
|
||||
static HandleEntry g_handleTables[Sched::MaxProcesses][MaxHandlesPerProcess] = {};
|
||||
@@ -160,6 +170,100 @@ namespace Ipc {
|
||||
|
||||
static void ReleaseRawObject(Object* object);
|
||||
|
||||
static kcp::Mutex g_tlbShootdownLock;
|
||||
static volatile uint64_t g_tlbShootdownSeq = 0;
|
||||
static volatile uint64_t g_tlbShootdownPml4 = 0;
|
||||
static volatile uint64_t g_tlbShootdownStartVa = 0;
|
||||
static volatile uint32_t g_tlbShootdownPages = 0;
|
||||
static volatile uint64_t g_tlbShootdownDone[Smp::MaxCPUs] = {};
|
||||
|
||||
static bool CpuCurrentlyUsesPml4(Smp::CpuData* cpu, uint64_t pml4Phys) {
|
||||
if (cpu == nullptr || pml4Phys == 0 || cpu->currentSlot < 0) return false;
|
||||
|
||||
Sched::Process* proc = Sched::GetProcessSlot(cpu->currentSlot);
|
||||
if (proc == nullptr) return false;
|
||||
if (proc->state == Sched::ProcessState::Free) return false;
|
||||
return proc->pml4Phys == pml4Phys;
|
||||
}
|
||||
|
||||
static void InvalidateLocalUserRange(uint64_t startVa, uint32_t pages) {
|
||||
if (pages == 0) return;
|
||||
|
||||
if (pages > 1024) {
|
||||
Memory::VMM::FlushTLB();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t p = 0; p < pages; p++) {
|
||||
uint64_t va = startVa + (uint64_t)p * 0x1000ULL;
|
||||
asm volatile("invlpg (%0)" :: "r"(va) : "memory");
|
||||
}
|
||||
}
|
||||
|
||||
static void TlbShootdownIpiHandler(uint8_t) {
|
||||
Smp::CpuData* cpu = Smp::GetCurrentCpuData();
|
||||
uint64_t seq = g_tlbShootdownSeq;
|
||||
uint64_t pml4 = g_tlbShootdownPml4;
|
||||
uint64_t startVa = g_tlbShootdownStartVa;
|
||||
uint32_t pages = g_tlbShootdownPages;
|
||||
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
|
||||
if (cpu != nullptr && cpu->cpuIndex >= 0 && cpu->cpuIndex < Smp::MaxCPUs) {
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownDone[cpu->cpuIndex] = seq;
|
||||
}
|
||||
}
|
||||
|
||||
static void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages) {
|
||||
if (pml4Phys == 0 || pages == 0) return;
|
||||
|
||||
bool targets[Smp::MaxCPUs] = {};
|
||||
Smp::CpuData* currentCpu = Smp::GetCurrentCpuData();
|
||||
int currentCpuIndex = currentCpu ? currentCpu->cpuIndex : -1;
|
||||
|
||||
g_tlbShootdownLock.Acquire();
|
||||
uint64_t seq = g_tlbShootdownSeq + 1;
|
||||
g_tlbShootdownPml4 = pml4Phys;
|
||||
g_tlbShootdownStartVa = startVa;
|
||||
g_tlbShootdownPages = pages;
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownSeq = seq;
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu == nullptr || !cpu->started) continue;
|
||||
|
||||
if (i == currentCpuIndex) {
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
targets[i] = true;
|
||||
Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
if (!targets[i]) continue;
|
||||
while (g_tlbShootdownDone[i] != seq) {
|
||||
asm volatile("pause");
|
||||
}
|
||||
}
|
||||
|
||||
g_tlbShootdownLock.Release();
|
||||
}
|
||||
|
||||
static void InitObject(Object& object, HandleType type) {
|
||||
object.type = type;
|
||||
object.active = true;
|
||||
@@ -374,14 +478,15 @@ namespace Ipc {
|
||||
|
||||
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;
|
||||
}
|
||||
if (numPages == 1) return Memory::g_pfa->AllocateZeroed();
|
||||
|
||||
// ReallocConsecutive(nullptr, n) allocates an n-page contiguous span
|
||||
// directly. The previous implementation always allocated a single
|
||||
// throwaway page first, then asked ReallocConsecutive to migrate from
|
||||
// it -- which copies and frees the throwaway page for no benefit.
|
||||
void* span = Memory::g_pfa->ReallocConsecutive(nullptr, numPages);
|
||||
if (span == nullptr) return nullptr;
|
||||
memset(span, 0, (size_t)numPages * 0x1000);
|
||||
return span;
|
||||
}
|
||||
|
||||
@@ -669,32 +774,36 @@ namespace Ipc {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int written = 0;
|
||||
uint32_t head = stream->head;
|
||||
uint32_t cap = stream->capacity;
|
||||
uint32_t space = cap - stream->count;
|
||||
int first = ((uint32_t)len < space) ? ((head + (uint32_t)len <= cap) ? len : (cap - head)) : (cap - head);
|
||||
|
||||
// Total bytes we can accept is bounded by both requested length and free space.
|
||||
// The previous implementation could write up to (cap - head) bytes whenever
|
||||
// len >= space, which exceeded `space` whenever cap-head > space and silently
|
||||
// corrupted the ring buffer (count > cap, overwritten unread data).
|
||||
uint32_t toWrite = ((uint32_t)len < space) ? (uint32_t)len : space;
|
||||
|
||||
// First contiguous chunk: from head to end of buffer
|
||||
uint32_t first = (toWrite < (cap - head)) ? toWrite : (cap - head);
|
||||
if (first > 0) {
|
||||
memcpy(stream->buffer + head, data, first);
|
||||
head = (head + first) % cap;
|
||||
stream->count += first;
|
||||
written = first;
|
||||
}
|
||||
if (written < len && stream->count < cap) {
|
||||
int second = len - written;
|
||||
if (second > (int)(cap - stream->count)) second = cap - stream->count;
|
||||
if (second > 0) {
|
||||
memcpy(stream->buffer + head, data + written, second);
|
||||
head = (head + second) % cap;
|
||||
stream->count += second;
|
||||
written += second;
|
||||
}
|
||||
// Remainder wraps to the start of the buffer
|
||||
uint32_t second = toWrite - first;
|
||||
if (second > 0) {
|
||||
memcpy(stream->buffer + head, data + first, second);
|
||||
head = (head + second) % cap;
|
||||
stream->count += second;
|
||||
}
|
||||
|
||||
stream->head = head;
|
||||
stream->lock.Release();
|
||||
|
||||
if (written > 0) NotifyObjectChanged((Object*)stream);
|
||||
return written;
|
||||
if (toWrite > 0) NotifyObjectChanged((Object*)stream);
|
||||
return (int)toWrite;
|
||||
}
|
||||
|
||||
int StreamReadHandle(int handle, uint8_t* out, int maxLen) {
|
||||
@@ -1447,29 +1556,179 @@ namespace Ipc {
|
||||
return surface->sizeBytes;
|
||||
}
|
||||
|
||||
static void RollbackSurfaceGrowMaps(Surface* surface, uint32_t newPages) {
|
||||
for (int s = 0; s < Sched::MaxProcesses; s++) {
|
||||
auto* proc = Sched::GetProcessSlot(s);
|
||||
if (proc == nullptr) continue;
|
||||
if (proc->state == Sched::ProcessState::Free) continue;
|
||||
uint64_t pml4 = proc->pml4Phys;
|
||||
if (pml4 == 0) continue;
|
||||
|
||||
for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) {
|
||||
auto& m = g_surfaceMaps[s][i];
|
||||
if (!m.used || m.surface != surface) continue;
|
||||
if (newPages <= m.numPages) continue;
|
||||
|
||||
uint64_t startVa = m.va + (uint64_t)m.numPages * 0x1000ULL;
|
||||
uint32_t rollbackPages = newPages - m.numPages;
|
||||
for (uint32_t p = m.numPages; p < newPages; p++) {
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4, m.va + (uint64_t)p * 0x1000ULL);
|
||||
}
|
||||
ShootdownUserRange(pml4, startVa, rollbackPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool PreinstallSurfaceGrowMaps(Surface* surface, uint64_t* scratch, uint32_t newPages) {
|
||||
for (int s = 0; s < Sched::MaxProcesses; s++) {
|
||||
auto* proc = Sched::GetProcessSlot(s);
|
||||
if (proc == nullptr) continue;
|
||||
if (proc->state == Sched::ProcessState::Free) continue;
|
||||
uint64_t pml4 = proc->pml4Phys;
|
||||
if (pml4 == 0) continue;
|
||||
|
||||
for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) {
|
||||
auto& m = g_surfaceMaps[s][i];
|
||||
if (!m.used || m.surface != surface) continue;
|
||||
if (newPages <= m.numPages) continue;
|
||||
|
||||
for (uint32_t p = m.numPages; p < newPages; p++) {
|
||||
uint64_t va = m.va + (uint64_t)p * 0x1000ULL;
|
||||
if (!Memory::VMM::Paging::MapUserIn(pml4, scratch[p], va)) {
|
||||
RollbackSurfaceGrowMaps(surface, newPages);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 (newSize > (uint64_t)MaxSurfacePages * 0x1000ULL) return -1;
|
||||
uint32_t newPages = (uint32_t)((newSize + 0xFFFULL) / 0x1000ULL);
|
||||
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();
|
||||
|
||||
// Transactional resize: allocate all new pages BEFORE freeing the old
|
||||
// ones, then swap them in under the lock. The previous implementation
|
||||
// freed first and then allocated outside the lock, leaving readers
|
||||
// with numPages updated but physPages still zero (or transiently
|
||||
// freed) for the entire allocation window; on partial failure the
|
||||
// surface was left in a half-shrunken half-allocated state.
|
||||
// Heap-allocate the scratch arrays: with MaxSurfacePages=8192, two
|
||||
// uint64_t[8192] arrays are 128 KiB - far past the 16 KiB kernel stack.
|
||||
uint64_t* scratch = new uint64_t[newPages];
|
||||
if (scratch == nullptr) return -1;
|
||||
for (uint32_t i = 0; i < newPages; i++) scratch[i] = 0;
|
||||
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);
|
||||
if (page == nullptr) {
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(scratch[j]));
|
||||
}
|
||||
delete[] scratch;
|
||||
return -1;
|
||||
}
|
||||
scratch[i] = Memory::SubHHDM((uint64_t)page);
|
||||
}
|
||||
|
||||
surface->lock.Acquire();
|
||||
uint32_t oldNumPages = surface->numPages;
|
||||
uint64_t* oldPages = (oldNumPages > 0) ? new uint64_t[oldNumPages] : nullptr;
|
||||
if (oldNumPages > 0 && oldPages == nullptr) {
|
||||
surface->lock.Release();
|
||||
for (uint32_t i = 0; i < newPages; i++) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(scratch[i]));
|
||||
}
|
||||
delete[] scratch;
|
||||
return -1;
|
||||
}
|
||||
for (uint32_t i = 0; i < oldNumPages; i++) oldPages[i] = surface->physPages[i];
|
||||
|
||||
// Grow mappings can require new page-table pages. Preinstall the
|
||||
// additional leaf PTEs before changing the surface metadata so an OOM
|
||||
// can roll back cleanly instead of reporting a successful resize with
|
||||
// only some processes mapped to the larger range.
|
||||
if (!PreinstallSurfaceGrowMaps(surface, scratch, newPages)) {
|
||||
surface->lock.Release();
|
||||
for (uint32_t i = 0; i < newPages; i++) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(scratch[i]));
|
||||
}
|
||||
delete[] oldPages;
|
||||
delete[] scratch;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < newPages; i++) surface->physPages[i] = scratch[i];
|
||||
for (uint32_t i = newPages; i < oldNumPages; i++) surface->physPages[i] = 0;
|
||||
surface->numPages = newPages;
|
||||
surface->sizeBytes = (uint64_t)newPages * 0x1000ULL;
|
||||
|
||||
// Refresh every existing mapping of this surface so each mapper's PTEs
|
||||
// point at the new physical frames before we hand the old frames back
|
||||
// to the PFA. Without this, MapSurfaceHandle re-returns the cached VA
|
||||
// (see MapSurfaceForPid early-return) and any process that mapped the
|
||||
// surface before the resize keeps writing to freed pages -- corrupting
|
||||
// whatever the PFA next hands them out for.
|
||||
//
|
||||
// We hold surface->lock during the walk so g_surfaceMaps reads stay
|
||||
// coherent with the physPages snapshot we just committed. MapUserIn
|
||||
// overwriting an existing leaf PTE doesn't free the old frame -- that
|
||||
// happens below, after every mapping has been redirected and every CPU
|
||||
// currently running the affected address space has invalidated the
|
||||
// touched user range.
|
||||
for (int s = 0; s < Sched::MaxProcesses; s++) {
|
||||
auto* proc = Sched::GetProcessSlot(s);
|
||||
if (proc == nullptr) continue;
|
||||
if (proc->state == Sched::ProcessState::Free) continue;
|
||||
uint64_t pml4 = proc->pml4Phys;
|
||||
if (pml4 == 0) continue;
|
||||
|
||||
for (int i = 0; i < MaxSurfaceMapsPerProcess; i++) {
|
||||
auto& m = g_surfaceMaps[s][i];
|
||||
if (!m.used || m.surface != surface) continue;
|
||||
|
||||
uint64_t baseVa = m.va;
|
||||
uint32_t mapPages = m.numPages;
|
||||
uint32_t common = (mapPages < newPages) ? mapPages : newPages;
|
||||
uint32_t flushPages = (mapPages > newPages) ? mapPages : newPages;
|
||||
|
||||
// Pages that exist in both old and new: redirect PTE to the
|
||||
// new physical frame.
|
||||
// MapUserIn here only overwrites the leaf PTE -- all page
|
||||
// table levels are guaranteed present from the original map,
|
||||
// so walkLevel never has to allocate and cannot fail. Treat
|
||||
// failure as a kernel-state-corruption panic rather than a
|
||||
// recoverable error.
|
||||
for (uint32_t p = 0; p < common; p++) {
|
||||
uint64_t va = baseVa + (uint64_t)p * 0x1000ULL;
|
||||
if (!Memory::VMM::Paging::MapUserIn(pml4, surface->physPages[p], va)) {
|
||||
Panic("ResizeSurface: MapUserIn failed refreshing existing PTE", nullptr);
|
||||
}
|
||||
}
|
||||
// Pages trimmed off the end on shrink: unmap them entirely.
|
||||
for (uint32_t p = newPages; p < mapPages; p++) {
|
||||
uint64_t va = baseVa + (uint64_t)p * 0x1000ULL;
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4, va);
|
||||
}
|
||||
|
||||
ShootdownUserRange(pml4, baseVa, flushPages);
|
||||
m.numPages = newPages;
|
||||
}
|
||||
}
|
||||
surface->lock.Release();
|
||||
|
||||
for (uint32_t i = 0; i < oldNumPages; i++) {
|
||||
if (oldPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(oldPages[i]));
|
||||
}
|
||||
}
|
||||
delete[] oldPages;
|
||||
delete[] scratch;
|
||||
NotifyObjectChanged((Object*)surface);
|
||||
return 0;
|
||||
}
|
||||
@@ -1553,9 +1812,21 @@ namespace Ipc {
|
||||
int slot = SlotForPid(pid);
|
||||
if (slot < 0) return -1;
|
||||
|
||||
// surface->lock serialises us against ResizeSurface so the
|
||||
// numPages/physPages snapshot we install into the caller's page tables
|
||||
// is internally consistent. Without this, a resize landing mid-loop
|
||||
// could surface zero/freed physical addresses (see ResizeSurface),
|
||||
// causing MapUserIn to map PA 0 into the caller's address space.
|
||||
//
|
||||
// RetainRawObject also takes surface->lock (Surface inherits its lock
|
||||
// from Object), so the refcount bump is inlined below to avoid
|
||||
// self-deadlocking on the non-reentrant mutex.
|
||||
surface->lock.Acquire();
|
||||
|
||||
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;
|
||||
surface->lock.Release();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1567,15 +1838,20 @@ namespace Ipc {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mapIdx < 0) return -1;
|
||||
if (mapIdx < 0) {
|
||||
surface->lock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t numPages = surface->numPages;
|
||||
uint64_t baseVa = heapNext;
|
||||
for (uint32_t i = 0; i < surface->numPages; i++) {
|
||||
for (uint32_t i = 0; i < 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);
|
||||
}
|
||||
surface->lock.Release();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -1583,11 +1859,25 @@ namespace Ipc {
|
||||
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);
|
||||
g_surfaceMaps[slot][mapIdx].numPages = numPages;
|
||||
surface->refs++; // inlined RetainRawObject — we already hold surface->lock
|
||||
|
||||
heapNext += (uint64_t)surface->numPages * 0x1000ULL;
|
||||
// Reserve the full surface address-range cap, not just the currently
|
||||
// mapped pages. ResizeSurface can grow this surface up to
|
||||
// MaxSurfacePages, and its grow path extends the existing VA range
|
||||
// in-place by mapping new pages at baseVa + p*0x1000 for p beyond the
|
||||
// original size. If we only bumped heapNext by numPages*0x1000, any
|
||||
// subsequent mapping (another surface, heap allocation, etc.) would
|
||||
// sit at baseVa + numPages*0x1000 -- and a later grow would silently
|
||||
// overwrite its PTEs. Reserving MaxSurfacePages*0x1000 (32 MiB) of
|
||||
// VA per mapping is cheap: 47-bit user VA gives 128 TiB of headroom,
|
||||
// and at most MaxSurfaceMapsPerProcess (64) mappings per process
|
||||
// means at most 2 GiB of reserved VA per process. Page tables are
|
||||
// only populated for pages actually mapped; unused VA in the
|
||||
// reservation costs nothing beyond the address-space slot.
|
||||
heapNext += (uint64_t)MaxSurfacePages * 0x1000ULL;
|
||||
outVa = baseVa;
|
||||
surface->lock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1982,6 +2272,7 @@ namespace Ipc {
|
||||
for (int i = 0; i < Sched::MaxProcesses; i++) {
|
||||
g_processObjectsBySlot[i] = nullptr;
|
||||
}
|
||||
Hal::RegisterIrqHandler(Hal::IRQ_TLB_SHOOTDOWN, TlbShootdownIpiHandler);
|
||||
Kt::KernelLogStream(Kt::OK, "IPC") << "Initialized ("
|
||||
<< (uint64_t)MaxHandlesPerProcess << " handles/process, "
|
||||
<< (uint64_t)MaxStreams << " streams, "
|
||||
|
||||
Reference in New Issue
Block a user