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:
102
src/core/kernel/fcram.cpp
Normal file
102
src/core/kernel/fcram.cpp
Normal 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!");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user