Memory rework pt 2 (#801)

* Memory: Rework FCRAM management entirely

Disables a lot of functionality... but I didn't want to commit too much to this commit
Also reworks virtual memory management somewhat (but needs more work)

* Accurately handle MemoryState for virtual memory

Previously all non-free blocks were marked as Reserved

* Memory: Consolidate state and permission changes

Can now use a single function to change either state, permissions, or both
Also merge vmem blocks that have the same state and permissions

* Memory: Fix double reset for FCRAM manager

Fix minor bug with permission tracking

* Memory: Implement Protect operation in ControlMemory

* Memory: Implement Unmap in ControlMemory

Also do a sanity check to make sure the memory region is free for linear allocations

* Memory: Make TLS only 0x200 bytes for each thread

Also move TLS to Base region

* RO: Unmap CROs when unloaded

Thanks @noumidev

* Kernel: Return used app memory for Commit ResourceLimit

Not quite correct, but nothing to be done until process management is improved
Also remove the stack limit for CXIs (thanks amogus)

* Kernel: Report used app memory for GetProcessInfo 2

Not really correct, but it should be accurate for applications at least

* Formatting changes

* Initial fastmem support

* PCSX2 fastmem depression

* Move away from PCSX2 fastmem

* Add enum_flag_ops.hpp

* Finally building on Windows

* Almost got a PoC

* Fix arm64 builds

* This somehow works

* This also works...

* Properly fix fastmem

* Add free region manager

* Update boost

* Add ScopeExit

* Comment out asserts on Linux/Mac/Android

* Comment out ASSERT_MSG asserts too

* Fix derp

* Attempt to fix Android

* Disable fastmem on Android

* Fix Android again maybe pt 2

* android pls

* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

* Update host_memory.cpp

* Properly reset memory arena on reset

* Proper ashmem code for Android

* more

* Add temporary Android buildjet script for faster prototype builds

* Fix fastmem (again)

* Clean up shared memory

* Remove Android BuildJet runner

* a

* Revert "a"

This reverts commit 5443ad6f2a794c19c9b1a1567ca1c7f58eed78cd.

* Re-add ELF support

* Re-add 3DSX support

* GetSystemInfo, GetProcessInfo: Memory sizes should be in bytes

* Update Boost

* Update metal-cpp

* Fix metal renderer compilation

* Fix fastmem mapping

* Clean up fastmem code

* Fix oopsie again

* Emulator: Reorder struct

* Kernel types: Cleanup

* Cleanup

* More cleanup

* Make invalid mprotects warn instead of panicking

* Add setting for toggling fastmem

* More cleanup

* Properly initialize BSS to zeroes

* Remove unused code

* Formatting

* Cleanup

* Memory/CRO: Workaround for Pokemon XY

* NCSD loader: Fix BSS (again)

* NCSD loader: Fix BSS (again) (again)

* More memory fixes

* Memory: Remove unused code

* FS: Warn on unimplemented functions instead of panic

* Update software_keyboard.cpp

* Libretro: Add fastmem option

* FRD: Stub SaveLocalAccountData

---------

Co-authored-by: PSI-Rockin <PSI-Rockin@users.noreply.github.com>
This commit is contained in:
wheremyfoodat
2025-08-07 20:18:09 +03:00
committed by GitHub
parent 5ebee8ea72
commit 6d1ef7cb4f
40 changed files with 2901 additions and 526 deletions

102
src/core/kernel/fcram.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "fcram.hpp"
#include "memory.hpp"
void KFcram::Region::reset(u32 start, size_t size) {
this->start = start;
pages = size >> 12;
freePages = pages;
Block initialBlock(pages, 0);
blocks.clear();
blocks.push_back(initialBlock);
}
void KFcram::Region::alloc(std::list<FcramBlock>& out, s32 allocPages, bool linear) {
for (auto it = blocks.begin(); it != blocks.end(); it++) {
if (it->used) continue;
// On linear allocations, only a single contiguous block may be used
if (it->pages < allocPages && linear) continue;
// If the current block is bigger than the allocation, split it
if (it->pages > allocPages) {
Block newBlock(it->pages - allocPages, it->pageOffset + allocPages);
it->pages = allocPages;
blocks.insert(it, newBlock);
}
// Mark the block as allocated and add it to the output list
it->used = true;
allocPages -= it->pages;
freePages -= it->pages;
u32 paddr = start + (it->pageOffset << 12);
FcramBlock outBlock(paddr, it->pages);
out.push_back(outBlock);
if (allocPages < 1) {
return;
}
}
// Official kernel panics here
Helpers::panic("Failed to allocate FCRAM, not enough guest memory");
}
u32 KFcram::Region::getUsedCount() { return pages - freePages; }
u32 KFcram::Region::getFreeCount() { return freePages; }
KFcram::KFcram(Memory& mem) : mem(mem) {}
void KFcram::reset(size_t ramSize, size_t appSize, size_t sysSize, size_t baseSize) {
fcram = mem.getFCRAM();
refs = std::unique_ptr<u32>(new u32[ramSize >> 12]);
std::memset(refs.get(), 0, (ramSize >> 12) * sizeof(u32));
appRegion.reset(0, appSize);
sysRegion.reset(appSize, sysSize);
baseRegion.reset(appSize + sysSize, baseSize);
}
void KFcram::alloc(FcramBlockList& out, s32 pages, FcramRegion region, bool linear) {
switch (region) {
case FcramRegion::App: appRegion.alloc(out, pages, linear); break;
case FcramRegion::Sys: sysRegion.alloc(out, pages, linear); break;
case FcramRegion::Base: baseRegion.alloc(out, pages, linear); break;
default: Helpers::panic("Invalid FCRAM region chosen for allocation!"); break;
}
incRef(out);
}
void KFcram::incRef(FcramBlockList& list) {
for (auto it = list.begin(); it != list.end(); it++) {
for (int i = 0; i < it->pages; i++) {
u32 index = (it->paddr >> 12) + i;
refs.get()[index]++;
}
}
}
void KFcram::decRef(FcramBlockList& list) {
for (auto it = list.begin(); it != list.end(); it++) {
for (int i = 0; i < it->pages; i++) {
u32 index = (it->paddr >> 12) + i;
refs.get()[index]--;
if (!refs.get()[index]) {
Helpers::panic("TODO: Freeing FCRAM");
}
}
}
}
u32 KFcram::getUsedCount(FcramRegion region) {
switch (region) {
case FcramRegion::App: return appRegion.getUsedCount();
case FcramRegion::Sys: return sysRegion.getUsedCount();
case FcramRegion::Base: return baseRegion.getUsedCount();
default: Helpers::panic("Invalid FCRAM region in getUsedCount!");
}
}

View File

@@ -17,6 +17,8 @@ idle_thread_main:
b idle_thread_main
*/
using namespace KernelMemoryTypes;
static constexpr u8 idleThreadCode[] = {
0x00, 0x00, 0xA0, 0xE3, // mov r0, #0
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
@@ -27,18 +29,16 @@ static constexpr u8 idleThreadCode[] = {
// Set up an idle thread to run when no thread is able to run
void Kernel::setupIdleThread() {
Thread& t = threads[idleThreadIndex];
constexpr u32 codeAddress = 0xBFC00000;
// Reserve some memory for the idle thread's code. We map this memory to vaddr BFC00000 which is not userland-accessible
// Reserve some memory for the idle thread's code. We map this memory to vaddr 3FC00000 which shouldn't be accessed by applications
// We only allocate 4KB (1 page) because our idle code is pretty small
const u32 fcramIndex = mem.allocateSysMemory(Memory::pageSize);
auto vaddr = mem.allocateMemory(codeAddress, fcramIndex, Memory::pageSize, true, true, false, true, false, true);
if (!vaddr.has_value() || vaddr.value() != codeAddress) {
constexpr u32 codeAddress = 0x3FC00000;
if (!mem.allocMemory(codeAddress, 1, FcramRegion::Base, true, true, false, MemoryState::Locked)) {
Helpers::panic("Failed to setup idle thread");
}
// Copy idle thread code to the allocated FCRAM
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
mem.copyToVaddr(codeAddress, idleThreadCode, sizeof(idleThreadCode));
t.entrypoint = codeAddress;
t.initialSP = 0;

View File

@@ -6,7 +6,7 @@
#include "kernel_types.hpp"
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua)
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config, lua) {
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config, lua), fcramManager(mem) {
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
mutexHandles.reserve(8);
portHandles.reserve(32);
@@ -180,9 +180,7 @@ void Kernel::reset() {
}
// Get pointer to thread-local storage
u32 Kernel::getTLSPointer() {
return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize;
}
u32 Kernel::getTLSPointer() { return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; }
// Result CloseHandle(Handle handle)
void Kernel::svcCloseHandle() {
@@ -271,7 +269,7 @@ void Kernel::getProcessInfo() {
// According to 3DBrew: Amount of private (code, data, heap) memory used by the process + total supervisor-mode
// stack size + page-rounded size of the external handle table
case 2:
regs[1] = mem.getUsedUserMem();
regs[1] = fcramManager.getUsedCount(FcramRegion::App) * Memory::pageSize;
regs[2] = 0;
break;
@@ -364,7 +362,7 @@ void Kernel::getSystemInfo() {
switch (subtype) {
// Total used memory size in the APPLICATION memory region
case 1:
regs[1] = mem.getUsedUserMem();
regs[1] = fcramManager.getUsedCount(FcramRegion::App) * Memory::pageSize;
regs[2] = 0;
break;

View File

@@ -30,10 +30,10 @@ namespace MemoryPermissions {
};
}
using namespace KernelMemoryTypes;
// Returns whether "value" is aligned to a page boundary (Ie a boundary of 4096 bytes)
static constexpr bool isAligned(u32 value) {
return (value & 0xFFF) == 0;
}
static constexpr bool isAligned(u32 value) { return (value & 0xFFF) == 0; }
// Result ControlMemory(u32* outaddr, u32 addr0, u32 addr1, u32 size,
// MemoryOperation operation, MemoryPermission permissions)
@@ -44,6 +44,7 @@ void Kernel::controlMemory() {
u32 addr0 = regs[1];
u32 addr1 = regs[2];
u32 size = regs[3];
u32 pages = size >> 12; // Official kernel truncates nonaligned sizes
u32 perms = regs[4];
if (perms == MemoryPermissions::DontCare) {
@@ -61,7 +62,7 @@ void Kernel::controlMemory() {
Helpers::panic("ControlMemory: attempted to allocate executable memory");
}
if (!isAligned(addr0) || !isAligned(addr1) || !isAligned(size)) {
if (!isAligned(addr0) || !isAligned(addr1)) {
Helpers::panic("ControlMemory: Unaligned parameters\nAddr0: %08X\nAddr1: %08X\nSize: %08X", addr0, addr1, size);
}
@@ -72,22 +73,54 @@ void Kernel::controlMemory() {
switch (operation & 0xFF) {
case Operation::Commit: {
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear, r, w, x, true);
if (!address.has_value()) {
Helpers::panic("ControlMemory: Failed to allocate memory");
// TODO: base this from the exheader
auto region = FcramRegion::App;
u32 outAddr = 0;
if (linear) {
if (!mem.allocMemoryLinear(outAddr, addr0, pages, region, r, w, false)) {
Helpers::panic("ControlMemory: Failed to allocate linear memory");
}
} else {
if (!mem.allocMemory(addr0, pages, region, r, w, false, MemoryState::Private)) {
Helpers::panic("ControlMemory: Failed to allocate memory");
}
outAddr = addr0;
}
regs[1] = address.value();
regs[1] = outAddr;
break;
}
case Operation::Map: mem.mirrorMapping(addr0, addr1, size); break;
case Operation::Map:
// Official kernel only allows Private regions to be mapped to Free regions. An Alias or Aliased region cannot be mapped again
if (!mem.mapVirtualMemory(
addr0, addr1, pages, r, w, false, MemoryState::Free, MemoryState::Private, MemoryState::Alias, MemoryState::Aliased
))
Helpers::panic("ControlMemory: Failed to map memory");
break;
case Operation::Unmap:
// The same as a Map operation, except in reverse
if (!mem.mapVirtualMemory(
addr0, addr1, pages, false, false, false, MemoryState::Alias, MemoryState::Aliased, MemoryState::Free, MemoryState::Private
)) {
Helpers::panic("ControlMemory: Failed to unmap memory");
}
break;
case Operation::Protect:
Helpers::warn(
"Ignoring mprotect! Hope nothing goes wrong but if the game accesses invalid memory or crashes then we prolly need to implement "
"this\n"
);
// Official kernel has an internal state bit to indicate that the region's permissions may be changed
// But this should account for all cases
if (!mem.testMemoryState(addr0, pages, MemoryState::Private) && !mem.testMemoryState(addr0, pages, MemoryState::Alias) &&
!mem.testMemoryState(addr0, pages, MemoryState::Aliased) && !mem.testMemoryState(addr0, pages, MemoryState::AliasCode)) {
Helpers::warn("Tried to mprotect invalid region!");
return;
}
mem.changePermissions(addr0, pages, r, w, false);
regs[1] = addr0;
break;
default: Helpers::warn("ControlMemory: unknown operation %X\n", operation); break;
@@ -104,10 +137,11 @@ void Kernel::queryMemory() {
logSVC("QueryMemory(mem info pointer = %08X, page info pointer = %08X, addr = %08X)\n", memInfo, pageInfo, addr);
const auto info = mem.queryMemory(addr);
regs[0] = Result::Success;
KernelMemoryTypes::MemoryInfo info;
const auto result = mem.queryMemory(info, addr);
regs[0] = result;
regs[1] = info.baseAddr;
regs[2] = info.size;
regs[2] = info.pages * Memory::pageSize;
regs[3] = info.perms;
regs[4] = info.state;
regs[5] = 0; // page flags

View File

@@ -82,7 +82,9 @@ void Kernel::getResourceLimitCurrentValues() {
s32 Kernel::getCurrentResourceValue(const KernelObject* limit, u32 resourceName) {
const auto data = static_cast<ResourceLimits*>(limit->data);
switch (resourceName) {
case ResourceType::Commit: return mem.usedUserMemory;
// TODO: needs to use the current amount of memory allocated by the process
case ResourceType::Commit: return fcramManager.getUsedCount(FcramRegion::App) * Memory::pageSize;
case ResourceType::Thread: return threadIndices.size();
default: Helpers::panic("Attempted to get current value of unknown kernel resource: %d\n", resourceName);
}